22
22
from collections import defaultdict
23
23
from enum import Enum
24
24
from tempfile import mkdtemp
25
- from typing import TYPE_CHECKING , Callable , Iterable , Sequence
25
+ from typing import TYPE_CHECKING , Any , Callable , Iterable , Sequence
26
26
27
27
import mediafile
28
28
32
32
from .state import ImportState
33
33
34
34
if TYPE_CHECKING :
35
+ from beets .autotag .match import Recommendation
36
+
35
37
from .session import ImportSession
36
38
37
39
# Global logger.
@@ -159,6 +161,7 @@ class ImportTask(BaseImportTask):
159
161
cur_album : str | None = None
160
162
cur_artist : str | None = None
161
163
candidates : Sequence [autotag .AlbumMatch | autotag .TrackMatch ] = []
164
+ rec : Recommendation | None = None
162
165
163
166
def __init__ (
164
167
self ,
@@ -167,11 +170,9 @@ def __init__(
167
170
items : Iterable [library .Item ] | None ,
168
171
):
169
172
super ().__init__ (toppath , paths , items )
170
- self .rec = None
171
173
self .should_remove_duplicates = False
172
174
self .should_merge_duplicates = False
173
175
self .is_album = True
174
- self .search_ids = [] # user-supplied candidate IDs.
175
176
176
177
def set_choice (
177
178
self , choice : Action | autotag .AlbumMatch | autotag .TrackMatch
@@ -356,20 +357,17 @@ def handle_created(self, session: ImportSession):
356
357
tasks = [t for inner in tasks for t in inner ]
357
358
return tasks
358
359
359
- def lookup_candidates (self ):
360
- """Retrieve and store candidates for this album. User-specified
361
- candidate IDs are stored in self.search_ids: if present, the
362
- initial lookup is restricted to only those IDs.
360
+ def lookup_candidates (self , search_ids : list [str ]) -> None :
361
+ """Retrieve and store candidates for this album.
362
+
363
+ If User-specified ``search_ids`` list is not empty, the lookup is
364
+ restricted to only those IDs.
363
365
"""
364
- artist , album , prop = autotag . tag_album (
365
- self .items , search_ids = self . search_ids
366
+ self . cur_artist , self . cur_album , ( self . candidates , self . rec ) = (
367
+ autotag . tag_album ( self .items , search_ids = search_ids )
366
368
)
367
- self .cur_artist = artist
368
- self .cur_album = album
369
- self .candidates = prop .candidates
370
- self .rec = prop .recommendation
371
369
372
- def find_duplicates (self , lib : library .Library ):
370
+ def find_duplicates (self , lib : library .Library ) -> list [ library . Album ] :
373
371
"""Return a list of albums from `lib` with the same artist and
374
372
album name as the task.
375
373
"""
@@ -695,12 +693,12 @@ def _emit_imported(self, lib):
695
693
for item in self .imported_items ():
696
694
plugins .send ("item_imported" , lib = lib , item = item )
697
695
698
- def lookup_candidates (self ) :
699
- prop = autotag .tag_item (self . item , search_ids = self . search_ids )
700
- self .candidates = prop . candidates
701
- self . rec = prop . recommendation
696
+ def lookup_candidates (self , search_ids : list [ str ]) -> None :
697
+ self . candidates , self . rec = autotag .tag_item (
698
+ self .item , search_ids = search_ids
699
+ )
702
700
703
- def find_duplicates (self , lib ):
701
+ def find_duplicates (self , lib : library . Library ) -> list [ library . Item ]: # type: ignore[override] # Need splitting Singleton and Album tasks into separate classes
704
702
"""Return a list of items from `lib` that have the same artist
705
703
and title as the task.
706
704
"""
@@ -802,6 +800,11 @@ def _emit_imported(self, lib):
802
800
pass
803
801
804
802
803
+ ArchiveHandler = tuple [
804
+ Callable [[util .StrPath ], bool ], Callable [[util .StrPath ], Any ]
805
+ ]
806
+
807
+
805
808
class ArchiveImportTask (SentinelImportTask ):
806
809
"""An import task that represents the processing of an archive.
807
810
@@ -827,42 +830,41 @@ def is_archive(cls, path):
827
830
if not os .path .isfile (path ):
828
831
return False
829
832
830
- for path_test , _ in cls .handlers () :
833
+ for path_test , _ in cls .handlers :
831
834
if path_test (os .fsdecode (path )):
832
835
return True
833
836
return False
834
837
835
- @classmethod
836
- def handlers (cls ):
838
+ @util . cached_classproperty
839
+ def handlers (cls ) -> list [ ArchiveHandler ] :
837
840
"""Returns a list of archive handlers.
838
841
839
842
Each handler is a `(path_test, ArchiveClass)` tuple. `path_test`
840
843
is a function that returns `True` if the given path can be
841
844
handled by `ArchiveClass`. `ArchiveClass` is a class that
842
845
implements the same interface as `tarfile.TarFile`.
843
846
"""
844
- if not hasattr (cls , "_handlers" ):
845
- cls ._handlers : list [tuple [Callable , ...]] = []
846
- from zipfile import ZipFile , is_zipfile
847
+ _handlers : list [ArchiveHandler ] = []
848
+ from zipfile import ZipFile , is_zipfile
847
849
848
- cls . _handlers .append ((is_zipfile , ZipFile ))
849
- import tarfile
850
+ _handlers .append ((is_zipfile , ZipFile ))
851
+ import tarfile
850
852
851
- cls . _handlers .append ((tarfile .is_tarfile , tarfile .open ))
852
- try :
853
- from rarfile import RarFile , is_rarfile
854
- except ImportError :
855
- pass
856
- else :
857
- cls . _handlers .append ((is_rarfile , RarFile ))
858
- try :
859
- from py7zr import SevenZipFile , is_7zfile
860
- except ImportError :
861
- pass
862
- else :
863
- cls . _handlers .append ((is_7zfile , SevenZipFile ))
853
+ _handlers .append ((tarfile .is_tarfile , tarfile .open ))
854
+ try :
855
+ from rarfile import RarFile , is_rarfile
856
+ except ImportError :
857
+ pass
858
+ else :
859
+ _handlers .append ((is_rarfile , RarFile ))
860
+ try :
861
+ from py7zr import SevenZipFile , is_7zfile
862
+ except ImportError :
863
+ pass
864
+ else :
865
+ _handlers .append ((is_7zfile , SevenZipFile ))
864
866
865
- return cls . _handlers
867
+ return _handlers
866
868
867
869
def cleanup (self , copy = False , delete = False , move = False ):
868
870
"""Removes the temporary directory the archive was extracted to."""
@@ -879,7 +881,7 @@ def extract(self):
879
881
"""
880
882
assert self .toppath is not None , "toppath must be set"
881
883
882
- for path_test , handler_class in self .handlers () :
884
+ for path_test , handler_class in self .handlers :
883
885
if path_test (os .fsdecode (self .toppath )):
884
886
break
885
887
else :
@@ -925,7 +927,7 @@ def __init__(self, toppath: util.PathBytes, session: ImportSession):
925
927
self .imported = 0 # "Real" tasks created.
926
928
self .is_archive = ArchiveImportTask .is_archive (util .syspath (toppath ))
927
929
928
- def tasks (self ):
930
+ def tasks (self ) -> Iterable [ ImportTask ] :
929
931
"""Yield all import tasks for music found in the user-specified
930
932
path `self.toppath`. Any necessary sentinel tasks are also
931
933
produced.
@@ -1114,7 +1116,10 @@ def albums_in_dir(path: util.PathBytes):
1114
1116
a list of Items that is probably an album. Specifically, any folder
1115
1117
containing any media files is an album.
1116
1118
"""
1117
- collapse_pat = collapse_paths = collapse_items = None
1119
+ collapse_paths : list [util .PathBytes ] = []
1120
+ collapse_items : list [util .PathBytes ] = []
1121
+ collapse_pat = None
1122
+
1118
1123
ignore : list [str ] = config ["ignore" ].as_str_seq ()
1119
1124
ignore_hidden : bool = config ["ignore_hidden" ].get (bool )
1120
1125
@@ -1139,7 +1144,7 @@ def albums_in_dir(path: util.PathBytes):
1139
1144
# proceed to process the current one.
1140
1145
if collapse_items :
1141
1146
yield collapse_paths , collapse_items
1142
- collapse_pat = collapse_paths = collapse_items = None
1147
+ collapse_pat , collapse_paths , collapse_items = None , [], []
1143
1148
1144
1149
# Check whether this directory looks like the *first* directory
1145
1150
# in a multi-disc sequence. There are two indicators: the file
0 commit comments