Commit 973df1ca4c51

Vincent Demeester <vincent@sbr.pm>
2025-12-20 22:31:55
refactor(music-playlist-dl): simplify directory structure
- Remove nested library/{artist}/{show} in favor of {show} - Move playlists to sibling directory for shared access - Enable flexible base_dir configuration (mixes vs podcasts) Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 4652017
Changed files (3)
modules
music-playlist-dl
tools
modules/music-playlist-dl/default.nix
@@ -49,7 +49,7 @@ in
     baseDir = mkOption {
       type = types.str;
       default = "/neo/music";
-      description = "Base directory for downloads (library and playlists subdirectories)";
+      description = "Base directory for downloads (shows and playlists subdirectories)";
     };
 
     schedule = mkOption {
@@ -120,10 +120,9 @@ in
           ''
         );
 
-        # Ensure directories exist
+        # Ensure base directory exists (playlists dir created by script)
         ExecStartPre = pkgs.writeShellScript "music-playlist-dl-prepare" ''
-          mkdir -p "${cfg.baseDir}/library"
-          mkdir -p "${cfg.baseDir}/playlist"
+          mkdir -p "${cfg.baseDir}"
         '';
 
         # Resource limits
tools/music-playlist-dl/config.yaml.example
@@ -2,9 +2,11 @@
 # Copy this file to /neo/music/music-playlist-dl.yaml and customize
 
 # Base directory for downloads
-# - Downloads go to: {base_dir}/library/{artist}/{show}/
-# - Playlists go to: {base_dir}/playlist/{artist} - {show}.m3u
-base_dir: /neo/music
+# - Downloads go to: {base_dir}/{show}/
+# - Playlists go to: {base_dir}/../playlists/{artist} - {show}.m3u
+# Example base_dir: /neo/music/mixes or /neo/music/podcasts
+# Playlists will be in: /neo/music/playlists/
+base_dir: /neo/music/mixes
 
 # Beets integration (optional, disabled by default)
 # Enable this to automatically import podcasts to beets music library manager
@@ -86,21 +88,18 @@ yt_dlp_options:
 # After running, your directory structure will look like:
 #
 # /neo/music/
-# ├── library/
-# │   ├── Above & Beyond/
-# │   │   └── Group Therapy/
-# │   │       ├── .downloaded.txt              # Archive file (tracks downloaded IDs)
-# │   │       ├── Group Therapy 657-abc123.m4a
-# │   │       └── Group Therapy 658-def456.m4a
-# │   ├── Armin van Buuren/
-# │   │   └── A State of Trance/
-# │   │       ├── .downloaded.txt
-# │   │       └── ASOT Episode 1255-xyz789.m4a
-# │   └── Tiësto/
-# │       └── CLUBLIFE/
-# │           ├── .downloaded.txt
-# │           └── CLUBLIFE Podcast 908-ghi012.m4a
-# └── playlist/
+# ├── mixes/                     # base_dir (or podcasts/)
+# │   ├── Group Therapy/
+# │   │   ├── .downloaded.txt              # Archive file (tracks downloaded IDs)
+# │   │   ├── Group Therapy 657-abc123.m4a
+# │   │   └── Group Therapy 658-def456.m4a
+# │   ├── A State of Trance/
+# │   │   ├── .downloaded.txt
+# │   │   └── ASOT Episode 1255-xyz789.m4a
+# │   └── CLUBLIFE/
+# │       ├── .downloaded.txt
+# │       └── CLUBLIFE Podcast 908-ghi012.m4a
+# └── playlists/                 # Shared playlists directory
 #     ├── Above & Beyond - Group Therapy.m3u
 #     ├── Armin van Buuren - A State of Trance.m3u
 #     └── Tiësto - CLUBLIFE.m3u
@@ -114,8 +113,8 @@ yt_dlp_options:
 # Playlists are standard M3U format with relative paths:
 #
 # #EXTM3U
-# ../library/Above & Beyond/Group Therapy/Group Therapy 657-abc123.m4a
-# ../library/Above & Beyond/Group Therapy/Group Therapy 658-def456.m4a
+# ../Group Therapy/Group Therapy 657-abc123.m4a
+# ../Group Therapy/Group Therapy 658-def456.m4a
 
 # Podcast Information & Sources
 #
@@ -160,7 +159,7 @@ yt_dlp_options:
 # 2. Run music-playlist-dl --import-existing to import all existing files once
 # 3. Future runs will automatically import new downloads
 #
-# Files stay in library/{artist}/{show}/ - beets imports in-place (doesn't move)
+# Files stay in {base_dir}/{show}/ - beets imports in-place (doesn't move)
 # This preserves yt-dlp's download archive (.downloaded.txt) for deduplication
 #
 # Tag Hierarchy:
