2929from beets .plugins import send as send_event
3030from beets .util import (
3131 bytestring_path ,
32- displayable_path ,
3332 mkdirall ,
3433 normpath ,
3534 path_as_posix ,
@@ -64,7 +63,7 @@ def __init__(self) -> None:
6463 "forward_slash" : False ,
6564 "prefix" : "" ,
6665 "urlencode" : False ,
67- "pretend_paths " : False ,
66+ "format " : "$artist - $title" ,
6867 "output" : "m3u" ,
6968 }
7069 )
@@ -89,23 +88,26 @@ def commands(self) -> list[ui.Subcommand]:
8988 help = "display query results but don't write playlist files." ,
9089 )
9190 spl_update .parser .add_option (
92- "--pretend-paths" ,
93- action = "store_true" ,
94- dest = "pretend_paths" ,
95- help = "in pretend mode, log the playlist item URIs/paths." ,
91+ "-f" ,
92+ "--format" ,
93+ type = "string" ,
94+ default = self .config ["format" ].get (),
95+ help = "print per-track log lines with custom format" ,
9696 )
9797 spl_update .parser .add_option (
9898 "-d" ,
9999 "--playlist-dir" ,
100100 dest = "playlist_dir" ,
101101 metavar = "PATH" ,
102102 type = "string" ,
103+ default = self .config ["playlist_dir" ].get (),
103104 help = "directory to write the generated playlist files to." ,
104105 )
105106 spl_update .parser .add_option (
106107 "--dest-regen" ,
107108 action = "store_true" ,
108109 dest = "dest_regen" ,
110+ default = self .config ["dest_regen" ].get (bool ),
109111 help = "regenerate the destination path as 'move' or 'convert' "
110112 "commands would do." ,
111113 )
@@ -114,33 +116,39 @@ def commands(self) -> list[ui.Subcommand]:
114116 dest = "relative_to" ,
115117 metavar = "PATH" ,
116118 type = "string" ,
119+ default = self .config ["relative_to" ].get (),
117120 help = "generate playlist item paths relative to this path." ,
118121 )
119122 spl_update .parser .add_option (
120123 "--prefix" ,
121124 type = "string" ,
125+ default = self .config ["prefix" ].get (),
122126 help = "prepend string to every path in the playlist file." ,
123127 )
124128 spl_update .parser .add_option (
125129 "--forward-slash" ,
126130 action = "store_true" ,
127131 dest = "forward_slash" ,
132+ default = self .config ["forward_slash" ].get (bool ),
128133 help = "force forward slash in paths within playlists." ,
129134 )
130135 spl_update .parser .add_option (
131136 "--urlencode" ,
132137 action = "store_true" ,
138+ default = self .config ["urlencode" ].get (bool ),
133139 help = "URL-encode all paths." ,
134140 )
135141 spl_update .parser .add_option (
136142 "--uri-format" ,
137143 dest = "uri_format" ,
138144 type = "string" ,
145+ default = self .config ["uri_format" ].get (),
139146 help = "playlist item URI template, e.g. http://beets:8337/item/$id/file." ,
140147 )
141148 spl_update .parser .add_option (
142149 "--output" ,
143150 type = "string" ,
151+ default = self .config ["output" ].get (),
144152 help = "specify the playlist format: m3u|extm3u." ,
145153 )
146154 spl_update .func = self .update_cmd
@@ -172,14 +180,9 @@ def update_cmd(self, lib: Library, opts: Any, args: list[str]) -> None:
172180 else :
173181 self ._matched_playlists = self ._unmatched_playlists
174182
175- self .__apply_opts_to_config ( opts )
183+ self .config . set ( vars ( opts ) )
176184 self .update_playlists (lib , opts .pretend )
177185
178- def __apply_opts_to_config (self , opts : Any ) -> None :
179- for k , v in opts .__dict__ .items ():
180- if v is not None and k in self .config :
181- self .config [k ] = v
182-
183186 def _parse_one_query (
184187 self , playlist : dict [str , Any ], key : str , model_cls : type
185188 ) -> tuple [PlaylistQuery , Sort | None ]:
@@ -263,35 +266,29 @@ def db_change(self, lib: Library, model: Item | Album) -> None:
263266 self ._unmatched_playlists -= self ._matched_playlists
264267
265268 def update_playlists (self , lib : Library , pretend : bool = False ) -> None :
266- if pretend :
267- self ._log .info (
268- "Showing query results for {} smart playlists..." ,
269- len (self ._matched_playlists ),
270- )
271- else :
272- self ._log .info (
273- "Updating {} smart playlists..." , len (self ._matched_playlists )
274- )
269+ self ._log .info (
270+ "Updating {} smart playlists..." ,
271+ len (self ._matched_playlists ),
272+ )
275273
276274 playlist_dir = bytestring_path (
277275 self .config ["playlist_dir" ].as_filename ()
278276 )
279277 tpl = self .config ["uri_format" ].get ()
280278 prefix = bytestring_path (self .config ["prefix" ].as_str ())
281- dest_regen = self .config ["dest_regen" ].get ()
279+ dest_regen = self .config ["dest_regen" ].get (bool )
282280 relative_to = self .config ["relative_to" ].get ()
283281 if relative_to :
284282 relative_to = normpath (relative_to )
285283
286- # Maps playlist filenames to lists of track filenames.
284+ # Maps playlist filenames to lists of track entries and URI sets used
285+ # to deduplicate output lines.
287286 m3us : dict [str , list [PlaylistItem ]] = {}
287+ m3u_uris_by_name : dict [str , set [Any ]] = {}
288288
289289 for playlist in self ._matched_playlists :
290+ matched_count = 0
290291 name , (query , q_sort ), (album_query , a_q_sort ) = playlist
291- if pretend :
292- self ._log .info ("Results for playlist {}:" , name )
293- else :
294- self ._log .info ("Creating playlist {}" , name )
295292 items = []
296293
297294 # Handle tuple/list of queries (preserves order)
@@ -321,11 +318,13 @@ def update_playlists(self, lib: Library, pretend: bool = False) -> None:
321318
322319 # As we allow tags in the m3u names, we'll need to iterate through
323320 # the items and generate the correct m3u file names.
321+ matched_items : list [tuple [Any , Any ]] = []
324322 for item in items :
325323 m3u_name = item .evaluate_template (name , True )
326324 m3u_name = sanitize_path (m3u_name , lib .replacements )
327325 if m3u_name not in m3us :
328326 m3us [m3u_name ] = []
327+ m3u_uris_by_name [m3u_name ] = set ()
329328 item_uri = item .path
330329 if tpl :
331330 item_uri = tpl .replace ("$id" , str (item .id )).encode ("utf-8" )
@@ -334,20 +333,27 @@ def update_playlists(self, lib: Library, pretend: bool = False) -> None:
334333 item_uri = item .destination ()
335334 if relative_to :
336335 item_uri = os .path .relpath (item_uri , relative_to )
337- if self .config ["forward_slash" ].get ():
336+ if self .config ["forward_slash" ].get (bool ):
338337 item_uri = path_as_posix (item_uri )
339- if self .config ["urlencode" ]:
338+ if self .config ["urlencode" ]. get ( bool ) :
340339 item_uri = bytestring_path (
341340 pathname2url (os .fsdecode (item_uri ))
342341 )
343342 item_uri = prefix + item_uri
344343
345- if item_uri not in m3us [m3u_name ]:
344+ if item_uri not in m3u_uris_by_name [m3u_name ]:
345+ m3u_uris_by_name [m3u_name ].add (item_uri )
346346 m3us [m3u_name ].append (PlaylistItem (item , item_uri ))
347- if pretend and self .config ["pretend_paths" ]:
348- print (displayable_path (item_uri ))
349- elif pretend :
350- print (item )
347+ matched_items .append ((item , item_uri ))
348+ matched_count += 1
349+
350+ self ._log .info (
351+ "Creating playlist {}: {} tracks." , name , matched_count
352+ )
353+ for item , item_uri in matched_items :
354+ self ._log .debug (
355+ item .evaluate_template (self .config ["format" ].as_str ())
356+ )
351357
352358 if not pretend :
353359 # Write all of the accumulated track lists to files.
@@ -387,7 +393,7 @@ def update_playlists(self, lib: Library, pretend: bool = False) -> None:
387393
388394 if pretend :
389395 self ._log .info (
390- "Displayed results for {} playlists" ,
396+ "{} playlists would be updated " ,
391397 len (self ._matched_playlists ),
392398 )
393399 else :
0 commit comments