diff options
30 files changed, 236 insertions, 169 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 86a8ecc82..76b3c1dcb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -45,6 +45,7 @@ - [Froghut](https://github.com/Froghut) - [fruhnow](https://github.com/fruhnow) - [geilername](https://github.com/geilername) + - [GermanCoding](https://github.com/GermanCoding) - [gnattu](https://github.com/gnattu) - [GodTamIt](https://github.com/GodTamIt) - [grafixeyehero](https://github.com/grafixeyehero) @@ -76,6 +77,7 @@ - [mitchfizz05](https://github.com/mitchfizz05) - [MrTimscampi](https://github.com/MrTimscampi) - [n8225](https://github.com/n8225) + - [Nalsai](https://github.com/Nalsai) - [Narfinger](https://github.com/Narfinger) - [NathanPickard](https://github.com/NathanPickard) - [neilsb](https://github.com/neilsb) diff --git a/Dockerfile b/Dockerfile index f0090889f..c3038b1d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,10 +22,10 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" # https://github.com/intel/compute-runtime/releases -ARG GMMLIB_VERSION=21.2.1 -ARG IGC_VERSION=1.0.8517 -ARG NEO_VERSION=21.35.20826 -ARG LEVEL_ZERO_VERSION=1.2.20826 +ARG GMMLIB_VERSION=22.0.2 +ARG IGC_VERSION=1.0.10395 +ARG NEO_VERSION=22.08.22549 +ARG LEVEL_ZERO_VERSION=1.3.22549 # Install dependencies: # mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding. @@ -48,8 +48,7 @@ RUN apt-get update \ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${NEO_VERSION}_amd64.deb \ - && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-ocloc_${NEO_VERSION}_amd64.deb \ + && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \ && dpkg -i *.deb \ && cd .. \ diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 758986945..57c2f1a5e 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.Images protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items) { - var useBackdrop = primaryItem is CollectionFolder; + var useBackdrop = primaryItem is CollectionFolder || primaryItem is UserView; return items .Select(i => { diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 575680653..9f9a4902a 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -84,16 +84,20 @@ namespace Emby.Server.Implementations.Images }).GroupBy(x => x.Id) .Select(x => x.First()); + List<BaseItem> returnItems; if (isUsingCollectionStrip) { - return items + returnItems = items .Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)) .ToList(); + returnItems.Shuffle(); + return returnItems; } - - return items + returnItems = items .Where(i => i.HasImage(ImageType.Primary)) .ToList(); + returnItems.Shuffle(); + return returnItems; } protected override bool Supports(BaseItem item) diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 568a8e447..e06f8e6fe 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -119,5 +119,7 @@ "TaskDownloadMissingSubtitles": "Download missing subtitles", "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", "TaskOptimizeDatabase": "Optimize database", - "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance." + "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", + "TaskKeyframeExtractor": "Keyframe Extractor", + "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time." } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6c679ea20..ab860ef67 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -424,9 +424,14 @@ namespace Emby.Server.Implementations.Session var nowPlayingQueue = info.NowPlayingQueue; - if (nowPlayingQueue != null) + if (nowPlayingQueue?.Length > 0) { session.NowPlayingQueue = nowPlayingQueue; + + var itemIds = nowPlayingQueue.Select(queue => queue.Id).ToArray(); + session.NowPlayingQueueFullItems = _dtoService.GetBaseItemDtos( + _libraryManager.GetItemList(new InternalItemsQuery { ItemIds = itemIds }), + new DtoOptions(true)); } } diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 0b2604640..27eb22339 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -126,9 +126,11 @@ namespace Jellyfin.Api.Controllers HomeSectionType.SmallLibraryTiles, HomeSectionType.Resume, HomeSectionType.ResumeAudio, + HomeSectionType.ResumeBook, HomeSectionType.LiveTv, HomeSectionType.NextUp, - HomeSectionType.LatestMedia, HomeSectionType.None, + HomeSectionType.LatestMedia, + HomeSectionType.None, }; if (!Guid.TryParse(displayPreferencesId, out var itemId)) @@ -182,7 +184,7 @@ namespace Jellyfin.Api.Controllers var order = int.Parse(key.AsSpan().Slice("homesection".Length)); if (!Enum.TryParse<HomeSectionType>(displayPreferences.CustomPrefs[key], true, out var type)) { - type = order < 7 ? defaults[order] : HomeSectionType.None; + type = order < 8 ? defaults[order] : HomeSectionType.None; } displayPreferences.CustomPrefs.Remove(key); diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index b41df1abb..b227dba2d 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; -using Jellyfin.Api.Models.PluginDtos; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -44,61 +43,6 @@ namespace Jellyfin.Api.Controllers } /// <summary> - /// Get plugin security info. - /// </summary> - /// <response code="200">Plugin security info returned.</response> - /// <returns>Plugin security info.</returns> - [Obsolete("This endpoint should not be used.")] - [HttpGet("SecurityInfo")] - [ProducesResponseType(StatusCodes.Status200OK)] - public static ActionResult<PluginSecurityInfo> GetPluginSecurityInfo() - { - return new PluginSecurityInfo - { - IsMbSupporter = true, - SupporterKey = "IAmTotallyLegit" - }; - } - - /// <summary> - /// Gets registration status for a feature. - /// </summary> - /// <param name="name">Feature name.</param> - /// <response code="200">Registration status returned.</response> - /// <returns>Mb registration record.</returns> - [Obsolete("This endpoint should not be used.")] - [HttpPost("RegistrationRecords/{name}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public static ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute, Required] string name) - { - return new MBRegistrationRecord - { - IsRegistered = true, - RegChecked = true, - TrialVersion = false, - IsValid = true, - RegError = false - }; - } - - /// <summary> - /// Gets registration status for a feature. - /// </summary> - /// <param name="name">Feature name.</param> - /// <response code="501">Not implemented.</response> - /// <returns>Not Implemented.</returns> - /// <exception cref="NotImplementedException">This endpoint is not implemented.</exception> - [Obsolete("Paid plugins are not supported")] - [HttpGet("Registrations/{name}")] - [ProducesResponseType(StatusCodes.Status501NotImplemented)] - public static ActionResult GetRegistration([FromRoute, Required] string name) - { - // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, - // delete all these registration endpoints. They are only kept for compatibility. - throw new NotImplementedException(); - } - - /// <summary> /// Gets a list of currently installed plugins. /// </summary> /// <response code="200">Installed plugins returned.</response> @@ -317,20 +261,5 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - - /// <summary> - /// Updates plugin security info. - /// </summary> - /// <param name="pluginSecurityInfo">Plugin security info.</param> - /// <response code="204">Plugin security info updated.</response> - /// <returns>An <see cref="NoContentResult"/>.</returns> - [Obsolete("This endpoint should not be used.")] - [HttpPost("SecurityInfo")] - [Authorize(Policy = Policies.RequiresElevation)] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo) - { - return NoContent(); - } } } diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 56a54fb1d..e70b79837 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -18,6 +18,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -42,6 +43,8 @@ namespace Jellyfin.Api.Helpers /// </summary> private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>(); + private readonly IAttachmentExtractor _attachmentExtractor; + private readonly IApplicationPaths _appPaths; private readonly IAuthorizationContext _authorizationContext; private readonly EncodingHelper _encodingHelper; private readonly IFileSystem _fileSystem; @@ -55,6 +58,8 @@ namespace Jellyfin.Api.Helpers /// <summary> /// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class. /// </summary> + /// <param name="attachmentExtractor">Instance of the <see cref="IAttachmentExtractor"/> interface.</param> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobHelpers}"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> @@ -65,6 +70,8 @@ namespace Jellyfin.Api.Helpers /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> public TranscodingJobHelper( + IAttachmentExtractor attachmentExtractor, + IApplicationPaths appPaths, ILogger<TranscodingJobHelper> logger, IMediaSourceManager mediaSourceManager, IFileSystem fileSystem, @@ -75,6 +82,8 @@ namespace Jellyfin.Api.Helpers EncodingHelper encodingHelper, ILoggerFactory loggerFactory) { + _attachmentExtractor = attachmentExtractor; + _appPaths = appPaths; _logger = logger; _mediaSourceManager = mediaSourceManager; _fileSystem = fileSystem; @@ -513,6 +522,13 @@ namespace Jellyfin.Api.Helpers throw new ArgumentException("FFmpeg path not set."); } + // If subtitles get burned in fonts may need to be extracted from the media file + 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); + } + var process = new Process { StartInfo = new ProcessStartInfo diff --git a/Jellyfin.Api/Models/PluginDtos/MBRegistrationRecord.cs b/Jellyfin.Api/Models/PluginDtos/MBRegistrationRecord.cs deleted file mode 100644 index 7f1255f4b..000000000 --- a/Jellyfin.Api/Models/PluginDtos/MBRegistrationRecord.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Jellyfin.Api.Models.PluginDtos -{ - /// <summary> - /// MB Registration Record. - /// </summary> - public class MBRegistrationRecord - { - /// <summary> - /// Gets or sets expiration date. - /// </summary> - public DateTime ExpirationDate { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether is registered. - /// </summary> - public bool IsRegistered { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether reg checked. - /// </summary> - public bool RegChecked { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether reg error. - /// </summary> - public bool RegError { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether trial version. - /// </summary> - public bool TrialVersion { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether is valid. - /// </summary> - public bool IsValid { get; set; } - } -} diff --git a/Jellyfin.Api/Models/PluginDtos/PluginSecurityInfo.cs b/Jellyfin.Api/Models/PluginDtos/PluginSecurityInfo.cs deleted file mode 100644 index a90398425..000000000 --- a/Jellyfin.Api/Models/PluginDtos/PluginSecurityInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Jellyfin.Api.Models.PluginDtos -{ - /// <summary> - /// Plugin security info. - /// </summary> - public class PluginSecurityInfo - { - /// <summary> - /// Gets or sets the supporter key. - /// </summary> - public string? SupporterKey { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether is mb supporter. - /// </summary> - public bool IsMbSupporter { get; set; } - } -} diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 4d9aac6f9..cb5ff6eec 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -848,6 +848,18 @@ namespace MediaBrowser.Controller.Entities return true; } + if (query.HasThemeSong.HasValue) + { + Logger.LogDebug("Query requires post-filtering due to HasThemeSong"); + return true; + } + + if (query.HasThemeVideo.HasValue) + { + Logger.LogDebug("Query requires post-filtering due to HasThemeVideo"); + return true; + } + // Filter by VideoType if (query.VideoTypes.Length > 0) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index cdfde97f6..931337bf4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using Jellyfin.Data.Enums; using Jellyfin.Extensions; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; @@ -28,7 +29,7 @@ namespace MediaBrowser.Controller.MediaEncoding private const string VideotoolboxAlias = "vt"; private const string OpenclAlias = "ocl"; private const string CudaAlias = "cu"; - + private readonly IApplicationPaths _appPaths; private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; @@ -51,9 +52,11 @@ namespace MediaBrowser.Controller.MediaEncoding }; public EncodingHelper( + IApplicationPaths appPaths, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder) { + _appPaths = appPaths; _mediaEncoder = mediaEncoder; _subtitleEncoder = subtitleEncoder; } @@ -1080,6 +1083,12 @@ namespace MediaBrowser.Controller.MediaEncoding var alphaParam = enableAlpha ? ":alpha=1" : string.Empty; var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty; + var fontPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); + var fontParam = string.Format( + CultureInfo.InvariantCulture, + ":fontsdir={0}", + _mediaEncoder.EscapeSubtitleFilterPath(fontPath)); + // TODO // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); // string fallbackFontParam = string.Empty; @@ -1120,11 +1129,12 @@ namespace MediaBrowser.Controller.MediaEncoding // TODO: Perhaps also use original_size=1920x800 ?? return string.Format( CultureInfo.InvariantCulture, - "subtitles=f='{0}'{1}{2}{3}{4}", + "subtitles=f='{0}'{1}{2}{3}{4}{5}", _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), charsetParam, alphaParam, sub2videoParam, + fontParam, // fallbackFontParam, setPtsParam); } @@ -1133,11 +1143,12 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "subtitles='{0}:si={1}{2}{3}'{4}", + "subtitles='{0}:si={1}{2}{3}{4}'{5}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture), alphaParam, sub2videoParam, + fontParam, // fallbackFontParam, setPtsParam); } @@ -1338,29 +1349,37 @@ namespace MediaBrowser.Controller.MediaEncoding switch (encodingOptions.EncoderPreset) { case "veryslow": - - param += " -preset slow"; // lossless is only supported on maxwell and newer(2014+) + param += " -preset p7"; break; case "slow": + param += " -preset p6"; + break; + case "slower": - param += " -preset slow"; + param += " -preset p5"; break; case "medium": - param += " -preset medium"; + param += " -preset p4"; break; case "fast": + param += " -preset p3"; + break; + case "faster": + param += " -preset p2"; + break; + case "veryfast": case "superfast": case "ultrafast": - param += " -preset fast"; + param += " -preset p1"; break; default: - param += " -preset default"; + param += " -preset p4"; break; } } @@ -2162,6 +2181,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) && state.TranscodingType != TranscodingJobType.Progressive + && state.TranscodingType != TranscodingJobType.Hls && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) && (state.BaseRequest.StartTimeTicks ?? 0) > 0) { diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index 4e7e26624..a2b6be1e6 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.MediaEncoding @@ -17,5 +18,10 @@ namespace MediaBrowser.Controller.MediaEncoding string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken); + Task ExtractAllAttachments( + string inputFile, + MediaSourceInfo mediaSource, + string outputPath, + CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 6134c0cf3..c2ca23386 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -39,6 +39,8 @@ namespace MediaBrowser.Controller.Session AdditionalUsers = Array.Empty<SessionUserInfo>(); PlayState = new PlayerStateInfo(); SessionControllers = Array.Empty<ISessionController>(); + NowPlayingQueue = Array.Empty<QueueItem>(); + NowPlayingQueueFullItems = Array.Empty<BaseItemDto>(); } public PlayerStateInfo PlayState { get; set; } @@ -219,7 +221,9 @@ namespace MediaBrowser.Controller.Session } } - public QueueItem[] NowPlayingQueue { get; set; } + public IReadOnlyList<QueueItem> NowPlayingQueue { get; set; } + + public IReadOnlyList<BaseItemDto> NowPlayingQueueFullItems { get; set; } public bool HasCustomDeviceName { get; set; } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 3fd4cd731..06d20d90e 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -83,6 +83,130 @@ namespace MediaBrowser.MediaEncoding.Attachments return (mediaAttachment, attachmentStream); } + public async Task ExtractAllAttachments( + string inputFile, + MediaSourceInfo mediaSource, + 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)) + { + await ExtractAllAttachmentsInternal( + _mediaEncoder.GetInputArgument(inputFile, mediaSource), + outputPath, + cancellationToken).ConfigureAwait(false); + } + } + finally + { + semaphore.Release(); + } + } + + private async Task ExtractAllAttachmentsInternal( + string inputPath, + string outputPath, + CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(inputPath)) + { + throw new ArgumentNullException(nameof(inputPath)); + } + + if (string.IsNullOrEmpty(outputPath)) + { + throw new ArgumentNullException(nameof(outputPath)); + } + + Directory.CreateDirectory(outputPath); + + var processArgs = string.Format( + CultureInfo.InvariantCulture, + "-dump_attachment:t \"\" -i {0} -t 0 -f null null", + inputPath); + + int exitCode; + + using (var process = new Process + { + StartInfo = new ProcessStartInfo + { + Arguments = processArgs, + FileName = _mediaEncoder.EncoderPath, + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + WorkingDirectory = outputPath, + ErrorDialog = false + }, + EnableRaisingEvents = true + }) + { + _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); + + process.Start(); + + var ranToCompletion = await ProcessExtensions.WaitForExitAsync(process, cancellationToken).ConfigureAwait(false); + + if (!ranToCompletion) + { + try + { + _logger.LogWarning("Killing ffmpeg attachment extraction process"); + process.Kill(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error killing attachment extraction process"); + } + } + + exitCode = ranToCompletion ? process.ExitCode : -1; + } + + var failed = false; + + if (exitCode != 0) + { + failed = true; + + _logger.LogWarning("Deleting extracted attachments {Path} due to failure: {ExitCode}", outputPath, exitCode); + try + { + if (Directory.Exists(outputPath)) + { + Directory.Delete(outputPath); + } + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted attachments {Path}", outputPath); + } + } + else if (!Directory.Exists(outputPath)) + { + failed = true; + } + + if (failed) + { + _logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); + + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath)); + } + else + { + _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath); + } + } + private async Task<Stream> GetAttachmentStream( MediaSourceInfo mediaSource, MediaAttachment mediaAttachment, diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs index a1cef7a9f..c9e948780 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.MediaEncoding.Probing public class MediaChapter { [JsonPropertyName("id")] - public int Id { get; set; } + public long Id { get; set; } [JsonPropertyName("time_base")] public string TimeBase { get; set; } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 4c8f19604..8313ab5bc 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -691,9 +691,9 @@ namespace MediaBrowser.MediaEncoding.Probing if (string.IsNullOrEmpty(stream.Title)) { - // mp4 missing track title workaround: fall back to handler_name if populated + // mp4 missing track title workaround: fall back to handler_name if populated and not the default "SoundHandler" string handlerName = GetDictionaryValue(streamInfo.Tags, "handler_name"); - if (!string.IsNullOrEmpty(handlerName)) + if (!string.IsNullOrEmpty(handlerName) && !string.Equals(handlerName, "SoundHandler", StringComparison.OrdinalIgnoreCase)) { stream.Title = handlerName; } diff --git a/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs b/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs index d80925fc9..a7d52184b 100644 --- a/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs +++ b/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs @@ -39,13 +39,13 @@ public class KeyframeExtractionScheduledTask : IScheduledTask } /// <inheritdoc /> - public string Name => "Keyframe Extractor"; + public string Name => _localizationManager.GetLocalizedString("TaskKeyframeExtractor"); /// <inheritdoc /> public string Key => "KeyframeExtraction"; /// <inheritdoc /> - public string Description => "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time."; + public string Description => _localizationManager.GetLocalizedString("TaskKeyframeExtractorDescription"); /// <inheritdoc /> public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory"); diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index e04bfffbb..e3237a8e3 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -21,7 +21,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index f7f9c0361..abe7bee64 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -13,7 +13,7 @@ <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index a9935bbdb..cb1536274 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -8,7 +8,7 @@ <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index 7a1d88ca6..7803e49e1 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -23,7 +23,7 @@ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> </ItemGroup> diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index cc3d4faa0..929764e42 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -13,7 +13,7 @@ <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 00aac8965..5c3c39bf6 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -17,7 +17,7 @@ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <!-- Code Analyzers--> diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 5531049f5..59c737c7d 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -14,7 +14,7 @@ <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 066112dcb..fb8593345 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -22,7 +22,7 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 43e38ea6e..0af39affa 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -16,7 +16,7 @@ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Xunit.Priority" Version="1.1.6" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <ItemGroup> diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 9576f6a11..bc829d0a1 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -16,7 +16,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index f34dbc922..c4469d10a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -14,7 +14,7 @@ <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> |
