From 4b4923b8ef72ec0b54a174fe55e17788b5da33ce Mon Sep 17 00:00:00 2001 From: Vincent Roy Chevalier <919691+vincentroyc@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:35:13 -0400 Subject: [PATCH 1/2] Fix yt-dlp command build when ytid contains special characters like a "-" as first character --- Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs b/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs index 831c324..3814952 100644 --- a/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs +++ b/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs @@ -118,7 +118,7 @@ public ActionResult> FinTubeDownload([FromBody] FinTu args += " --prefer-free-format"; else args += " --audio-format mp3"; - args += $" -o \"{targetFilename}.%(ext)s\" {data.ytid}"; + args += $" -o \"{targetFilename}.%(ext)s\" -- {data.ytid}"; } else { @@ -128,7 +128,7 @@ public ActionResult> FinTubeDownload([FromBody] FinTu args = "-f mp4"; if(!string.IsNullOrEmpty(data.videoresolution)) args += $" -S res:{data.videoresolution}"; - args += $" -o \"{targetFilename}-%(title)s.%(ext)s\" {data.ytid}"; + args += $" -o \"{targetFilename}-%(title)s.%(ext)s\" -- {data.ytid}"; } status += $"Exec: {config.exec_YTDL} {args}
"; From 9c5ca9daebf9d405cece5551bf965039c4c8e8c8 Mon Sep 17 00:00:00 2001 From: Vincent Roy Chevalier <919691+vincentroyc@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:16:34 -0400 Subject: [PATCH 2/2] Force vorbis for free format audio and add tagging with vorbiscomment (vorbis-tools package) --- .../Api/FinTubeActivityController.cs | 174 ++++++++++-------- .../Configuration/PluginConfiguration.cs | 6 + .../Configuration/configPage.html | 9 +- 3 files changed, 111 insertions(+), 78 deletions(-) diff --git a/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs b/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs index 3814952..5d672e1 100644 --- a/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs +++ b/Jellyfin.Plugin.FinTube/Api/FinTubeActivityController.cs @@ -71,87 +71,107 @@ public ActionResult> FinTubeDownload([FromBody] FinTu _logger.LogInformation("FinTubeDownload : {ytid} to {targetfoldeer}, prefer free format: {preferfreeformat} audio only: {audioonly}", data.ytid, data.targetfolder, data.preferfreeformat, data.audioonly); Dictionary response = new Dictionary(); - PluginConfiguration? config = Plugin.Instance.Configuration; - String status = ""; + PluginConfiguration? config = Plugin.Instance?.Configuration; - - // check binaries - if(!System.IO.File.Exists(config.exec_YTDL)) - throw new Exception("YT-DL Executable configured incorrectly"); - - bool hasid3v2 = System.IO.File.Exists(config.exec_ID3); - - - // Ensure proper / separator - data.targetfolder = String.Join("/", data.targetfolder.Split("/", StringSplitOptions.RemoveEmptyEntries)); - String targetPath = data.targetlibrary.EndsWith("/") ? data.targetlibrary + data.targetfolder : data.targetlibrary + "/" + data.targetfolder; - // Create Folder if it doesn't exist - if(!System.IO.Directory.CreateDirectory(targetPath).Exists) - throw new Exception("Directory could not be created"); - - - // Check for tags - bool hasTags = 1 < (data.title.Length + data.album.Length + data.artist.Length + data.track.ToString().Length); - - // Save file with ytdlp as mp4 or mp3 depending on audioonly - String targetFilename; - String targetExtension = (data.preferfreeformat ? (data.audioonly ? @".opus" : @".webm") : (data.audioonly ? @".mp3" : @".mp4")); - - if (!String.IsNullOrWhiteSpace(data.targetfilename)) - targetFilename = System.IO.Path.Combine(targetPath, $"{data.targetfilename}"); - else if (data.audioonly && hasTags && data.title.Length > 1) // Use title Tag for filename - targetFilename = System.IO.Path.Combine(targetPath, $"{data.title}"); - else // Use YTID as filename - targetFilename = System.IO.Path.Combine(targetPath, $"{data.ytid}"); - - // Check if filename exists - if(System.IO.File.Exists(targetFilename)) - throw new Exception($"File {targetFilename} already exists"); - - status += $"Filename: {targetFilename}
"; - - String args; - if(data.audioonly) + if (config != null) { - args = "-x"; - if(data.preferfreeformat) - args += " --prefer-free-format"; + String status = ""; + + // check binaries + if(!System.IO.File.Exists(config.exec_YTDL)) + throw new Exception("YT-DL Executable configured incorrectly"); + + bool hasid3v2 = System.IO.File.Exists(config.exec_ID3); + + + // Ensure proper / separator + data.targetfolder = String.Join("/", data.targetfolder.Split("/", StringSplitOptions.RemoveEmptyEntries)); + String targetPath = data.targetlibrary.EndsWith("/") ? data.targetlibrary + data.targetfolder : data.targetlibrary + "/" + data.targetfolder; + // Create Folder if it doesn't exist + if(!System.IO.Directory.CreateDirectory(targetPath).Exists) + throw new Exception("Directory could not be created"); + + + // Check for tags + bool hasTags = 1 < (data.title.Length + data.album.Length + data.artist.Length + data.track.ToString().Length); + + // Save file with ytdlp as mp4 or mp3 depending on audioonly and free format preference + String targetFilename; + String targetExtension = (data.preferfreeformat ? (data.audioonly ? @".ogg" : @".webm") : (data.audioonly ? @".mp3" : @".mp4")); + + if (!String.IsNullOrWhiteSpace(data.targetfilename)) + targetFilename = System.IO.Path.Combine(targetPath, $"{data.targetfilename}"); + else if (data.audioonly && hasTags && data.title.Length > 1) // Use title Tag for filename + targetFilename = System.IO.Path.Combine(targetPath, $"{data.title}"); + else // Use YTID as filename + targetFilename = System.IO.Path.Combine(targetPath, $"{data.ytid}"); + + // Check if filename exists + if(System.IO.File.Exists(targetFilename)) + throw new Exception($"File {targetFilename} already exists"); + + status += $"Filename: {targetFilename}
"; + + String args; + if(data.audioonly) + { + args = "-x"; + if(data.preferfreeformat) + args += " --audio-format vorbis"; + else + args += " --audio-format mp3"; + args += $" -o \"{targetFilename}.%(ext)s\" -- {data.ytid}"; + } else - args += " --audio-format mp3"; - args += $" -o \"{targetFilename}.%(ext)s\" -- {data.ytid}"; + { + if(data.preferfreeformat) + args = "--prefer-free-format"; + else + args = "-f mp4"; + if(!string.IsNullOrEmpty(data.videoresolution)) + args += $" -S res:{data.videoresolution}"; + args += $" -o \"{targetFilename}-%(title)s.%(ext)s\" -- {data.ytid}"; + } + + status += $"Exec: {config.exec_YTDL} {args}
"; + + var procyt = createProcess(config.exec_YTDL, args); + procyt.Start(); + procyt.WaitForExit(); + + // If audioonly and has tags + if (data.audioonly && hasTags) + { + Process? tagsProcess = null; + // Tag the mp3 file if id3v2 + if (!data.preferfreeformat && hasid3v2) + { + args = $"-a \"{data.artist}\" -A \"{data.album}\" -t \"{data.title}\" -T \"{data.track}\" \"{targetFilename}{targetExtension}\""; + + status += $"Exec: {config.exec_ID3} {args}
"; + + tagsProcess = createProcess(config.exec_ID3, args); + } + { + args = $"-w -t \"TITLE={data.title}\" -t \"ALBUM={data.album}\" -t \"TRACKNUMBER={data.track}\""; + foreach (var artist in data.artist.Split(';')) + { + args += $" -t \"ARTIST={artist}\""; + } + args += $" \"{targetFilename}{targetExtension}\""; + + status += $"Exec: {config.exec_vorbiscomment} {args}
"; + + tagsProcess = createProcess(config.exec_vorbiscomment, args); + } + tagsProcess.Start(); + tagsProcess.WaitForExit(); + } + + status += "File Saved!"; + + response.Add("message", status); } - else - { - if(data.preferfreeformat) - args = "--prefer-free-format"; - else - args = "-f mp4"; - if(!string.IsNullOrEmpty(data.videoresolution)) - args += $" -S res:{data.videoresolution}"; - args += $" -o \"{targetFilename}-%(title)s.%(ext)s\" -- {data.ytid}"; - } - - status += $"Exec: {config.exec_YTDL} {args}
"; - - var procyt = createProcess(config.exec_YTDL, args); - procyt.Start(); - procyt.WaitForExit(); - - // If audioonly AND id3v2 AND tags are set - Tag the mp3 file - if (data.audioonly && hasid3v2 && hasTags) - { - args = $"-a \"{data.artist}\" -A \"{data.album}\" -t \"{data.title}\" -T \"{data.track}\" \"{targetFilename}{targetExtension}\""; - - status += $"Exec: {config.exec_ID3} {args}
"; - - var procid3 = createProcess(config.exec_ID3, args); - procid3.Start(); - procid3.WaitForExit(); - } - - status += "File Saved!"; - - response.Add("message", status); return Ok(response); } catch(Exception e) diff --git a/Jellyfin.Plugin.FinTube/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.FinTube/Configuration/PluginConfiguration.cs index cf010e4..d9ee045 100644 --- a/Jellyfin.Plugin.FinTube/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.FinTube/Configuration/PluginConfiguration.cs @@ -14,6 +14,7 @@ public PluginConfiguration() { exec_YTDL = "/usr/local/bin/yt-dlp"; exec_ID3 = "/usr/bin/id3v2"; + exec_vorbiscomment = "/usr/bin/vorbiscomment"; } /// @@ -25,4 +26,9 @@ public PluginConfiguration() /// Executable for ID3v2 /// public string exec_ID3 { get; set; } + + /// + /// Executable for vorbiscomment + /// + public string exec_vorbiscomment { get; set; } } diff --git a/Jellyfin.Plugin.FinTube/Configuration/configPage.html b/Jellyfin.Plugin.FinTube/Configuration/configPage.html index 1fb1c54..d494a6b 100644 --- a/Jellyfin.Plugin.FinTube/Configuration/configPage.html +++ b/Jellyfin.Plugin.FinTube/Configuration/configPage.html @@ -10,7 +10,7 @@
- +
The executable filepath to youtube-dl/yt-dlp
@@ -19,6 +19,11 @@
The executable filepath to id3v2
+
+ + +
The executable filepath to vorbiscomment
+