Skip to content

Commit 1fb6c71

Browse files
committed
detectmissing: init
This lets users detect missing files and albums, in case they've deleted files outside of `beet remove -d`.
1 parent 0fec858 commit 1fb6c71

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

beets/library/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class Album(LibModel):
224224
Reflects the library's "albums" table, including album art.
225225
"""
226226

227-
artpath: bytes
227+
artpath: bytes | None
228228

229229
_table = "albums"
230230
_flex_table = "album_attributes"

beetsplug/detectmissing.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# This file is part of beets.
2+
# Copyright 2025, Rebecca Turner.
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining
5+
# a copy of this software and associated documentation files (the
6+
# "Software"), to deal in the Software without restriction, including
7+
# without limitation the rights to use, copy, modify, merge, publish,
8+
# distribute, sublicense, and/or sell copies of the Software, and to
9+
# permit persons to whom the Software is furnished to do so, subject to
10+
# the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be
13+
# included in all copies or substantial portions of the Software.
14+
"""Detect missing files, folders, and album art."""
15+
16+
from __future__ import annotations
17+
18+
import optparse
19+
import os
20+
from pathlib import Path
21+
22+
from beets import plugins, ui, util
23+
from beets.library import Library
24+
25+
26+
class DetectMissingPlugin(plugins.BeetsPlugin):
27+
def __init__(self) -> None:
28+
super().__init__()
29+
30+
def commands(self) -> list[ui.Subcommand]:
31+
cmd = ui.Subcommand(
32+
"detectmissing", help="Detect missing files, folders, and album art"
33+
)
34+
35+
cmd.parser.add_option(
36+
"--delete",
37+
help="Also delete missing items from the library",
38+
action="store_true",
39+
)
40+
41+
cmd.func = self.detect_missing
42+
43+
return [cmd]
44+
45+
def detect_missing(
46+
self, lib: Library, opts: optparse.Values, _args: list[str]
47+
) -> None:
48+
should_delete: bool = opts.delete or False
49+
50+
for album in lib.albums():
51+
art_filepath = album.art_filepath
52+
if art_filepath is not None and not art_filepath.exists():
53+
print(f"{art_filepath}")
54+
55+
if should_delete:
56+
with lib.transaction():
57+
album.artpath = None
58+
album.store(fields=["artpath"])
59+
60+
for item in lib.items():
61+
if item.path is not None:
62+
path = Path(os.fsdecode(item.path))
63+
if not path.exists():
64+
print(f"{path}")
65+
66+
if should_delete:
67+
util.prune_dirs(
68+
os.path.dirname(item.path), item._db.directory
69+
)
70+
with lib.transaction():
71+
item.remove(delete=False, with_album=True)

0 commit comments

Comments
 (0)