Skip to content

Commit 7de70d8

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 7de70d8

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 os
19+
from pathlib import Path
20+
from typing import TYPE_CHECKING
21+
22+
from beets import plugins, ui, util
23+
24+
if TYPE_CHECKING:
25+
import optparse
26+
27+
from beets.library import Library
28+
29+
30+
class DetectMissingPlugin(plugins.BeetsPlugin):
31+
def __init__(self) -> None:
32+
super().__init__()
33+
34+
def commands(self) -> list[ui.Subcommand]:
35+
cmd = ui.Subcommand(
36+
"detectmissing", help="Detect missing files, folders, and album art"
37+
)
38+
39+
cmd.parser.add_option(
40+
"--delete",
41+
help="Also delete missing items from the library",
42+
action="store_true",
43+
)
44+
45+
cmd.func = self.detect_missing
46+
47+
return [cmd]
48+
49+
def detect_missing(
50+
self, lib: Library, opts: optparse.Values, _args: list[str]
51+
) -> None:
52+
should_delete: bool = opts.delete or False
53+
54+
for album in lib.albums():
55+
art_filepath = album.art_filepath
56+
if art_filepath is not None and not art_filepath.exists():
57+
print(f"{art_filepath}")
58+
59+
if should_delete:
60+
with lib.transaction():
61+
album.artpath = None
62+
album.store(fields=["artpath"])
63+
64+
for item in lib.items():
65+
if item.path is not None:
66+
path = Path(os.fsdecode(item.path))
67+
if not path.exists():
68+
print(f"{path}")
69+
70+
if should_delete:
71+
util.prune_dirs(
72+
os.path.dirname(item.path), lib.directory
73+
)
74+
with lib.transaction():
75+
item.remove(delete=False, with_album=True)

0 commit comments

Comments
 (0)