tools/music-playlist-dl/music-playlist-dl.py
@@ -144,11 +144,11 @@ def build_yt_dlp_command(
 
 
 def download_mixcloud_show(
-    show: MixcloudShow, library_dir: Path, yt_dlp_options: dict
+    show: MixcloudShow, base_dir: Path, yt_dlp_options: dict
 ) -> bool:
     """Download a Mixcloud show."""
     url = f"https://www.mixcloud.com/{show.handle}/"
-    output_dir = library_dir / show.artist / show.show
+    output_dir = base_dir / show.show
     output_dir.mkdir(parents=True, exist_ok=True)
 
     output_template = str(output_dir / "%(title)s-%(id)s.%(ext)s")
@@ -168,10 +168,10 @@ def download_mixcloud_show(
 
 
 def download_soundcloud_show(
-    show: SoundcloudShow, library_dir: Path, yt_dlp_options: dict
+    show: SoundcloudShow, base_dir: Path, yt_dlp_options: dict
 ) -> bool:
     """Download a SoundCloud show."""
-    output_dir = library_dir / show.artist / show.show
+    output_dir = base_dir / show.show
     output_dir.mkdir(parents=True, exist_ok=True)
 
     output_template = str(output_dir / "%(title)s-%(id)s.%(ext)s")
@@ -191,10 +191,10 @@ def download_soundcloud_show(
 
 
 def generate_playlist(
-    artist: str, show: str, library_dir: Path, playlist_dir: Path
+    artist: str, show: str, base_dir: Path, playlist_dir: Path
 ):
     """Generate M3U playlist for a show."""
-    show_dir = library_dir / artist / show
+    show_dir = base_dir / show
     if not show_dir.exists():
         logging.warning(f"Show directory does not exist: {show_dir}")
         return
@@ -233,7 +233,7 @@ def generate_playlist(
 
 
 def import_to_beets(
-    library_dir: Path,
+    base_dir: Path,
     artist: str,
     show: str,
     show_beets_tags: dict,
@@ -243,7 +243,7 @@ def import_to_beets(
     if not beets_config.enable:
         return True  # Skip if disabled
 
-    show_dir = library_dir / artist / show
+    show_dir = base_dir / show
     if not show_dir.exists():
         logging.warning(f"Show directory does not exist: {show_dir}")
         return False
@@ -323,33 +323,33 @@ def main():
         sys.exit(1)
 
     # Setup directories
-    library_dir = config.base_dir / "library"
-    playlist_dir = config.base_dir / "playlist"
-    library_dir.mkdir(parents=True, exist_ok=True)
+    base_dir = config.base_dir
+    playlist_dir = base_dir.parent / "playlists"
+    base_dir.mkdir(parents=True, exist_ok=True)
     playlist_dir.mkdir(parents=True, exist_ok=True)
 
     logging.info(
-        f"Starting music podcast downloads to: {library_dir}"
+        f"Starting music podcast downloads to: {base_dir}"
     )
     logging.info("=" * 60)
 
     # Download Mixcloud shows
     for show in config.mixcloud_shows:
-        download_mixcloud_show(show, library_dir, config.yt_dlp_options)
+        download_mixcloud_show(show, base_dir, config.yt_dlp_options)
 
     # Download SoundCloud shows
     for show in config.soundcloud_shows:
-        download_soundcloud_show(show, library_dir, config.yt_dlp_options)
+        download_soundcloud_show(show, base_dir, config.yt_dlp_options)
 
     logging.info("=" * 60)
     logging.info("Generating playlists...")
 
     # Generate playlists for all shows
     for show in config.mixcloud_shows:
-        generate_playlist(show.artist, show.show, library_dir, playlist_dir)
+        generate_playlist(show.artist, show.show, base_dir, playlist_dir)
 
     for show in config.soundcloud_shows:
-        generate_playlist(show.artist, show.show, library_dir, playlist_dir)
+        generate_playlist(show.artist, show.show, base_dir, playlist_dir)
 
     # Import to beets if enabled
     if config.beets.enable:
@@ -358,7 +358,7 @@ def main():
             logging.info("Importing all existing files to beets...")
             for show in config.mixcloud_shows:
                 import_to_beets(
-                    library_dir,
+                    base_dir,
                     show.artist,
                     show.show,
                     show.beets_tags,
@@ -366,7 +366,7 @@ def main():
                 )
             for show in config.soundcloud_shows:
                 import_to_beets(
-                    library_dir,
+                    base_dir,
                     show.artist,
                     show.show,
                     show.beets_tags,
@@ -380,7 +380,7 @@ def main():
             logging.info("Importing new downloads to beets...")
             for show in config.mixcloud_shows:
                 import_to_beets(
-                    library_dir,
+                    base_dir,
                     show.artist,
                     show.show,
                     show.beets_tags,
@@ -388,7 +388,7 @@ def main():
                 )
             for show in config.soundcloud_shows:
                 import_to_beets(
-                    library_dir,
+                    base_dir,
                     show.artist,
                     show.show,
                     show.beets_tags,