Commit 09fbaeae8ba0

Vincent Demeester <vincent@sbr.pm>
2026-04-20 16:02:11
feat(beets): generate podcast playlists statically, prefix mixes
Removed podcast playlist entries from beets smartplaylists since podcasts aren't in the beets database. Added podcastDir option to beets-auto-import module to generate static M3U playlists from podcast folder contents. Prefixed music-playlist-dl playlists with 'Mix -' for consistent naming in Navidrome.
1 parent ee71222
Changed files (4)
home
common
services
modules
beets-auto-import
systems
tools
music-playlist-dl
home/common/services/beets.nix
@@ -443,143 +443,9 @@ in
             query = "albumtype:single artist+ year+ album+ disc+ track+";
           }
           # ===========================================
-          # Podcasts (Podcast - Name)
-          # Sorted by title (episode name/date)
+          # Podcasts: generated as static M3U directly on aion
+          # (not managed by beets - podcasts aren't in beets DB)
           # ===========================================
-          # Music / DJ shows
-          {
-            name = "Podcast - Bon Entendeur.m3u";
-            query = "path::podcasts/Bon Entendeur title+";
-          }
-          {
-            name = "Podcast - Deep Search.m3u";
-            query = "path::podcasts/\\[DEEP\\]Search title+";
-          }
-          {
-            name = "Podcast - Fip Tape.m3u";
-            query = "path::podcasts/Fip Tape title+";
-          }
-          {
-            name = "Podcast - Florileges.m3u";
-            query = "path::podcasts/Florileges title+";
-          }
-          {
-            name = "Podcast - French Touch.m3u";
-            query = "path::podcasts/French Touch title+";
-          }
-          {
-            name = "Podcast - Global DJ Broadcast.m3u";
-            query = "path::podcasts/Markus Schulz title+";
-          }
-          {
-            name = "Podcast - La Ballade.m3u";
-            query = "path::podcasts/La ballade title+";
-          }
-          {
-            name = "Podcast - La Ballade Souchon Voulzy.m3u";
-            query = "path::podcasts/La ballade de Souchon title+";
-          }
-          {
-            name = "Podcast - La Radio De.m3u";
-            query = "path::podcasts/La radio de title+";
-          }
-          {
-            name = "Podcast - Les Feuilletons Musicaux.m3u";
-            query = "path::podcasts/Les Feuilletons Musicaux title+";
-          }
-          {
-            name = "Podcast - Les Sagas Musicales.m3u";
-            query = "path::podcasts/Les Sagas musicales title+";
-          }
-          {
-            name = "Podcast - Perfecto.m3u";
-            query = "path::podcasts/Perfecto title+";
-          }
-          {
-            name = "Podcast - Pogo Chill.m3u";
-            query = "path::podcasts/P\\(\\)G\\(\\) title+";
-          }
-          {
-            name = "Podcast - Resonation Radio.m3u";
-            query = "path::podcasts/Resonation title+";
-          }
-          {
-            name = "Podcast - Vonyc Sessions.m3u";
-            query = "path::podcasts/Paul van Dyk title+";
-          }
-          {
-            name = "Podcast - WYM Radio.m3u";
-            query = "path::podcasts/Cosmic Gate title+";
-          }
-          # Talk / Culture / Tech
-          {
-            name = "Podcast - Chaleur Humaine.m3u";
-            query = "path::podcasts/Chaleur Humaine title+";
-          }
-          {
-            name = "Podcast - Command Line Heroes.m3u";
-            query = "path::podcasts/Command Line Heroes title+";
-          }
-          {
-            name = "Podcast - Continue Tu M'interesses.m3u";
-            query = "path::podcasts/Continue title+";
-          }
-          {
-            name = "Podcast - Darknet Diaries.m3u";
-            query = "path::podcasts/Darknet Diaries title+";
-          }
-          {
-            name = "Podcast - Developer Voices.m3u";
-            query = "path::podcasts/Developer Voices title+";
-          }
-          {
-            name = "Podcast - Entendez-Vous L'Eco.m3u";
-            query = "path::podcasts/Entendez-vous title+";
-          }
-          {
-            name = "Podcast - Entre La Chaise Et Le Clavier.m3u";
-            query = "path::podcasts/Entre la chaise title+";
-          }
-          {
-            name = "Podcast - Fifty States.m3u";
-            query = "path::podcasts/Fifty States title+";
-          }
-          {
-            name = "Podcast - Grandes Traversees.m3u";
-            query = "path::podcasts/GRANDES TRAVERSEES title+";
-          }
-          {
-            name = "Podcast - IRL.m3u";
-            query = "path::podcasts/IRL title+";
-          }
-          {
-            name = "Podcast - La Terre Au Carre.m3u";
-            query = "path::podcasts/La Terre au title+";
-          }
-          {
-            name = "Podcast - Le Fil Mental.m3u";
-            query = "path::podcasts/Le Fil Mental title+";
-          }
-          {
-            name = "Podcast - Les Grands Remplacants.m3u";
-            query = "path::podcasts/Les grands title+";
-          }
-          {
-            name = "Podcast - Nota Bene.m3u";
-            query = "path::podcasts/Nota Bene title+";
-          }
-          {
-            name = "Podcast - Personnage Principal.m3u";
-            query = "path::podcasts/Personnage principal title+";
-          }
-          {
-            name = "Podcast - Sismique.m3u";
-            query = "path::podcasts/Sismique title+";
-          }
-          {
-            name = "Podcast - Strangers On A Bench.m3u";
-            query = "path::podcasts/Strangers on a Bench title+";
-          }
         ];
       };
 
