From 6904edf68c359cff56b5e6cae9c1d05e2173cd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20F=C3=BCrni=C3=9F?= Date: Wed, 9 Mar 2022 20:47:03 +0100 Subject: [PATCH 1/5] add extracting attachments from external subs --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 10 +++ .../MediaEncoding/IAttachmentExtractor.cs | 5 ++ .../Attachments/AttachmentExtractor.cs | 80 ++++++++++++++----- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 49a3948688..3235da2920 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -13,6 +13,7 @@ using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -530,6 +531,15 @@ namespace Jellyfin.Api.Helpers { var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, CancellationToken.None).ConfigureAwait(false); + + if (state.SubtitleStream.IsExternal) + { + string subtitlePath = state.SubtitleStream.Path; + string subtitlePathArgument = string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", subtitlePath.Replace("\"", "\\\"", StringComparison.Ordinal)); + string subtitleId = subtitlePath.GetMD5().ToString("N", CultureInfo.InvariantCulture); + + await _attachmentExtractor.ExtractAllAttachmentsExternal(subtitlePathArgument, subtitleId, attachmentPath, CancellationToken.None).ConfigureAwait(false); + } } var process = new Process diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index a2b6be1e6d..cffc60ba37 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -23,5 +23,10 @@ namespace MediaBrowser.Controller.MediaEncoding MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken); + Task ExtractAllAttachmentsExternal( + string inputArgument, + string id, + string outputPath, + CancellationToken cancellationToken); } } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 06d20d90e1..eb58dd9e24 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -89,29 +89,63 @@ namespace MediaBrowser.MediaEncoding.Attachments string outputPath, CancellationToken cancellationToken) { - var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + if (!Directory.Exists(outputPath)) { - if (!Directory.Exists(outputPath)) + var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try { await ExtractAllAttachmentsInternal( _mediaEncoder.GetInputArgument(inputFile, mediaSource), outputPath, + false, cancellationToken).ConfigureAwait(false); } + finally + { + semaphore.Release(); + } } - finally + } + + public async Task ExtractAllAttachmentsExternal( + string inputArgument, + string id, + string outputPath, + CancellationToken cancellationToken) + { + if (!File.Exists(Path.Join(outputPath, id))) { - semaphore.Release(); + var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + await ExtractAllAttachmentsInternal( + inputArgument, + outputPath, + true, + cancellationToken).ConfigureAwait(false); + } + finally + { + if (Directory.Exists(outputPath)) + { + File.Create(Path.Join(outputPath, id)); + } + + semaphore.Release(); + } } } private async Task ExtractAllAttachmentsInternal( string inputPath, string outputPath, + bool isExternal, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) @@ -124,11 +158,14 @@ namespace MediaBrowser.MediaEncoding.Attachments throw new ArgumentNullException(nameof(outputPath)); } - Directory.CreateDirectory(outputPath); + if (!Directory.Exists(outputPath)) + { + Directory.CreateDirectory(outputPath); + } var processArgs = string.Format( CultureInfo.InvariantCulture, - "-dump_attachment:t \"\" -i {0} -t 0 -f null null", + "-dump_attachment:t \"\" -y -i {0} -t 0 -f null null", inputPath); int exitCode; @@ -174,19 +211,24 @@ namespace MediaBrowser.MediaEncoding.Attachments if (exitCode != 0) { - failed = true; - - _logger.LogWarning("Deleting extracted attachments {Path} due to failure: {ExitCode}", outputPath, exitCode); - try + if (isExternal && exitCode == 1) { - if (Directory.Exists(outputPath)) + // ffmpeg returns exitCode 1 because there is no video or audio stream + // this can be ignored + } + else + { + failed = true; + + _logger.LogWarning("Deleting extracted attachments {Path} due to failure: {ExitCode}", outputPath, exitCode); + try { Directory.Delete(outputPath); } - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting extracted attachments {Path}", outputPath); + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted attachments {Path}", outputPath); + } } } else if (!Directory.Exists(outputPath)) From 90463d5e4f83c6b9972b975cce343e42a181b8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20F=C3=BCrni=C3=9F?= Date: Sat, 12 Mar 2022 10:19:57 +0100 Subject: [PATCH 2/5] Pass CancellationToken to ExtractAllAttachments --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 4 ++-- MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 3235da2920..d8479de2dd 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -530,7 +530,7 @@ namespace Jellyfin.Api.Helpers if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); - await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, CancellationToken.None).ConfigureAwait(false); + await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); if (state.SubtitleStream.IsExternal) { @@ -538,7 +538,7 @@ namespace Jellyfin.Api.Helpers string subtitlePathArgument = string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", subtitlePath.Replace("\"", "\\\"", StringComparison.Ordinal)); string subtitleId = subtitlePath.GetMD5().ToString("N", CultureInfo.InvariantCulture); - await _attachmentExtractor.ExtractAllAttachmentsExternal(subtitlePathArgument, subtitleId, attachmentPath, CancellationToken.None).ConfigureAwait(false); + await _attachmentExtractor.ExtractAllAttachmentsExternal(subtitlePathArgument, subtitleId, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index cffc60ba37..09840d2eea 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -18,11 +18,13 @@ namespace MediaBrowser.Controller.MediaEncoding string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken); + Task ExtractAllAttachments( string inputFile, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken); + Task ExtractAllAttachmentsExternal( string inputArgument, string id, From eb44f892b8f91451317fa63380f8c62907e24d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20F=C3=BCrni=C3=9F?= Date: Sat, 12 Mar 2022 13:49:23 +0100 Subject: [PATCH 3/5] Only extract attachments from external .mks file --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index d8479de2dd..da3c1530c5 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -532,7 +532,7 @@ namespace Jellyfin.Api.Helpers var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); - if (state.SubtitleStream.IsExternal) + if (state.SubtitleStream.IsExternal && string.Equals(Path.GetExtension(state.SubtitleStream.Path), ".mks", StringComparison.OrdinalIgnoreCase)) { string subtitlePath = state.SubtitleStream.Path; string subtitlePathArgument = string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", subtitlePath.Replace("\"", "\\\"", StringComparison.Ordinal)); From b4bb82b6d7537dde1ee868b1a1cf9d3652f83866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20F=C3=BCrni=C3=9F?= Date: Sat, 12 Mar 2022 21:46:29 +0100 Subject: [PATCH 4/5] Improve code --- .../Attachments/AttachmentExtractor.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index eb58dd9e24..00aafa1262 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -89,12 +89,12 @@ namespace MediaBrowser.MediaEncoding.Attachments string outputPath, CancellationToken cancellationToken) { + var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + if (!Directory.Exists(outputPath)) { - var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try { await ExtractAllAttachmentsInternal( @@ -116,12 +116,12 @@ namespace MediaBrowser.MediaEncoding.Attachments string outputPath, CancellationToken cancellationToken) { + var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + if (!File.Exists(Path.Join(outputPath, id))) { - var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try { await ExtractAllAttachmentsInternal( @@ -158,10 +158,7 @@ namespace MediaBrowser.MediaEncoding.Attachments throw new ArgumentNullException(nameof(outputPath)); } - if (!Directory.Exists(outputPath)) - { - Directory.CreateDirectory(outputPath); - } + Directory.CreateDirectory(outputPath); var processArgs = string.Format( CultureInfo.InvariantCulture, From d7d36a102aedf1801c8f76a0ead983c5a5a87ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20F=C3=BCrni=C3=9F?= Date: Sun, 13 Mar 2022 11:20:09 +0100 Subject: [PATCH 5/5] Fix releasing lock --- .../Attachments/AttachmentExtractor.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 00aafa1262..142571e8f6 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -93,9 +93,9 @@ namespace MediaBrowser.MediaEncoding.Attachments await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - if (!Directory.Exists(outputPath)) + try { - try + if (!Directory.Exists(outputPath)) { await ExtractAllAttachmentsInternal( _mediaEncoder.GetInputArgument(inputFile, mediaSource), @@ -103,10 +103,10 @@ namespace MediaBrowser.MediaEncoding.Attachments false, cancellationToken).ConfigureAwait(false); } - finally - { - semaphore.Release(); - } + } + finally + { + semaphore.Release(); } } @@ -120,26 +120,26 @@ namespace MediaBrowser.MediaEncoding.Attachments await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - if (!File.Exists(Path.Join(outputPath, id))) + try { - try + if (!File.Exists(Path.Join(outputPath, id))) { await ExtractAllAttachmentsInternal( inputArgument, outputPath, true, cancellationToken).ConfigureAwait(false); - } - finally - { + if (Directory.Exists(outputPath)) { File.Create(Path.Join(outputPath, id)); } - - semaphore.Release(); } } + finally + { + semaphore.Release(); + } } private async Task ExtractAllAttachmentsInternal(