diff --git a/.gitignore b/.gitignore index 9d1adc2..a5b8aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ bin/ obj/ releases/ -wiki/ + whisper.cpp/ BenchmarkDotNet.Artifacts/ diff --git a/Directory.Build.props b/Directory.Build.props index 471a913..a55be05 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - 3.0.0.0 - 3.0.0.0 - 3.0.0.0 + 3.0.0.1 + 3.0.0.1 + 3.0.0.1 Jellyfin.Plugin.WhisperSubtitles Whisper Plugin Contributors Automatic subtitle generation for Jellyfin using OpenAI's Whisper diff --git a/Jellyfin.Plugin.Template.sln b/Jellyfin.Plugin.Template.sln deleted file mode 100644 index 7c9b9ee..0000000 --- a/Jellyfin.Plugin.Template.sln +++ /dev/null @@ -1,28 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Plugin.Template", "Jellyfin.Plugin.Template\Jellyfin.Plugin.Template.csproj", "{D921B930-CF91-406F-ACBC-08914DCD0D34}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|x64.ActiveCfg = Debug|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|x64.Build.0 = Debug|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|x86.ActiveCfg = Debug|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|x86.Build.0 = Debug|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.Build.0 = Release|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|x64.ActiveCfg = Release|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|x64.Build.0 = Release|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|x86.ActiveCfg = Release|Any CPU - {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/Jellyfin.Plugin.Template/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.Template/Configuration/PluginConfiguration.cs deleted file mode 100644 index 564e6bf..0000000 --- a/Jellyfin.Plugin.Template/Configuration/PluginConfiguration.cs +++ /dev/null @@ -1,57 +0,0 @@ -using MediaBrowser.Model.Plugins; - -namespace Jellyfin.Plugin.Template.Configuration; - -/// -/// The configuration options. -/// -public enum SomeOptions -{ - /// - /// Option one. - /// - OneOption, - - /// - /// Second option. - /// - AnotherOption -} - -/// -/// Plugin configuration. -/// -public class PluginConfiguration : BasePluginConfiguration -{ - /// - /// Initializes a new instance of the class. - /// - public PluginConfiguration() - { - // set default options here - Options = SomeOptions.AnotherOption; - TrueFalseSetting = true; - AnInteger = 2; - AString = "string"; - } - - /// - /// Gets or sets a value indicating whether some true or false setting is enabled.. - /// - public bool TrueFalseSetting { get; set; } - - /// - /// Gets or sets an integer setting. - /// - public int AnInteger { get; set; } - - /// - /// Gets or sets a string setting. - /// - public string AString { get; set; } - - /// - /// Gets or sets an enum option. - /// - public SomeOptions Options { get; set; } -} diff --git a/Jellyfin.Plugin.Template/Configuration/configPage.html b/Jellyfin.Plugin.Template/Configuration/configPage.html deleted file mode 100644 index 23f024e..0000000 --- a/Jellyfin.Plugin.Template/Configuration/configPage.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - Template - - -
-
-
-
-
- - -
-
- - -
A Description
-
-
- -
-
- - -
Another Description
-
-
- -
-
-
-
- -
- - diff --git a/Jellyfin.Plugin.Template/Jellyfin.Plugin.Template.csproj b/Jellyfin.Plugin.Template/Jellyfin.Plugin.Template.csproj deleted file mode 100644 index f275ec1..0000000 --- a/Jellyfin.Plugin.Template/Jellyfin.Plugin.Template.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - Jellyfin.Plugin.Template - true - true - enable - AllEnabledByDefault - ../jellyfin.ruleset - - - - - - - - - - - - - - - - - - - diff --git a/Jellyfin.Plugin.Template/Plugin.cs b/Jellyfin.Plugin.Template/Plugin.cs deleted file mode 100644 index 445c2bc..0000000 --- a/Jellyfin.Plugin.Template/Plugin.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Jellyfin.Plugin.Template.Configuration; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Serialization; - -namespace Jellyfin.Plugin.Template; - -/// -/// The main plugin. -/// -public class Plugin : BasePlugin, IHasWebPages -{ - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) - : base(applicationPaths, xmlSerializer) - { - Instance = this; - } - - /// - public override string Name => "Template"; - - /// - public override Guid Id => Guid.Parse("eb5d7894-8eef-4b36-aa6f-5d124e828ce1"); - - /// - /// Gets the current plugin instance. - /// - public static Plugin? Instance { get; private set; } - - /// - public IEnumerable GetPages() - { - return - [ - new PluginPageInfo - { - Name = Name, - EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", GetType().Namespace) - } - ]; - } -} diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Services/WhisperBinaryManager.cs b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Services/WhisperBinaryManager.cs index 9108a86..20ec22a 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Services/WhisperBinaryManager.cs +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Services/WhisperBinaryManager.cs @@ -162,30 +162,43 @@ public async Task DownloadBinaryAsync(CancellationToken cancellationToken { try { - // Skip deployment if any binary already exists - if (IsBinaryAvailable()) + var cpuExists = File.Exists(_binaryPath); + var cudaExists = File.Exists(_cudaBinaryPath); + + // If CPU binary exists and CUDA is either present or not bundled, skip + if (cpuExists && cudaExists) { - _logger.LogInformation("Binary already available in cache, skipping deployment"); + _logger.LogInformation("Both CPU and CUDA binaries already in cache, skipping deployment"); return true; } - _logger.LogInformation("Deploying bundled whisper binary from plugin directory..."); - - var source = FindBundledBinary(); - if (source is null) + if (cpuExists && FindBundledCudaBinary() is null) { - _logger.LogError( - "Bundled binary not found inside plugin folder. " + - "Expected '{Name}' inside whisper/{Platform}/ sub-directory.", - BundledBinaryName, GetPlatformString()); - return false; + _logger.LogInformation("CPU binary cached, no CUDA binary bundled — skipping deployment"); + return true; } - _logger.LogInformation("Copying {Source} → {Dest}", source, _binaryPath); - File.Copy(source, _binaryPath, overwrite: true); - EnsureExecutable(_binaryPath); + if (!cpuExists) + { + _logger.LogInformation("Deploying bundled whisper binary from plugin directory..."); + + var source = FindBundledBinary(); + if (source is null) + { + _logger.LogError( + "Bundled binary not found inside plugin folder. " + + "Expected '{Name}' inside whisper/{Platform}/ sub-directory.", + BundledBinaryName, GetPlatformString()); + return false; + } + + _logger.LogInformation("Copying {Source} → {Dest}", source, _binaryPath); + File.Copy(source, _binaryPath, overwrite: true); + EnsureExecutable(_binaryPath); + } // Deploy CUDA binary and .so files if present in the bundle + // (runs even when CPU is cached, so upgrades from CPU-only deploy CUDA) var cudaSource = FindBundledCudaBinary(); if (cudaSource is not null) { @@ -204,12 +217,12 @@ public async Task DownloadBinaryAsync(CancellationToken cancellationToken } } } - else - { - _logger.LogInformation("No CUDA binary bundled — GPU acceleration will not be available"); - } - return await TestBinaryAsync(cancellationToken); + // Test only if we freshly deployed the CPU binary + if (!cpuExists) + return await TestBinaryAsync(cancellationToken); + + return true; } catch (Exception ex) { diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json index 6fbf2b6..2b8e7be 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json @@ -6,7 +6,7 @@ "compilationOptions": {}, "targets": { ".NETCoreApp,Version=v9.0": { - "Jellyfin.Plugin.WhisperSubtitles/3.0.0.0": { + "Jellyfin.Plugin.WhisperSubtitles/3.0.0.1": { "dependencies": { "Whisper.net": "1.8.1" }, @@ -25,7 +25,7 @@ } }, "libraries": { - "Jellyfin.Plugin.WhisperSubtitles/3.0.0.0": { + "Jellyfin.Plugin.WhisperSubtitles/3.0.0.1": { "type": "project", "serviceable": false, "sha512": "" diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll index 6dcad0d..0f4576f 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb index f9c50e2..711a66b 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.deps.json b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.deps.json index 6fbf2b6..2b8e7be 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.deps.json +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.deps.json @@ -6,7 +6,7 @@ "compilationOptions": {}, "targets": { ".NETCoreApp,Version=v9.0": { - "Jellyfin.Plugin.WhisperSubtitles/3.0.0.0": { + "Jellyfin.Plugin.WhisperSubtitles/3.0.0.1": { "dependencies": { "Whisper.net": "1.8.1" }, @@ -25,7 +25,7 @@ } }, "libraries": { - "Jellyfin.Plugin.WhisperSubtitles/3.0.0.0": { + "Jellyfin.Plugin.WhisperSubtitles/3.0.0.1": { "type": "project", "serviceable": false, "sha512": "" diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.dll b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.dll index 3d3f4ed..0f4576f 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.dll and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.dll differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.pdb b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.pdb index cc9f8c5..711a66b 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.pdb and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/Release/net9.0/publish/Jellyfin.Plugin.WhisperSubtitles.pdb differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs deleted file mode 100644 index feda5e9..0000000 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs deleted file mode 100644 index 500e684..0000000 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("Whisper Plugin Contributors")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyDescriptionAttribute("Automatic subtitle generation for Jellyfin using OpenAI\'s Whisper")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("3.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("3.0.0.0+c54e473f85a4476802aa855e84608f5c370f7694")] -[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Plugin.WhisperSubtitles")] -[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Plugin.WhisperSubtitles")] -[assembly: System.Reflection.AssemblyVersionAttribute("3.0.0.0")] -[assembly: System.Reflection.AssemblyMetadataAttribute("RepositoryUrl", "https://github.com/zakattack02/Whisper-Script")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache deleted file mode 100644 index 18c7052..0000000 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -cacf97a64e17f54edbbde3099797ce626aeb3f61e7870671d29f544b801e5a38 diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.GeneratedMSBuildEditorConfig.editorconfig b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 396f6a7..0000000 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -is_global = true -build_property.TargetFramework = net9.0 -build_property.TargetFrameworkIdentifier = .NETCoreApp -build_property.TargetFrameworkVersion = v9.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = Jellyfin.Plugin.WhisperSubtitles -build_property.ProjectDir = /home/kanucks/Documents/Whisper/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.EffectiveAnalysisLevelStyle = 9.0 -build_property.EnableCodeStyleSeverity = diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.GlobalUsings.g.cs b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.GlobalUsings.g.cs deleted file mode 100644 index d12bcbc..0000000 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.GlobalUsings.g.cs +++ /dev/null @@ -1,8 +0,0 @@ -// -global using System; -global using System.Collections.Generic; -global using System.IO; -global using System.Linq; -global using System.Net.Http; -global using System.Threading; -global using System.Threading.Tasks; diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.assets.cache b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.assets.cache deleted file mode 100644 index 441c83f..0000000 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.assets.cache and /dev/null differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.csproj.AssemblyReference.cache b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.csproj.AssemblyReference.cache deleted file mode 100644 index c11a9f1..0000000 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Debug/net9.0/Jellyfin.Plugin.WhisperSubtitles.csproj.AssemblyReference.cache and /dev/null differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Jellyfin.Plugin.WhisperSubtitles.csproj.nuget.dgspec.json b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Jellyfin.Plugin.WhisperSubtitles.csproj.nuget.dgspec.json index ba4c885..161c67c 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Jellyfin.Plugin.WhisperSubtitles.csproj.nuget.dgspec.json +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Jellyfin.Plugin.WhisperSubtitles.csproj.nuget.dgspec.json @@ -5,7 +5,7 @@ }, "projects": { "/home/kanucks/Documents/Whisper/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles.csproj": { - "version": "3.0.0", + "version": "3.0.0.1", "restore": { "projectUniqueName": "/home/kanucks/Documents/Whisper/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles.csproj", "projectName": "Jellyfin.Plugin.WhisperSubtitles", diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs index b225b41..28289c9 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfo.cs @@ -13,11 +13,11 @@ [assembly: System.Reflection.AssemblyCompanyAttribute("Whisper Plugin Contributors")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyDescriptionAttribute("Automatic subtitle generation for Jellyfin using OpenAI\'s Whisper")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("3.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("3.0.0.0+aa6017ffa3b3b604a86a191dd05a9b42c7c498a2")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("3.0.0.1")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("3.0.0.1+cac4abd9ae8f85bb1c48353d80913740206a7677")] [assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Plugin.WhisperSubtitles")] [assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Plugin.WhisperSubtitles")] -[assembly: System.Reflection.AssemblyVersionAttribute("3.0.0.0")] +[assembly: System.Reflection.AssemblyVersionAttribute("3.0.0.1")] [assembly: System.Reflection.AssemblyMetadataAttribute("RepositoryUrl", "https://github.com/zakattack02/Whisper-Script")] // Generated by the MSBuild WriteCodeFragment class. diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache index 406c89a..d1fc1a6 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.AssemblyInfoInputs.cache @@ -1 +1 @@ -d7e9ef14f1e8beb7f919d35317860b87578e8dad27dd489eb18150c24ae79f8f +ca2b038b35e5b7d7d55d357a182aa5c2e5440e23cd7b7c480efe953b435026d6 diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json index 6fbf2b6..2b8e7be 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.deps.json @@ -6,7 +6,7 @@ "compilationOptions": {}, "targets": { ".NETCoreApp,Version=v9.0": { - "Jellyfin.Plugin.WhisperSubtitles/3.0.0.0": { + "Jellyfin.Plugin.WhisperSubtitles/3.0.0.1": { "dependencies": { "Whisper.net": "1.8.1" }, @@ -25,7 +25,7 @@ } }, "libraries": { - "Jellyfin.Plugin.WhisperSubtitles/3.0.0.0": { + "Jellyfin.Plugin.WhisperSubtitles/3.0.0.1": { "type": "project", "serviceable": false, "sha512": "" diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll index 6dcad0d..0f4576f 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.dll differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.genpublishdeps.cache b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.genpublishdeps.cache index 8ab2da8..5e7572d 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.genpublishdeps.cache +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.genpublishdeps.cache @@ -1 +1 @@ -c9ea070e3f7a1e3c71ceed533153278ae5b549104b6bcbeeb70b10b63c45abfe +8069054065e4b9b0055bd8fb929c1d98a2b8be14eb66ed83a26db90e847a76d2 diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb index f9c50e2..711a66b 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.pdb differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.sourcelink.json b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.sourcelink.json index 04ca493..ff2ac75 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.sourcelink.json +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/Jellyfin.Plugin.WhisperSubtitles.sourcelink.json @@ -1 +1 @@ -{"documents":{"/home/kanucks/Documents/Whisper/*":"https://raw.githubusercontent.com/zakattack02/Whisper-Script/aa6017ffa3b3b604a86a191dd05a9b42c7c498a2/*"}} \ No newline at end of file +{"documents":{"/home/kanucks/Documents/Whisper/*":"https://raw.githubusercontent.com/zakattack02/Whisper-Script/cac4abd9ae8f85bb1c48353d80913740206a7677/*"}} \ No newline at end of file diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/ref/Jellyfin.Plugin.WhisperSubtitles.dll b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/ref/Jellyfin.Plugin.WhisperSubtitles.dll index d02d1f5..cb4f866 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/ref/Jellyfin.Plugin.WhisperSubtitles.dll and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/ref/Jellyfin.Plugin.WhisperSubtitles.dll differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/refint/Jellyfin.Plugin.WhisperSubtitles.dll b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/refint/Jellyfin.Plugin.WhisperSubtitles.dll index d02d1f5..cb4f866 100644 Binary files a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/refint/Jellyfin.Plugin.WhisperSubtitles.dll and b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/Release/net9.0/refint/Jellyfin.Plugin.WhisperSubtitles.dll differ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.assets.json b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.assets.json index 6e12927..bd5357c 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.assets.json +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.assets.json @@ -1737,7 +1737,7 @@ "/home/kanucks/.nuget/packages/": {} }, "project": { - "version": "3.0.0", + "version": "3.0.0.1", "restore": { "projectUniqueName": "/home/kanucks/Documents/Whisper/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles.csproj", "projectName": "Jellyfin.Plugin.WhisperSubtitles", diff --git a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.nuget.cache b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.nuget.cache index 4de41fc..aed4d3b 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.nuget.cache +++ b/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/obj/project.nuget.cache @@ -1,6 +1,6 @@ { "version": 2, - "dgSpecHash": "ue1nUufKDPc=", + "dgSpecHash": "+EjcA62nMIw=", "success": true, "projectFilePath": "/home/kanucks/Documents/Whisper/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles.csproj", "expectedPackageFiles": [ diff --git a/Jellyfin.Plugin.WhisperSubtitles/Scripts/Build-whisper.sh b/Jellyfin.Plugin.WhisperSubtitles/Scripts/Build-whisper.sh index aa7dc29..1a65a99 100755 --- a/Jellyfin.Plugin.WhisperSubtitles/Scripts/Build-whisper.sh +++ b/Jellyfin.Plugin.WhisperSubtitles/Scripts/Build-whisper.sh @@ -100,7 +100,7 @@ build_in_docker() { echo "✓ CUDA binary: ${cuda_dest} ($(du -h "${cuda_dest}" | cut -f1))" # Verify CUDA .so files - for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12; do + for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12 libnccl.so.2; do if [ -f "${OUTPUT_DIR}/${lib}" ]; then chmod +x "${OUTPUT_DIR}/${lib}" 2>/dev/null || true echo " ✓ ${lib} ($(du -h "${OUTPUT_DIR}/${lib}" | cut -f1))" diff --git a/Jellyfin.Plugin.WhisperSubtitles/Scripts/Dockerfile.whisper b/Jellyfin.Plugin.WhisperSubtitles/Scripts/Dockerfile.whisper index 8670136..4608eb2 100644 --- a/Jellyfin.Plugin.WhisperSubtitles/Scripts/Dockerfile.whisper +++ b/Jellyfin.Plugin.WhisperSubtitles/Scripts/Dockerfile.whisper @@ -59,7 +59,8 @@ RUN cmake --build whisper.cpp/build --config Release -j $(nproc) --target whispe RUN mkdir -p /cuda-libs && \ cp -v /usr/local/cuda/lib64/libcudart.so.12 /cuda-libs/ && \ cp -v /usr/local/cuda/lib64/libcublas.so.12 /cuda-libs/ && \ - cp -v /usr/local/cuda/lib64/libcublasLt.so.12 /cuda-libs/ + cp -v /usr/local/cuda/lib64/libcublasLt.so.12 /cuda-libs/ && \ + find /usr -name 'libnccl.so.2' -exec cp -v {} /cuda-libs/ \; 2>/dev/null || true # ── Output stage ────────────────────────────────────────────────────── FROM ubuntu:22.04 AS output @@ -69,10 +70,12 @@ COPY --from=cuda-builder /build/whisper.cpp/build/bin/whisper-cli /build/whisper COPY --from=cuda-builder /cuda-libs/libcudart.so.12 /build/ COPY --from=cuda-builder /cuda-libs/libcublas.so.12 /build/ COPY --from=cuda-builder /cuda-libs/libcublasLt.so.12 /build/ +COPY --from=cuda-builder /cuda-libs/libnccl.so.2 /build/ CMD cp /build/whisper-whisper-cli /output/whisper-whisper-cli && \ cp /build/whisper-whisper-cli-cuda /output/whisper-whisper-cli-cuda && \ cp /build/libcudart.so.12 /output/ && \ cp /build/libcublas.so.12 /output/ && \ cp /build/libcublasLt.so.12 /output/ && \ + cp /build/libnccl.so.2 /output/ && \ chmod 755 /output/whisper* /output/lib*.so.* diff --git a/make-release.sh b/make-release.sh index 30d1d5b..78caab3 100755 --- a/make-release.sh +++ b/make-release.sh @@ -36,12 +36,14 @@ for arg in "$@"; do [ "$arg" = "--dry-run" ] && DRY_RUN=true done -# ── Preflight: dirty-tree check ─────────────────────────────────────────────── +# ── Preflight: dirty-tree check (warning only) ───────────────────────────────── if ! git diff-index --quiet HEAD; then - error_exit "Working tree has uncommitted changes. Commit or stash first." + echo -e "${YELLOW}⚠ Working tree has uncommitted changes. They will be included in the release commit.${NC}" + DIRTY_TREE=true fi if ! git diff-index --cached --quiet HEAD; then - error_exit "Staged but uncommitted changes. Commit first." + echo -e "${YELLOW}⚠ Staged but uncommitted changes. They will be included in the release commit.${NC}" + DIRTY_TREE=true fi # ── Preflight: dependencies ─────────────────────────────────────────────────── @@ -67,18 +69,13 @@ echo "" CURRENT_VERSION=$(grep -oP '\K[^<]+' "$BUILD_PROPS_FILE") [ -z "$CURRENT_VERSION" ] && error_exit "Could not read version from $BUILD_PROPS_FILE" -# Check if tag already exists -if git tag -l "v${CURRENT_VERSION}" | grep -q .; then - error_exit "Tag v${CURRENT_VERSION} already exists. Increment version first." -fi - echo -e "${CYAN}Current version: ${GREEN}$CURRENT_VERSION${NC}" echo "" echo -e "${YELLOW}How would you like to increment the version?${NC}" -echo " 1) Revision (3.0.0.0 \u2192 3.0.0.1 — hotfix/bug-fix)" -echo " 2) Patch (3.0.0.0 \u2192 3.0.1.0 — minor fix)" -echo " 3) Minor (3.0.0.0 \u2192 3.1.0.0 — new feature)" -echo " 4) Major (3.0.0.0 \u2192 4.0.0.0 — breaking change)" +echo -e " 1) Revision (3.0.0.0 \u2192 3.0.0.1 — hotfix/bug-fix)" +echo -e " 2) Patch (3.0.0.0 \u2192 3.0.1.0 — minor fix)" +echo -e " 3) Minor (3.0.0.0 \u2192 3.1.0.0 — new feature)" +echo -e " 4) Major (3.0.0.0 \u2192 4.0.0.0 — breaking change)" echo " 5) Manual entry" echo " 6) Use current version as-is" echo "" @@ -186,7 +183,7 @@ if $HAS_CUDA; then chmod +x "$WHISPER_OUTPUT/whisper-whisper-cli-cuda" 2>/dev/null || true echo -e "${GREEN}\u2713 CUDA binary verified: $WHISPER_OUTPUT/whisper-whisper-cli-cuda ($(du -h "$WHISPER_OUTPUT/whisper-whisper-cli-cuda" | cut -f1))${NC}" - for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12; do + for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12 libnccl.so.2; do if [ -f "$WHISPER_OUTPUT/$lib" ]; then chmod +x "$WHISPER_OUTPUT/$lib" 2>/dev/null || true echo " \u2713 $lib ($(du -h "$WHISPER_OUTPUT/$lib" | cut -f1))" @@ -222,7 +219,7 @@ if [ -f "$WHISPER_OUTPUT/whisper-whisper-cli-cuda" ]; then cp "$WHISPER_OUTPUT/whisper-whisper-cli-cuda" "$BUILD_OUTPUT/whisper/linux-x64/whisper-whisper-cli-cuda" chmod +x "$BUILD_OUTPUT/whisper/linux-x64/whisper-whisper-cli-cuda" 2>/dev/null || true - for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12; do + for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12 libnccl.so.2; do if [ -f "$WHISPER_OUTPUT/$lib" ]; then cp "$WHISPER_OUTPUT/$lib" "$BUILD_OUTPUT/whisper/linux-x64/$lib" fi @@ -260,7 +257,7 @@ if $HAS_CUDA; then mkdir -p "$CPU_OUTPUT/whisper/linux-x64" # Copy everything except CUDA binaries - rsync -a --exclude='whisper/linux-x64/whisper-whisper-cli-cuda' --exclude='whisper/linux-x64/libcudart.so.12' --exclude='whisper/linux-x64/libcublas.so.12' --exclude='whisper/linux-x64/libcublasLt.so.12' "$BUILD_OUTPUT/" "$CPU_OUTPUT/" + rsync -a --exclude='whisper/linux-x64/whisper-whisper-cli-cuda' --exclude='whisper/linux-x64/libcudart.so.12' --exclude='whisper/linux-x64/libcublas.so.12' --exclude='whisper/linux-x64/libcublasLt.so.12' --exclude='whisper/linux-x64/libnccl.so.2' "$BUILD_OUTPUT/" "$CPU_OUTPUT/" [ -f "$CPU_PACKAGE_PATH" ] && rm -f "$CPU_PACKAGE_PATH" (cd "$CPU_OUTPUT" && zip -q -r "$CPU_PACKAGE_PATH" .) @@ -311,7 +308,13 @@ echo "" # ── Step 8: Local commit + tag ──────────────────────────────────────────────── echo -e "${YELLOW}[8/9] Committing and tagging...${NC}" -git add "$MANIFEST_FILE" "$BUILD_PROPS_FILE" +# If tag already exists, delete and recreate (overwrite mode) +if git tag -l "v${NEW_VERSION}" | grep -q .; then + echo -e "${YELLOW} \u26a0 Tag v${NEW_VERSION} exists locally — deleting and recreating${NC}" + git tag -d "v${NEW_VERSION}" +fi + +git add -A git commit -m "Release v${NEW_VERSION}: ${CHANGELOG_ONELINE}" git tag "v${NEW_VERSION}" @@ -325,24 +328,32 @@ RELEASE_NOTES="$CHANGELOG_ONELINE" RELEASE_CREATED=false if command -v gh &>/dev/null && command -v git &>/dev/null; then + # Delete existing remote tag and release if present (overwrite mode) + if git ls-remote --tags origin "refs/tags/v${NEW_VERSION}" | grep -q .; then + echo -e "${YELLOW} \u26a0 Remote tag v${NEW_VERSION} exists — deleting${NC}" + git push --delete origin "v${NEW_VERSION}" + fi + if gh release view "v${NEW_VERSION}" &>/dev/null; then + echo -e "${YELLOW} \u26a0 Release v${NEW_VERSION} exists — deleting${NC}" + gh release delete "v${NEW_VERSION}" --yes + fi + + # Push branch and tag first so gh release create can find the tag + git push origin HEAD + git push origin "v${NEW_VERSION}" + if $HAS_CUDA && [ -f "$CPU_PACKAGE_PATH" ]; then - if gh release create "v${NEW_VERSION}" "$PACKAGE_PATH" "$CPU_PACKAGE_PATH" \ + gh release create "v${NEW_VERSION}" "$PACKAGE_PATH" "$CPU_PACKAGE_PATH" \ --title "v${NEW_VERSION}" \ - --notes "$RELEASE_NOTES"; then - RELEASE_CREATED=true + --notes "$RELEASE_NOTES" && RELEASE_CREATED=true && \ echo -e "${GREEN}\u2713 Uploaded combined + CPU-only zips${NC}" - fi else - if gh release create "v${NEW_VERSION}" "$PACKAGE_PATH" \ + gh release create "v${NEW_VERSION}" "$PACKAGE_PATH" \ --title "v${NEW_VERSION}" \ - --notes "$RELEASE_NOTES"; then - RELEASE_CREATED=true - fi + --notes "$RELEASE_NOTES" && RELEASE_CREATED=true fi if $RELEASE_CREATED; then - git push origin HEAD - git push origin "v${NEW_VERSION}" RELEASE_URL=$(gh release view "v${NEW_VERSION}" --json url --jq .url 2>/dev/null) echo -e "${GREEN}\u2713 GitHub release created: ${CYAN}${RELEASE_URL}${NC}" else @@ -373,6 +384,6 @@ if [ -n "$CPU_PACKAGE_PATH" ]; then fi echo "" echo -e "${CYAN}Install in Jellyfin:${NC}" -echo " Dashboard \u2192 Plugins \u2192 Repositories \u2192 Add" +echo -e " Dashboard \u2192 Plugins \u2192 Repositories \u2192 Add" echo " Or manually upload the zip" echo "" diff --git a/manifest.json b/manifest.json index e1ee7aa..d8fd7d5 100644 --- a/manifest.json +++ b/manifest.json @@ -8,6 +8,46 @@ "category": "Metadata", "imageUrl": "https://raw.githubusercontent.com/zakattack02/Whisper-Script/feature/jellyfin-plugin/logo.png", "versions": [ + { + "version": "3.0.0.1", + "changelog": "libnccl.so.2 added", + "targetAbi": "10.11.2.0", + "sourceUrl": "https://github.com/zakattack02/Whisper-Script/releases/download/v3.0.0.1/jellyfin-plugin-whispersubtitles_3.0.0.1.zip", + "checksum": "1438fb89f90c9fb8f18d7fe6b319de06", + "timestamp": "2026-05-28T04:51:24Z" + }, + { + "version": "3.0.0.1", + "changelog": "Bug fixes and improvements", + "targetAbi": "10.11.2.0", + "sourceUrl": "https://github.com/zakattack02/Whisper-Script/releases/download/v3.0.0.1/jellyfin-plugin-whispersubtitles_3.0.0.1.zip", + "checksum": "39f5c8252911b4c97eb50d528c8b62f1", + "timestamp": "2026-05-28T04:44:45Z" + }, + { + "version": "3.0.0.1", + "changelog": "Bug fixes and improvements", + "targetAbi": "10.11.2.0", + "sourceUrl": "https://github.com/zakattack02/Whisper-Script/releases/download/v3.0.0.1/jellyfin-plugin-whispersubtitles_3.0.0.1.zip", + "checksum": "695336d457fb478038585d9f3ced893f", + "timestamp": "2026-05-28T04:38:18Z" + }, + { + "version": "3.0.0.1", + "changelog": "Bug fixes and improvements", + "targetAbi": "10.11.2.0", + "sourceUrl": "https://github.com/zakattack02/Whisper-Script/releases/download/v3.0.0.1/jellyfin-plugin-whispersubtitles_3.0.0.1.zip", + "checksum": "5d8fd777650f280e8e4c0e8a50a24709", + "timestamp": "2026-05-28T04:36:09Z" + }, + { + "version": "3.0.0.1", + "changelog": "Bug fixes and improvements", + "targetAbi": "10.11.2.0", + "sourceUrl": "https://github.com/zakattack02/Whisper-Script/releases/download/v3.0.0.1/jellyfin-plugin-whispersubtitles_3.0.0.1.zip", + "checksum": "9bb77c28dc06a3ca8e05deb1319c1dca", + "timestamp": "2026-05-28T04:33:17Z" + }, { "version": "3.0.0.0", "changelog": "Semantic versioning restructure. Dual CPU+GPU binaries with CUDA support. Dockerfile now builds both whisper-whisper-cli (CPU) and whisper-whisper-cli-cuda (CUDA) + bundles libcudart/libcublas. Plugin auto-selects CUDA binary when GPU is enabled and cuda binary is deployed.", @@ -74,4 +114,4 @@ } ] } -] +] \ No newline at end of file diff --git a/wiki/Architecture.md b/wiki/Architecture.md new file mode 100644 index 0000000..209fd4c --- /dev/null +++ b/wiki/Architecture.md @@ -0,0 +1,140 @@ +# Architecture + +## Overview + +The plugin follows a straightforward pipeline: + +``` +Video File + │ + ▼ +Audio Extraction (FFmpeg → 16kHz mono WAV) + │ + ▼ +Duration Check + ├── ≤ 30 min → Single chunk + └── > 30 min → Split into 30-min chunks via FFmpeg segment muxer + │ + ▼ + whisper.cpp (CPU or CUDA binary) + │ + ▼ + SRT Files (one per chunk) + │ + ▼ + Merge SRTs (renumber segments) + │ + ▼ + Final SRT → saved next to video +``` + +## Key Components + +### WhisperService + +`Services/WhisperService.cs` — The core service that orchestrates subtitle generation. + +**Key methods:** +- `GenerateSubtitleAsync()` — Main entry point: ensures binary + model are ready, extracts audio, chooses chunked or direct processing +- `RunWhisperCli()` — Launches the whisper.cpp process, captures output, handles errors +- `BuildArguments()` — Constructs command-line arguments for whisper-cli +- `GetWavDurationMsAsync()` — Uses ffprobe to measure audio duration for chunking decisions +- `SplitWavAsync()` — Uses FFmpeg segment muxer to split WAV into 30-min chunks +- `MergeSrtInto()` — Merges individual chunk SRTs into one continuous SRT + +### WhisperBinaryManager + +`Services/WhisperBinaryManager.cs` — Manages binary deployment and system discovery. + +**Responsibilities:** +- Detecting the Jellyfin FFmpeg path +- Detecting ffprobe path +- Detecting GPU type (nvidia-smi, vulkaninfo) +- Deploying the bundled binary from plugin directory to cache +- Testing the binary with `--help` +- Finding both CPU and CUDA bundled binaries + +### WhisperSubtitleTask + +`Tasks/WhisperSubtitleTask.cs` — The scheduled task that iterates videos and calls WhisperService. + +**Progress reporting:** Reports per-video progress with sub-progress for chunked videos using a `Progress` wrapper. + +### WhisperSubtitlesController + +`Controllers/WhisperSubtitlesController.cs` — API endpoints used by the config page. + +**Endpoints:** +- `GET /api/WhisperSubtitles/BinaryStatus` — Checks if binary is deployed +- `POST /api/WhisperSubtitles/InstallBinary` — Deploys binary from bundle to cache +- `POST /api/WhisperSubtitles/DownloadModel` — Downloads a model file +- `GET /api/WhisperSubtitles/stats` — Returns diagnostics info + +## Binary Selection Logic + +From `WhisperService.cs`: + +```csharp +if (gpuType == "cuda" && _binaryManager.IsCudaBinaryAvailable) +{ + binaryPath = _binaryManager.CudaBinaryPath; + // Sets LD_LIBRARY_PATH to bundled .so directory +} +else +{ + binaryPath = _binaryManager.BinaryPath; // CPU binary +} +``` + +## Build Arguments + +The `BuildArguments` method constructs whisper-cli flags: + +| Flag | Purpose | Used when | +|---|---|---| +| `-m` | Model file path | Always | +| `-f` | Input audio file | Always | +| `-l` | Language code | Always | +| `-osrt` | Output SRT format | Always | +| `-of` | Output filename stem | Always | +| `-tr` | Translate to English | When enabled | +| `-ml 1` | Max line length (word timestamps) | When enabled | +| `-t N` | Thread count | Always (min of CPU count, 16) | +| `-dev 0` | Use GPU device 0 | GPU mode | +| `-ng` | No GPU | CPU mode | + +> Note: whisper-cli (the `examples/cli` target) does NOT support `--output-dir`, `-vv`, or `-ngl` flags that the `main` example supports. + +## Chunking Details + +- **Chunk duration:** 30 minutes (1800 seconds) — constant `ChunkDurationMs` +- **Chunking threshold:** Audio > 30 min gets chunked +- **Method:** `ffmpeg -f segment -segment_time 1800 -c:a pcm_s16le -ar 16000 -ac 1` +- **SRT merging:** Each chunk's segment numbers are offset by the previous chunk's max segment number +- **Temp file cleanup:** Chunk WAV files are deleted in a `finally` block + +## GPU Detection + +```csharp +private string? DetectGPU() +{ + if (CheckNvidiaGPU()) return "cuda"; + if (CheckVulkanGPU()) return "vulkan"; + if (IsOSX) return "metal"; + return null; // CPU-only +} +``` + +NVIDIA detection runs `nvidia-smi --query-gpu=name --format=csv,noheader`. Vulkan detection runs `vulkaninfo --summary`. + +## Cache Directory + +The plugin cache follows this priority: +1. `JELLYFIN_CACHE_DIR` environment variable +2. `$HOME/.cache/` or `Path.GetTempPath()` + +Structure: +``` +{base}/whisper/ — Model files (ggml-*.bin) +{base}/whisper-cpp/ — Binaries (whisper-whisper-cli, whisper-whisper-cli-cuda, .so files) +``` diff --git a/wiki/Build-from-Source.md b/wiki/Build-from-Source.md new file mode 100644 index 0000000..8d8de48 --- /dev/null +++ b/wiki/Build-from-Source.md @@ -0,0 +1,119 @@ +# Build from Source + +## Prerequisites + +- **Docker** (recommended) — for GLIBC-compatible builds +- **.NET SDK 9.0** — for building the C# plugin +- **git** — for version control +- **cmake, build-essential** — for native whisper.cpp builds (fallback) +- **`gh` CLI** (optional) — for publishing releases to GitHub + +## Quick Build + +The easiest way to build everything is using `make-release.sh`: + +```bash +# From the repo root +bash make-release.sh +``` + +This will: +1. Build whisper.cpp in Docker (CPU + CUDA binaries) +2. Build the C# plugin +3. Package the zip +4. Optionally publish to GitHub + +## Building whisper.cpp Only + +### Docker Build (Recommended) + +```bash +bash Scripts/Build-whisper.sh \ + Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/whisper/linux-x64/ +``` + +This produces: +- `whisper-whisper-cli` — CPU binary +- `whisper-whisper-cli-cuda` — CUDA binary (requires nvidia/cuda Docker image) +- `libcudart.so.12` — bundled CUDA runtime +- `libcublas.so.12` — bundled CUDA BLAS +- `libcublasLt.so.12` — bundled CUDA BLAS Light + +### Native Build (Fallback) + +```bash +bash Scripts/Build-whisper.sh --no-docker \ + Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/bin/whisper/linux-x64/ +``` + +> **Warning:** Native builds may require GLIBC 2.43+ (depends on host system). Use Docker for maximum compatibility. + +## Building the C# Plugin Only + +```bash +dotnet build \ + Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles.csproj +``` + +Or for a publish-ready build: + +```bash +dotnet publish \ + --configuration Release \ + Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles/Jellyfin.Plugin.WhisperSubtitles.csproj +``` + +## Full Release Process + +1. Ensure you're on the `feature/jellyfin-plugin` branch +2. Run `bash make-release.sh` +3. Select version increment (or keep current version) +4. Enter changelog +5. Confirm — the script commits, tags, pushes, and creates a GitHub release + +## Docker Build Internals + +The multi-stage Dockerfile (`Scripts/Dockerfile.whisper`): + +``` +Stage 1: cpu-builder + Base: ubuntu:22.04 + Build: cmake -DGGML_CUDA=OFF ... + Output: whisper-cli → whisper-whisper-cli + +Stage 2: cuda-builder + Base: nvidia/cuda:12.4.1-devel-ubuntu22.04 + Build: cmake -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES="50;60;70;75;80;86;89" ... + Output: whisper-cli → whisper-whisper-cli-cuda + Extract: libcudart.so.12, libcublas.so.12, libcublasLt.so.12 + +Stage 3: output + Base: ubuntu:22.04 (scratch equivalent) + Copy: all artifacts from stages 1 & 2 + CMD: copy to /output/ +``` + +## Important CMake Flags + +| Flag | Purpose | +|---|---| +| `-DGGML_NATIVE=OFF` | Prevents `-march=native` to avoid AVX-512 in the binary | +| `-DGGML_OPENMP=OFF` | Disables OpenMP (not needed, uses own threading) | +| `-DGGML_CUDA=ON` | Enables CUDA GPU support (cuda-builder stage only) | +| `-DWHISPER_BUILD_TESTS=OFF` | Faster build, no tests | +| `-DBUILD_SHARED_LIBS=OFF` | Static linking for whisper library | +| `-DCMAKE_CUDA_ARCHITECTURES` | Target GPU architectures (CUDA build only) | +| `-DCMAKE_C_FLAGS=-march=x86-64 -mtune=generic` | Maximum CPU compatibility | +| `-DCMAKE_EXE_LINKER_FLAGS=-static-libgcc -static-libstdc++` | Static GCC/GLIBCXX linkage | + +## Reducing CUDA Binary Size + +The CUDA binary is ~1.1 GB because GPU kernels are compiled for 7 architectures. To reduce size: + +1. Edit `Dockerfile.whisper` and change `CMAKE_CUDA_ARCHITECTURES` to target only your GPU +2. Common targets: + - `"75"` — Turing (RTX 2060, 2080, T4) + - `"86"` — Ampere (RTX 3060, 3080, A10) + - `"89"` — Ada (RTX 4060, 4090) + - `"50"` — Maxwell (very old GPUs, rarely needed) +3. Each architecture adds ~150 MB to the binary diff --git a/wiki/Configuration.md b/wiki/Configuration.md new file mode 100644 index 0000000..b284ab6 --- /dev/null +++ b/wiki/Configuration.md @@ -0,0 +1,117 @@ +# Configuration + +Access the config page at Dashboard → Plugins → Whisper Subtitles → Settings. + +## Model & Engine + +### Whisper Model + +The Whisper model determines accuracy vs. speed tradeoff: + +| Model | Size | Speed (vs Large) | VRAM | Quality | +|---|---|---|---|---| +| Tiny | 75 MB | ~10x | ~1 GB | Lowest | +| Tiny.en | 75 MB | ~10x | ~1 GB | English-only Tiny | +| Base | 140 MB | ~7x | ~1 GB | Low | +| Base.en | 140 MB | ~7x | ~1 GB | English-only Base | +| **Small** | **460 MB** | **~4x** | **~2 GB** | **Recommended** | +| Small.en | 460 MB | ~4x | ~2 GB | English-only Small | +| Medium | 1.5 GB | ~2x | ~5 GB | High | +| Medium.en | 1.5 GB | ~2x | ~5 GB | English-only Medium | +| Turbo | 1.6 GB | ~8x | ~6 GB | High (fast) | +| Large (v3) | 3 GB | 1x | ~10 GB | Best | + +> **Small** is the default and recommended starting point. Turbo is nearly as accurate as Large but 8x faster. + +### Download Model + +Click **Download Model** to pre-download the selected model. This happens automatically when the task runs, but pre-downloading lets you verify the download succeeded. + +### Target Language + +Language code for subtitles. Common values: + +- `en` — English +- `es` — Spanish +- `fr` — French +- `de` — German +- `ja` — Japanese +- `zh` — Chinese +- `auto` — auto-detect (slower) + +### AI Identifier + +A tag appended to subtitle filenames so they can be identified as AI-generated: + +``` +Movie Name (2024).en.whisper.srt + ↑ identifier +``` + +Default: `whisper`. Set to empty to disable tagging. + +## Acceleration + +### Enable CUDA (NVIDIA GPU) + +When checked, the plugin uses the CUDA GPU binary instead of the CPU binary. + +**Prerequisites:** +1. Jellyfin container must have `--gpus all` or nvidia-container-toolkit configured +2. The CUDA binary (`whisper-whisper-cli-cuda`) must be deployed in the cache +3. NVIDIA drivers must be installed on the host + +The config page shows: +- **Runtime Hardware Status** — detected GPU type (cuda, vulkan, metal, or none) +- **CUDA Binary** — whether the CUDA binary is deployed and ready + +See [GPU Acceleration](GPU-Acceleration) for full setup details. + +### FFprobe Path + +Custom path to the `ffprobe` binary. Used to measure audio duration for chunked processing. + +Leave empty for auto-detection. The plugin searches: +1. Next to the found `ffmpeg` binary +2. Known container paths (`/usr/lib/jellyfin-ffmpeg/ffprobe`, etc.) +3. `which ffprobe` via PATH +4. Config override (this field) + +## Library Automation + +### Process on Library Scan + +When enabled, the plugin hooks into Jellyfin's library scan and generates subtitles for new media as it's discovered. + +### Skip Existing Subtitles + +Skip videos that already have any subtitle track. Does NOT skip videos with only AI-generated subtitles (see Regenerate). + +### Regenerate AI Subtitles + +Force-regenerate subtitles even if an AI-tagged subtitle already exists. Useful when upgrading models or changing settings. + +### Translate to English + +Translate non-English audio to English subtitles. When unchecked, subtitles match the spoken language. + +### Enable Word-Level Timestamps + +Produces more precise subtitle timing with word-level alignment. **Significantly increases processing time** (roughly 2-3x slower). + +### Show in Main Menu + +Toggle the plugin entry in Jellyfin's main navigation sidebar. + +### Libraries to Process + +Select which media libraries to scan. Leave empty to process all libraries. + +### Folders to Exclude + +List absolute paths to exclude from processing (one per line). + +``` +/Media/Home Videos/Kids Stuff +/Media/Movies/Sample +``` diff --git a/wiki/Development.md b/wiki/Development.md new file mode 100644 index 0000000..a846680 --- /dev/null +++ b/wiki/Development.md @@ -0,0 +1,123 @@ +# Development + +## Repository Structure + +``` +├── Directory.Build.props — Version number, assembly metadata +├── make-release.sh — Full release orchestrator +├── manifest.json — Plugin repository manifest +├── releases/ — Built zips +├── wiki/ — Wiki documentation +│ +├── Jellyfin.Plugin.WhisperSubtitles/ +│ ├── Jellyfin.Plugin.WhisperSubtitles/ +│ │ ├── Plugin.cs — Plugin entry point (Registrator) +│ │ ├── Jellyfin.Plugin.WhisperSubtitles.csproj +│ │ ├── Configuration/ +│ │ │ ├── PluginConfiguration.cs +│ │ │ └── configPage.html +│ │ ├── Controllers/ +│ │ │ └── WhisperSubtitlesController.cs +│ │ ├── Services/ +│ │ │ ├── IWhisperService.cs +│ │ │ ├── WhisperService.cs +│ │ │ ├── WhisperBinaryManager.cs +│ │ │ ├── ISubtitleDetectionService.cs +│ │ │ └── SubtitleDetectionService.cs +│ │ └── Tasks/ +│ │ ├── WhisperSubtitleTask.cs +│ │ └── WhisperPostScanTask.cs +│ │ +│ └── Scripts/ +│ ├── Dockerfile.whisper — Multi-stage CPU+CUDA build +│ └── Build-whisper.sh — Build orchestrator script +``` + +## Key Classes + +### Plugin.cs +Entry point. Registers services with the Jellyfin DI container. Exposes `Plugin.Instance` singleton. + +### WhisperBinaryManager.cs +- Binary discovery and deployment +- GPU detection (nvidia-smi, vulkaninfo) +- FFmpeg/ffprobe path discovery +- CUDA binary and .so file deployment + +### WhisperService.cs +- Orchestrates the full subtitle generation pipeline +- Audio extraction via FFmpeg +- Chunking decision (duration-based) +- whisper-cli invocation and result handling +- SRT chunk merging + +### WhisperSubtitleTask.cs +- Scheduled task that processes videos in configured libraries +- Progress reporting wrapper for per-video sub-progress +- Library filtering using collection folder IDs +- Skip logic (existing subtitles, AI regeneration) + +### WhisperSubtitlesController.cs +- REST API endpoints used by the config page +- BinaryStatus, InstallBinary, DownloadModel, stats + +## Build Scripts + +### make-release.sh + +The main release script: + +``` +Step 1: Update version in Directory.Build.props +Step 2: Clean previous builds +Step 3: Build whisper.cpp (Docker → both CPU + CUDA binaries) +Step 4: Verify binaries +Step 5: Build C# plugin (dotnet publish) +Step 6: Package zip +Step 7: Update manifest.json +Step 8: GitHub release (commit, tag, push, gh release create) +``` + +### Build-whisper.sh + +Builds whisper.cpp. Supports two modes: +- **Docker** (default) — uses multi-stage Dockerfile for GLIBC compatibility +- **Native** (`--no-docker`) — builds on host (warning: GLIBC version depends on host) + +### Dockerfile.whisper + +Multi-stage build producing both binaries. Key details: +- CPU stage: `ubuntu:22.04`, `-DGGML_CUDA=OFF` +- CUDA stage: `nvidia/cuda:12.4.1-devel-ubuntu22.04`, `-DGGML_CUDA=ON`, 7 GPU architectures +- Output stage: copies all artifacts + CUDA .so files + +## Code Conventions + +- **No XML docs on implementation** — only on interfaces and public API surfaces +- **ILogger pattern** — all services take `ILogger` via constructor injection +- **Async all the way** — `Task` return types, `CancellationToken` parameters +- **Process management** — `ProcessStartInfo` for FFmpeg and whisper-cli, async stdout/stderr reading +- **Configuration** — `PluginConfiguration` extends `BasePluginConfiguration`, loaded via Jellyfin API +- **Versioning** — 4-part version in `Directory.Build.props`, updated by make-release.sh + +## Adding a New Feature + +1. Add config properties to `PluginConfiguration.cs` +2. Add UI fields to `configPage.html` (load/save in JS) +3. Implement logic in the appropriate service +4. Wire up in the task or controller +5. Update `make-release.sh` if new build artifacts are needed +6. Update `Directory.Build.props` version +7. Run `make-release.sh` to build and release + +## Release Checklist + +- [ ] All changes committed and tested +- [ ] `dotnet build` succeeds with no warnings +- [ ] whisper.cpp builds in Docker (CPU + CUDA) +- [ ] Binary GLIBC compatibility checked (should be ≤ 2.34) +- [ ] CUDA binary + .so files present in output +- [ ] `make-release.sh` completes successfully +- [ ] GitHub release created with correct zip and tag +- [ ] manifest.json updated with new version entry +- [ ] Test on remote server (deploy + run task) diff --git a/wiki/GPU-Acceleration.md b/wiki/GPU-Acceleration.md new file mode 100644 index 0000000..142593b --- /dev/null +++ b/wiki/GPU-Acceleration.md @@ -0,0 +1,143 @@ +# GPU Acceleration (CUDA) + +## Overview + +Starting with v0.0.0.98, the plugin ships **two separate binaries**: + +| Binary | Purpose | Size | +|---|---|---| +| `whisper-whisper-cli` | CPU-only (AVX2) | 4.2 MB | +| `whisper-whisper-cli-cuda` | CUDA GPU | ~1.1 GB | + +The CUDA binary is large because whisper.cpp compiles GPU kernels for 7 architectures embedded directly in the binary (Maxwell 5.0 through Ada Lovelace 8.9). + +## Prerequisites + +### On the Docker Host + +1. **NVIDIA driver** installed and working (verify with `nvidia-smi`) +2. **nvidia-container-toolkit** installed: + +```bash +# Ubuntu/Debian +sudo apt-get install nvidia-container-toolkit +sudo systemctl restart docker +``` + +3. **GPU** must be accessible from inside the container + +### In the Jellyfin Container + +The container must be started with GPU passthrough: + +```bash +docker run -d \ + --name jellyfin \ + --gpus all \ + -v /path/to/cache:/cache \ + # ... other volumes + jellyfin/jellyfin +``` + +Or using docker-compose: + +```yaml +services: + jellyfin: + image: jellyfin/jellyfin + runtime: nvidia + environment: + - NVIDIA_VISIBLE_DEVICES=all + # ... +``` + +## How the Dual-Binary System Works + +1. **Build time:** The Dockerfile builds both binaries in a multi-stage build +2. **Bundle time:** make-release.sh packages both binaries + CUDA .so files into the plugin zip +3. **Deploy time:** WhisperBinaryManager copies both to the cache directory +4. **Runtime:** WhisperService selects the binary based on config: + +``` +User checks "Enable CUDA" ✓ + ↓ +DetectGPU() returns "cuda" (nvidia-smi works in container) + ↓ +IsCudaBinaryAvailable() == true (CUDA binary in cache) + ↓ +Use CUDA binary at /cache/whisper-cpp/whisper-whisper-cli-cuda + ↓ +Set LD_LIBRARY_PATH to bundled .so directory + ↓ +BuildArguments() → "-dev 0" (GPU device 0) + ↓ +Run CUDA binary +``` + +If any step fails (no GPU detected, CUDA binary not found), the plugin logs a warning and falls back to the CPU binary. + +## Bundled CUDA Libraries + +The plugin bundles three shared libraries from the CUDA toolkit: + +| Library | Size | Purpose | +|---|---|---| +| `libcudart.so.12` | 692 KB | CUDA Runtime API | +| `libcublas.so.12` | 105 MB | CUDA BLAS (matrix operations) | +| `libcublasLt.so.12` | 422 MB | CUDA BLAS Light (optimized kernels) | + +These are extracted alongside the CUDA binary. The plugin sets `LD_LIBRARY_PATH` to find them. + +The NVIDIA driver library `libcuda.so.1` is NOT bundled — it comes from the host's NVIDIA driver via the container's GPU passthrough. + +## Verification + +### From the Config Page + +Check Dashboard → Plugins → Whisper Subtitles → Settings: + +- **Runtime Hardware Status:** shows "cuda" if NVIDIA GPU is detected +- **CUDA Binary:** shows "✓ deployed and ready" if the CUDA binary is available + +### From Jellyfin Logs + +``` +[INF] Detected GPU: cuda +[INF] CUDA binary at /cache/whisper-cpp/whisper-whisper-cli-cuda +[INF] Using CUDA binary at /cache/whisper-cpp/whisper-whisper-cli-cuda +``` + +### From Inside the Container + +```bash +# Check GPU access +docker exec jellyfin nvidia-smi + +# Verify CUDA binary is deployed +docker exec jellyfin ls -la /cache/whisper-cpp/whisper-whisper-cli-cuda + +# Check bundled .so files +docker exec jellyfin ls -la /cache/whisper-cpp/libcublas*.so* +``` + +## Performance + +Typical speedups with CUDA (relative to CPU-only on a Xeon E5-2660 v3, 6 threads): + +| Model | CPU (6 threads) | GPU (RTX 3060) | Speedup | +|---|---|---|---| +| Tiny | ~0.3x realtime | ~40x realtime | ~130x | +| Base | ~0.8x realtime | ~30x realtime | ~37x | +| Small | ~0.4x realtime | ~15x realtime | ~37x | +| Turbo | — | ~20x realtime | — | + +> A 30-minute chunk on CPU takes ~23 minutes with Base model. On CUDA it takes ~1 minute. + +## Troubleshooting GPU + +| Symptom | Likely Cause | Solution | +|---|---|---| +| "no GPU found" in logs | Container doesn't have `--gpus all` | Restart with GPU passthrough | +| "CUDA binary not available" | CUDA binary not deployed | Delete cache, reinstall plugin | +| nvidia-smi works on host but not in container | nvidia-container-toolkit not installed | Install and restart Docker | +| Plugin shows GPU but whisper uses CPU | "Enable CUDA" not checked | Check the checkbox in config | diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..e4019ba --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,44 @@ +# Whisper Subtitles Plugin for Jellyfin + +Generate AI-powered subtitles for your media library — completely local, no external API calls. + +Built on [whisper.cpp](https://github.com/ggerganov/whisper.cpp), this plugin runs the Whisper model directly on your Jellyfin server, supporting both CPU and NVIDIA GPU (CUDA) acceleration. + +## Features + +- **Fully local** — no data leaves your server, no API keys, no subscription +- **Multiple models** — Tiny through Large, plus Turbo for speed/quality balance +- **CPU & GPU** — runs on CPU with AVX2, or NVIDIA GPU via CUDA +- **Batch processing** — scheduled task processes your entire library +- **Library scan hook** — auto-generate subtitles for new media +- **Chunked processing** — handles long videos (3+ hours) without OOM +- **Progress reporting** — per-chunk progress for the scheduled task UI +- **SRT output** — standard subtitle format compatible with all clients +- **Translation** — translate any language to English +- **Word timestamps** — word-level timing for fine subtitle sync +- **AI identifier tags** — mark generated subtitles for easy identification + +## Quick Start + +1. Install the plugin via [manifest or manual zip](Installation) +2. Go to Dashboard → Plugins → Whisper Subtitles +3. Select a model (Small recommended) and target language +4. Click **Download Model** (first run only — downloads ~460MB) +5. Click **Save** at the bottom +6. Go to Dashboard → Scheduled Tasks → **Generate Whisper Subtitles** → Run + +> **First run will download the whisper model to `~/.cache/whisper/` and deploy the binary to `~/.cache/whisper-cpp/`. This happens automatically.** + +## Table of Contents + +| Page | Description | +|---|---| +| [Installation](Installation) | Install via manifest, zip, or manual deployment | +| [Configuration](Configuration) | All settings explained | +| [Usage](Usage) | Running tasks, interpreting results | +| [Architecture](Architecture) | How the plugin works internally | +| [GPU Acceleration](GPU-Acceleration) | CUDA setup and dual-binary system | +| [Build from Source](Build-from-Source) | Building whisper.cpp + plugin | +| [Troubleshooting](Troubleshooting) | Common issues and solutions | +| [Version History](Version-History) | Changelog of all releases | +| [Development](Development) | Code structure and contributing | diff --git a/wiki/Installation.md b/wiki/Installation.md new file mode 100644 index 0000000..2c44193 --- /dev/null +++ b/wiki/Installation.md @@ -0,0 +1,87 @@ +# Installation + +## Prerequisites + +- **Jellyfin 10.11.x** (other versions may work but 10.11.2 is the target ABI) +- **Linux x86_64** server (the binary is compiled for linux-x64) +- **~750MB free disk space** for the plugin zip (v0.0.0.98+ includes the CUDA binary) +- **Additional disk space** for downloaded model files (75MB for Tiny, up to 3GB for Large) + +## Method 1: Repository Manifest + +Add the plugin repository to Jellyfin: + +1. Dashboard → Plugins → Repositories → **Add** +2. URL: `https://github.com/zakattack02/Whisper-Script/raw/refs/heads/feature/jellyfin-plugin/manifest.json` +3. Name: `Whisper Subtitles` +4. Click **Save** + +Then: Catalog → Find "Whisper Subtitles" → Install → Restart Jellyfin. + +## Method 2: Manual ZIP Upload + +1. Download the latest release zip from [GitHub Releases](https://github.com/zakattack02/Whisper-Script/releases) +2. Dashboard → Plugins → **Manual Install** → Browse to the zip → Upload +3. Restart Jellyfin + +## Method 3: Manual Filesystem Deployment + +Extract the zip directly into the Jellyfin plugins directory: + +```bash +# Find your plugin directory (usually one of these) +ls /usr/lib/jellyfin/plugins/ +ls /var/lib/jellyfin/plugins/ +# or check from Jellyfin Dashboard → About + +# Extract the plugin +sudo unzip jellyfin-plugin-whispersubtitles_0.0.0.98.zip \ + -d /var/lib/jellyfin/plugins/Whisper\ Subtitles_0.0.0.98/ + +# Fix permissions +sudo chown -R jellyfin:jellyfin /var/lib/jellyfin/plugins/Whisper\ Subtitles_0.0.0.98/ + +# Restart Jellyfin +sudo systemctl restart jellyfin +``` + +## Post-Install + +After restart, the plugin needs to: + +1. **Copy the binary** from the plugin bundle to the cache directory (`~/.cache/whisper-cpp/`) +2. **Download the model** (if you click "Download Model" or when the task first runs) + +> On first use, the plugin automatically deploys the binary from its bundle. You do NOT need to manually copy anything. The cache is at `$JELLYFIN_CACHE_DIR/whisper-cpp/` or `~/.cache/whisper-cpp/`. + +## Upgrading + +When upgrading, delete the old binary cache so the new binary is deployed: + +```bash +rm -rf /cache/whisper-cpp/ +# or +rm -rf ~/.cache/whisper-cpp/ +``` + +Then install the new plugin version and restart Jellyfin. + +## Docker Considerations + +If running Jellyfin in Docker: + +```bash +# Ensure the cache directory persists +docker run -d \ + --name jellyfin \ + -v /path/to/cache:/cache \ + # ... other volumes + jellyfin/jellyfin + +# For GPU support, add: +docker run -d \ + --gpus all \ + # ... or use nvidia-container-toolkit +``` + +The plugin uses `JELLYFIN_CACHE_DIR` environment variable if set, otherwise `~/.cache/`. diff --git a/wiki/Troubleshooting.md b/wiki/Troubleshooting.md new file mode 100644 index 0000000..5e4613c --- /dev/null +++ b/wiki/Troubleshooting.md @@ -0,0 +1,145 @@ +# Troubleshooting + +## SIGILL (exit code 132) — Illegal Instruction + +**Symptom:** whisper-cli crashes immediately with exit code 132 (SIGILL). + +**Cause:** The binary was compiled with AVX-512 instructions (from `-DGGML_NATIVE=ON` on an AVX-512-capable CPU like Ryzen 9950X3D) but is running on a CPU that doesn't support AVX-512 (like Xeon E5-2660 v3). + +**Fix:** Rebuild with `-DGGML_NATIVE=OFF`: + +```bash +# In Dockerfile.whisper or cmake: +-DGGML_NATIVE=OFF +-DCMAKE_C_FLAGS="-march=x86-64 -mtune=generic" +-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=generic" +``` + +**Verification:** Check which SIMD extensions the binary uses: + +```bash +objdump -T whisper-cli | grep -i avx +``` + +A correct build shows `AVX` and `AVX2` but NOT `AVX512`. + +*Applies to: v0.0.0.92 and earlier. Fixed in v0.0.0.93+.* + +## "Unknown argument" — exit(0) without processing + +**Symptom:** whisper-cli exits immediately with code 0, no error, no processing. + +**Cause:** Using flags that the `whisper-cli` target doesn't support: + +| Flag | Status | Alternative | +|---|---|---| +| `--output-dir` | Not in `whisper-cli` | Use `-of` with `WorkingDirectory` | +| `-vv` | Not in `whisper-cli` | Remove (default verbosity is sufficient) | +| `-ngl N` | Not in `whisper-cli` | Use `-dev 0` for GPU, `-ng` for CPU | + +*Applies to: v0.0.0.91 and earlier. Fixed in v0.0.0.92+.* + +## OOM Killer (exit code 137) + +**Symptom:** Process killed by OOM (exit code 137 / SIGKILL), especially with long videos on low-memory servers. + +**Cause:** Whisper loads the entire audio as PCM float32 into memory. A 3-hour WAV is ~307MB, but as PCM float32 it's ~614MB, plus model weights (e.g., Base = 140MB), plus working memory → exceeds 4GB. + +**Fix:** Audio chunking splits long audio into 30-minute segments. Each segment is ~60MB WAV → ~120MB PCM → well within 4GB even with a Large model: + +| Model | RAM with chunking (per chunk) | +|---|---| +| Tiny | ~300 MB | +| Base | ~400 MB | +| Small | ~800 MB | +| Turbo | ~2 GB | +| Medium | ~2 GB | +| Large | ~3.8 GB | + +*Applies to: v0.0.0.94 and earlier. Fixed in v0.0.0.95+.* + +## ffprobe Not Found (Win32Exception / exit code 2) + +**Symptom:** Error starting ffprobe process: + +``` +Win32Exception: ApplicationName='/usr/lib/jellyfin-ffprobe/ffprobe' +The system cannot find the file specified +``` + +**Cause:** The old code used `.Replace("ffmpeg", "ffprobe")` on the ffmpeg path. If ffmpeg is at `/usr/lib/jellyfin-ffmpeg/ffmpeg`, this produces `/usr/lib/jellyfin-ffprobe/ffprobe` (wrong — the directory is `jellyfin-ffmpeg`, not `jellyfin-ffprobe`). + +**Fix:** v0.0.0.96+ uses `FindFfprobe()` which looks: +1. Next to the found ffmpeg binary (same directory) +2. Known paths: `/usr/lib/jellyfin-ffmpeg/ffprobe`, `/usr/lib/jellyfin-ffmpeg5/ffprobe`, etc. +3. `which ffprobe` +4. Config override in the FfprobePath field + +**Manual fix:** Set the **FFprobe Path** field in Settings to the correct path. + +*Applies to: v0.0.0.95 and earlier. Fixed in v0.0.0.96+.* + +## "no GPU found" + +**Symptom:** whisper.log shows `whisper_backend_init_gpu: no GPU found`. + +**Cause:** Either: +1. The plugin is using the CPU binary (expected — CPU binary says this) +2. The container doesn't have GPU access + +**Check:** +- Config page shows **Runtime Hardware Status** (should be "cuda") +- CUDA binary is deployed (config shows "✓ deployed and ready") +- Container has `--gpus all` or nvidia-container-toolkit configured +- `docker exec jellyfin nvidia-smi` works + +## Binary Fails to Deploy + +**Symptom:** "Bundled binary not found" in logs or config page shows "System Execution Binary Absent" + +**Cause:** Plugin zip was extracted incorrectly or the binary isn't in the expected path. + +**Plugin directory structure (expected):** +``` +plugins/Whisper Subtitles_0.0.0.98/ +├── Jellyfin.Plugin.WhisperSubtitles.dll +├── Jellyfin.Plugin.WhisperSubtitles.dll +├── whisper/ +│ └── linux-x64/ +│ ├── whisper-whisper-cli +│ ├── whisper-whisper-cli-cuda +│ ├── libcudart.so.12 +│ ├── libcublas.so.12 +│ └── libcublasLt.so.12 +└── ... +``` + +**Fix:** Reinstall the plugin. If manually extracting, ensure the `whisper/` directory is inside the plugin folder. + +## Plugin Config Page Shows Diagnostic Info + +The config page shows diagnostics including: +- Whether the binary is deployed +- GPU type detected +- Number of models cached +- Available CPU threads + +If diagnostics show "unconfigured," run the **Install Binary** button or trigger the task once (it auto-deploys). + +## Progress Stuck at 0% + +**Symptom:** The scheduled task shows 0% for a long time. + +**Cause:** Before v0.0.0.97, progress was reported per-video. A single video chunked into 6 parts would show 0% for 7+ minutes. + +**Fix:** v0.0.0.97+ reports progress per-chunk within each video. + +## Deploying a New Version + +When upgrading, always clear the old binary cache: + +```bash +rm -rf /cache/whisper-cpp/ +``` + +Otherwise the old binary is used even with the new plugin installed. diff --git a/wiki/Usage.md b/wiki/Usage.md new file mode 100644 index 0000000..e27993e --- /dev/null +++ b/wiki/Usage.md @@ -0,0 +1,77 @@ +# Usage + +## Running the Task + +1. Dashboard → Scheduled Tasks → **Generate Whisper Subtitles** +2. Click the **play** (▶) button to run immediately +3. Or configure a trigger (e.g., daily at 2 AM) + +The task processes all videos in the selected libraries (or all libraries, if none are configured). + +## What Happens During Processing + +For each video: + +1. **Skip check** — The plugin checks for existing subtitles. If `Skip Existing` is on and subtitles exist (non-AI), the video is skipped. +2. **Audio extraction** — FFmpeg extracts audio as 16kHz mono WAV. +3. **Duration check** — If audio > 30 minutes, it's split into chunks. +4. **Transcription** — Each chunk (or the full audio) is processed by whisper.cpp. +5. **SRT merging** — Chunks are merged into a single SRT with sequential numbering. +6. **File tagging** — The output filename includes the AI identifier tag. + +### File Naming + +``` +Original: /Media/Movies/My Movie (2024).mkv +Subtitle: /Media/Movies/My Movie (2024).en.whisper.srt + ↑ ↑ ↑ + lang ID tag +``` + +## Scheduled Triggers + +The task supports Jellyfin's scheduling system. Common setups: + +- **Daily** — run every night at 3 AM +- **Weekly** — run every Sunday +- **On library scan** — use "Process on Library Scan" in config instead + +## Monitoring Progress + +The scheduled task UI shows percentage progress. With chunked videos, progress updates per-chunk: + +- 22 videos, 1 video chunked into 6 parts = 27 "ticks" +- Progress: `0% → 3.7% → 7.4% → ... → 100%` + +## Logs + +Check the Jellyfin logs for detailed per-step logging: + +``` +[INF] Whisper task starting. Model=Small, Language="en", ... +[INF] Generating: /Media/Movies/My Movie (2024).mkv +[INF] Extracting audio ... +[INF] Audio extracted (123456789 bytes) +[INF] Audio split into 4 chunk(s) +[INF] Using CUDA binary at /cache/whisper-cpp/whisper-whisper-cli-cuda +[INF] whisper: "main: processing ... (1800.0 sec), 6 threads ..." +[INF] whisper: "main: processing ... (1800.0 sec), 6 threads ..." +[INF] Subtitles written: My Movie (2024).en.whisper.srt (12345 bytes) +[INF] Task complete. Generated=1, Skipped=0, Errors=0 +``` + +## Interpreting Results + +Check for the `.en.whisper.srt` (or `.{lang}.{tag}.srt`) file next to the video in Jellyfin. If subtitles don't appear: + +1. Scan the media library manually +2. Check if the subtitle file exists on disk +3. Verify the language tag matches what Jellyfin expects + +## Post-Scan Processing + +If "Process on Library Scan" is enabled, the plugin automatically generates subtitles for new media items when: +- A library scan completes +- New files are detected by the file system watcher + +Use `FoldersToExclude` to prevent processing in certain directories. diff --git a/wiki/Version-History.md b/wiki/Version-History.md new file mode 100644 index 0000000..dcdead6 --- /dev/null +++ b/wiki/Version-History.md @@ -0,0 +1,50 @@ +# Version History + +## v0.0.0.98 — CUDA GPU Support + +- Multi-stage Dockerfile builds both CPU and CUDA binaries +- Plugin auto-selects CUDA binary when GPU is enabled and available +- Bundles `libcudart.so.12`, `libcublas.so.12`, `libcublasLt.so.12` +- Config page shows CUDA binary deployment status +- CUDA binary compiled for 7 GPU architectures (Maxwell through Ada) +- Package size increased to ~742 MB (was 3.5 MB with CPU-only) + +## v0.0.0.97 — Per-Chunk Progress Reporting + +- Scheduled task now reports progress per-chunk instead of per-video +- `IProgress` passed through to chunking loop +- Smooth progress updates for long videos + +## v0.0.0.96 — ffprobe Path Fix + +- Added `FfprobePath` config field for manual override +- Added `FindFfprobe()` with proper fallback chain (same directory as ffmpeg) +- Fixed `.Replace("ffmpeg", "ffprobe")` bug producing wrong path + +## v0.0.0.95 — OOM Prevention + +- Added audio chunking for files > 30 minutes +- FFmpeg segment muxer splits WAV into 30-min chunks +- SRT merging with segment renumbering +- Temp file cleanup in `finally` block + +## v0.0.0.94 — GPU Flag Fix + +- Changed `-ngl 999` to `-dev 0` for GPU mode (whisper-cli doesn't support `-ngl`) + +## v0.0.0.93 — AVX-512 Fix + +- Added `-DGGML_NATIVE=OFF` to Dockerfile and Build-whisper.sh +- Explicit `-march=x86-64 -mtune=generic` flags +- Prevents SIGILL on CPUs without AVX-512 + +## v0.0.0.92 — BuildArguments Fix + +- Removed `--output-dir` flag (not in whisper-cli) +- Removed `-vv` flag (not in whisper-cli) +- Changed GPU flag to `-dev 0` +- Added `-ng` for CPU mode + +## Earlier Versions (v0.0.0.47 — v0.0.0.91) + +Initial development versions. Core functionality was established but suffered from the issues documented in [Troubleshooting](Troubleshooting).