modules/beets-auto-import/default.nix
@@ -69,6 +69,19 @@ in
       description = "Run beet splupdate after import to regenerate smart playlists";
     };
 
+    podcastDir = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Path to podcast directory. If set, generates static M3U playlists for each podcast subfolder.";
+      example = "/neo/music/podcasts";
+    };
+
+    playlistDir = mkOption {
+      type = types.str;
+      default = "/neo/music/playlists";
+      description = "Directory where playlists are written";
+    };
+
     schedule = mkOption {
       type = types.str;
       default = "daily";
@@ -134,6 +147,23 @@ in
           ${cfg.package}/bin/beet splupdate
         ''}
 
+        ${optionalString (cfg.podcastDir != null) ''
+          echo "Generating podcast playlists..."
+          podcast_dir="${cfg.podcastDir}"
+          playlist_dir="${cfg.playlistDir}"
+          for dir in "$podcast_dir"/*/; do
+            [ -d "$dir" ] || continue
+            name=$(${pkgs.coreutils}/bin/basename "$dir")
+            playlist="$playlist_dir/Podcast - $name.m3u"
+            echo "#EXTM3U" > "$playlist"
+            ${pkgs.findutils}/bin/find "$dir" -type f \( -name "*.mp3" -o -name "*.opus" -o -name "*.m4a" -o -name "*.ogg" -o -name "*.flac" \) | ${pkgs.coreutils}/bin/sort | while read -r f; do
+              echo "../podcasts/$name/$(${pkgs.coreutils}/bin/basename "$f")" >> "$playlist"
+            done
+            count=$(${pkgs.gnugrep}/bin/grep -c "^\.\." "$playlist" 2>/dev/null || echo 0)
+            echo "  Podcast - $name.m3u ($count tracks)"
+          done
+        ''}
+
         echo "Beets auto-import complete!"
       '';
     in
systems/aion/extra.nix
@@ -176,6 +176,8 @@ in
         "compilation"
       ];
       updatePlaylists = true;
+      podcastDir = "/neo/music/podcasts";
+      playlistDir = "/neo/music/playlists";
       schedule = "daily"; # Run daily
       notification = {
         enable = true;
tools/music-playlist-dl/music-playlist-dl.py
@@ -216,7 +216,7 @@ def generate_playlist(
         return
 
     # Generate playlist filename
-    playlist_name = f"{artist} - {show}.m3u"
+    playlist_name = f"Mix - {artist} - {show}.m3u"
     playlist_path = playlist_dir / playlist_name
 
     logging.info(