@@ -401,6 +401,51 @@ def _parse_nodes(parent_elem: ET.Element, path_prefix: str) -> List[Dict[str, An
401401 return (tree_roots , playlists_by_path )
402402
403403
404+ def resolve_playlist_key (
405+ playlist_name : str , playlists_by_path : Dict [str , Playlist ]
406+ ) -> Optional [str ]:
407+ """Resolve a user-provided playlist name to the path key used in playlists_by_path.
408+
409+ Accepts full path (e.g. ROOT/My Playlist), short name (My Playlist), or
410+ path without ROOT prefix. Returns the key to use for lookup, or None if not found.
411+
412+ Args:
413+ playlist_name: Name or path provided by user/CLI.
414+ playlists_by_path: Dict from parse_playlist_tree (path -> Playlist).
415+
416+ Returns:
417+ The key to use in playlists_by_path, or None.
418+ """
419+ if not playlist_name or not playlists_by_path :
420+ return None
421+ if playlist_name in playlists_by_path :
422+ return playlist_name
423+ path_without_root = playlist_name .strip ()
424+ if path_without_root .upper ().startswith ("ROOT/" ):
425+ path_without_root = path_without_root [5 :].lstrip ()
426+ elif path_without_root .upper ().startswith ("ROOT" ):
427+ path_without_root = path_without_root [4 :].lstrip ()
428+ if path_without_root and path_without_root in playlists_by_path :
429+ return path_without_root
430+ if path_without_root :
431+ canonical = f"ROOT/{ path_without_root } "
432+ if canonical in playlists_by_path :
433+ return canonical
434+ name_only = (
435+ path_without_root
436+ if path_without_root
437+ else (
438+ playlist_name .split ("/" )[- 1 ].strip ()
439+ if "/" in playlist_name
440+ else playlist_name
441+ )
442+ )
443+ for path , pl in playlists_by_path .items ():
444+ if pl .name == playlist_name or pl .name == name_only :
445+ return path
446+ return None
447+
448+
404449def extract_artists_from_title (title : str ) -> Optional [Tuple [str , str ]]:
405450 """Extract artist names from title if title follows "Artists - Title" format.
406451
0 commit comments