From 519709bf10e30a3666af100df4fcca206dcef498 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 3 Feb 2023 18:49:23 +0100 Subject: Revert "Remove DvdLib (#9068)" This reverts commit db1913b08fac0749133634efebd1ee7a7876147a. --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index cef02d5f8..4a795625d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -865,6 +865,85 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new NotImplementedException(); } + /// + public IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) + { + // min size 300 mb + const long MinPlayableSize = 314572800; + + // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size + // Once we reach a file that is at least the minimum, return all subsequent ones + var allVobs = _fileSystem.GetFiles(path, true) + .Where(file => string.Equals(file.Extension, ".vob", StringComparison.OrdinalIgnoreCase)) + .OrderBy(i => i.FullName) + .ToList(); + + // If we didn't find any satisfying the min length, just take them all + if (allVobs.Count == 0) + { + _logger.LogWarning("No vobs found in dvd structure."); + return Enumerable.Empty(); + } + + if (titleNumber.HasValue) + { + var prefix = string.Format( + CultureInfo.InvariantCulture, + titleNumber.Value >= 10 ? "VTS_{0}_" : "VTS_0{0}_", + titleNumber.Value); + var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (vobs.Count > 0) + { + var minSizeVobs = vobs + .SkipWhile(f => f.Length < MinPlayableSize) + .ToList(); + + return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName); + } + + _logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path); + } + + var files = allVobs + .SkipWhile(f => f.Length < MinPlayableSize) + .ToList(); + + // If we didn't find any satisfying the min length, just take them all + if (files.Count == 0) + { + _logger.LogWarning("Vob size filter resulted in zero matches. Taking all vobs."); + files = allVobs; + } + + // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file + if (files.Count > 0) + { + var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_'); + + if (parts.Length == 3) + { + var title = parts[1]; + + files = files.TakeWhile(f => + { + var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); + + return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); + }).ToList(); + + // If this resulted in not getting any vobs, just take them all + if (files.Count == 0) + { + _logger.LogWarning("Vob filename filter resulted in zero matches. Taking all vobs."); + files = allVobs; + } + } + } + + return files.Select(i => i.FullName); + } + public bool CanExtractSubtitles(string codec) { // TODO is there ever a case when a subtitle can't be extracted?? -- cgit v1.2.3 From ddfdec7f46dd31d437dc6210658392fc7f894138 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 3 Feb 2023 20:03:55 +0100 Subject: Fix BD and DVD folder probing and playback --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 5 +- .../MediaEncoding/EncodingHelper.cs | 10 ++ .../MediaEncoding/IMediaEncoder.cs | 16 +++ .../Encoder/EncodingUtils.cs | 27 +++- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 146 +++++++++------------ .../MediaInfo/FFProbeVideoInfo.cs | 68 +++++----- 6 files changed, 147 insertions(+), 125 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 12960f87a..29cb3d2f9 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -525,7 +525,10 @@ public class TranscodingJobHelper : IDisposable if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); - await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); + if (state.VideoType != VideoType.Dvd) + { + await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); + } if (state.SubtitleStream.IsExternal && string.Equals(Path.GetExtension(state.SubtitleStream.Path), ".mks", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a844e6443..b3a1b2a99 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -535,6 +535,16 @@ namespace MediaBrowser.Controller.MediaEncoding { var mediaPath = state.MediaPath ?? string.Empty; + if (state.MediaSource.VideoType == VideoType.Dvd) + { + return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource); + } + + if (state.MediaSource.VideoType == VideoType.BluRay) + { + return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2TsFiles(state.MediaPath, null).ToList(), state.MediaSource); + } + return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index fe8e9063e..c34ce5d29 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -153,6 +153,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. string GetInputArgument(string inputFile, MediaSourceInfo mediaSource); + /// + /// Gets the input argument. + /// + /// The input files. + /// The mediaSource. + /// System.String. + string GetInputArgument(IReadOnlyList inputFiles, MediaSourceInfo mediaSource); + /// /// Gets the input argument for an external subtitle file. /// @@ -195,5 +203,13 @@ namespace MediaBrowser.Controller.MediaEncoding /// The title number to start with. /// A playlist. IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); + + /// + /// Gets the primary playlist of .m2ts files. + /// + /// The to the .m2ts files. + /// The title number to start with. + /// A playlist. + IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index d0ea0429b..0f202a90e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -1,7 +1,9 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.Encoder @@ -15,21 +17,38 @@ namespace MediaBrowser.MediaEncoding.Encoder return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFile); } - return GetConcatInputArgument(inputFile, inputPrefix); + return GetFileInputArgument(inputFile, inputPrefix); + } + + public static string GetInputArgument(string inputPrefix, IReadOnlyList inputFiles, MediaProtocol protocol) + { + if (protocol != MediaProtocol.File) + { + return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFiles[0]); + } + + return GetConcatInputArgument(inputFiles, inputPrefix); } /// /// Gets the concat input argument. /// - /// The input file. + /// The input files. /// The input prefix. /// System.String. - private static string GetConcatInputArgument(string inputFile, string inputPrefix) + private static string GetConcatInputArgument(IReadOnlyList inputFiles, string inputPrefix) { // Get all streams // If there's more than one we'll need to use the concat command + if (inputFiles.Count > 1) + { + var files = string.Join("|", inputFiles.Select(NormalizePath)); + + return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files); + } + // Determine the input path for video files - return GetFileInputArgument(inputFile, inputPrefix); + return GetFileInputArgument(inputFiles[0], inputPrefix); } /// diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4a795625d..df31ba83e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -51,6 +51,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; + private readonly IBlurayExaminer _blurayExaminer; private readonly IConfiguration _config; private readonly IServerConfigurationManager _serverConfig; private readonly string _startupOptionFFmpegPath; @@ -95,6 +96,7 @@ namespace MediaBrowser.MediaEncoding.Encoder ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, + IBlurayExaminer blurayExaminer, ILocalizationManager localization, IConfiguration config, IServerConfigurationManager serverConfig) @@ -102,6 +104,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = logger; _configurationManager = configurationManager; _fileSystem = fileSystem; + _blurayExaminer = blurayExaminer; _localization = localization; _config = config; _serverConfig = serverConfig; @@ -117,16 +120,22 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public string ProbePath => _ffprobePath; + /// public Version EncoderVersion => _ffmpegVersion; + /// public bool IsPkeyPauseSupported => _isPkeyPauseSupported; + /// public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; + /// public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD; + /// public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; + /// public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier; /// @@ -344,26 +353,31 @@ namespace MediaBrowser.MediaEncoding.Encoder _ffmpegVersion = validator.GetFFmpegVersion(); } + /// public bool SupportsEncoder(string encoder) { return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); } + /// public bool SupportsDecoder(string decoder) { return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } + /// public bool SupportsHwaccel(string hwaccel) { return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); } + /// public bool SupportsFilter(string filter) { return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase); } + /// public bool SupportsFilterWithOption(FilterOptionType option) { if (_filtersWithOption.TryGetValue((int)option, out var val)) @@ -394,24 +408,16 @@ namespace MediaBrowser.MediaEncoding.Encoder return true; } - /// - /// Gets the media info. - /// - /// The request. - /// The cancellation token. - /// Task. + /// public Task GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken) { var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - var inputFile = request.MediaSource.Path; - string analyzeDuration = string.Empty; string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty; if (request.MediaSource.AnalyzeDurationMs > 0) { - analyzeDuration = "-analyzeduration " + - (request.MediaSource.AnalyzeDurationMs * 1000).ToString(); + analyzeDuration = "-analyzeduration " + (request.MediaSource.AnalyzeDurationMs * 1000).ToString(); } else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration)) { @@ -419,7 +425,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } return GetMediaInfoInternal( - GetInputArgument(inputFile, request.MediaSource), + GetInputArgument(request.MediaSource.Path, request.MediaSource), request.MediaSource.Path, request.MediaSource.Protocol, extractChapters, @@ -429,36 +435,24 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken); } - /// - /// Gets the input argument. - /// - /// The input file. - /// The mediaSource. - /// System.String. - /// Unrecognized InputType. - public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) + /// + public string GetInputArgument(IReadOnlyList inputFiles, MediaSourceInfo mediaSource) { - var prefix = "file"; - if (mediaSource.VideoType == VideoType.BluRay - || mediaSource.IsoType == IsoType.BluRay) - { - prefix = "bluray"; - } + return EncodingUtils.GetInputArgument("file", inputFiles, mediaSource.Protocol); + } - return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol); + /// + public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) + { + return EncodingUtils.GetInputArgument("file", new List() { inputFile }, mediaSource.Protocol); } - /// - /// Gets the input argument for an external subtitle file. - /// - /// The input file. - /// System.String. - /// Unrecognized InputType. + /// public string GetExternalSubtitleInputArgument(string inputFile) { const string Prefix = "file"; - return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File); + return EncodingUtils.GetInputArgument(Prefix, new List { inputFile }, MediaProtocol.File); } /// @@ -549,6 +543,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// public Task ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) { var mediaSource = new MediaSourceInfo @@ -559,11 +554,13 @@ namespace MediaBrowser.MediaEncoding.Encoder return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken); } + /// public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ImageFormat.Jpg, cancellationToken); } + /// public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken) { return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, targetFormat, cancellationToken); @@ -767,6 +764,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// public string GetTimeParameter(long ticks) { var time = TimeSpan.FromTicks(ticks); @@ -868,80 +866,56 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) { - // min size 300 mb - const long MinPlayableSize = 314572800; - - // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size - // Once we reach a file that is at least the minimum, return all subsequent ones + // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title VOBs ending with _0.VOB var allVobs = _fileSystem.GetFiles(path, true) - .Where(file => string.Equals(file.Extension, ".vob", StringComparison.OrdinalIgnoreCase)) + .Where(file => string.Equals(file.Extension, ".VOB", StringComparison.OrdinalIgnoreCase)) + .Where(file => !string.Equals(file.Name, "VIDEO_TS.VOB", StringComparison.OrdinalIgnoreCase)) + .Where(file => !file.Name.EndsWith("_0.VOB", StringComparison.OrdinalIgnoreCase)) .OrderBy(i => i.FullName) .ToList(); - // If we didn't find any satisfying the min length, just take them all - if (allVobs.Count == 0) - { - _logger.LogWarning("No vobs found in dvd structure."); - return Enumerable.Empty(); - } - if (titleNumber.HasValue) { - var prefix = string.Format( - CultureInfo.InvariantCulture, - titleNumber.Value >= 10 ? "VTS_{0}_" : "VTS_0{0}_", - titleNumber.Value); + var prefix = string.Format(CultureInfo.InvariantCulture, "VTS_{0:D2}_", titleNumber.Value); var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); if (vobs.Count > 0) { - var minSizeVobs = vobs - .SkipWhile(f => f.Length < MinPlayableSize) - .ToList(); - - return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName); + return vobs.Select(i => i.FullName); } - _logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path); + _logger.LogWarning("Could not determine VOB file list for title {Title} of {Path}.", titleNumber, path); } - var files = allVobs - .SkipWhile(f => f.Length < MinPlayableSize) + // Check for multiple big titles (> 900 MB) + var titles = allVobs + .Where(vob => vob.Length >= 900 * 1024 * 1024) + .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1]) + .GroupBy(x => x) + .Select(y => y.First()) .ToList(); - // If we didn't find any satisfying the min length, just take them all - if (files.Count == 0) + // Fall back to first title if no big title is found + if (titles.FirstOrDefault() == null) { - _logger.LogWarning("Vob size filter resulted in zero matches. Taking all vobs."); - files = allVobs; + titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).Split('_')[1]); } - // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file - if (files.Count > 0) - { - var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_'); - - if (parts.Length == 3) - { - var title = parts[1]; - - files = files.TakeWhile(f => - { - var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); - - return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); - }).ToList(); + // Aggregate all VOBs of the titles + return allVobs + .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1])) + .Select(i => i.FullName) + .ToList(); + } - // If this resulted in not getting any vobs, just take them all - if (files.Count == 0) - { - _logger.LogWarning("Vob filename filter resulted in zero matches. Taking all vobs."); - files = allVobs; - } - } - } + public IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber) + { + var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; + var directoryFiles = _fileSystem.GetFiles(path + "/BDMV/STREAM/"); - return files.Select(i => i.FullName); + return directoryFiles + .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) + .Select(f => f.FullName); } public bool CanExtractSubtitles(string codec) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 81434b862..393e1f22a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -90,36 +90,49 @@ namespace MediaBrowser.Providers.MediaInfo if (!item.IsShortcut || options.EnableRemoteContentProbe) { - string[] streamFileNames = null; - if (item.VideoType == VideoType.Dvd) { - streamFileNames = FetchFromDvdLib(item); + // Fetch metadata of first VOB + var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null).ToList(); + mediaInfoResult = await GetMediaInfo( + new Video + { + Path = vobs.First() + }, + cancellationToken).ConfigureAwait(false); - if (streamFileNames.Length == 0) + // Remove first VOB + vobs.RemoveAt(0); + + // Add runtime from all other VOBs + foreach (var vob in vobs) { - _logger.LogError("No playable vobs found in dvd structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; + var tmpMediaInfo = await GetMediaInfo( + new Video + { + Path = vob + }, + cancellationToken).ConfigureAwait(false); + + mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks; } } - else if (item.VideoType == VideoType.BluRay) + else { - var inputPath = item.Path; - - blurayDiscInfo = GetBDInfo(inputPath); - - streamFileNames = blurayDiscInfo.Files; - - if (streamFileNames.Length == 0) + if (item.VideoType == VideoType.BluRay) { - _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } - } + blurayDiscInfo = GetBDInfo(item.Path); - streamFileNames ??= Array.Empty(); + if (blurayDiscInfo.Files.Length == 0) + { + _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); + return ItemUpdateType.MetadataImport; + } + } - mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); + mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } cancellationToken.ThrowIfCancellationRequested(); } @@ -189,19 +202,8 @@ namespace MediaBrowser.Providers.MediaInfo } mediaAttachments = mediaInfo.MediaAttachments; - video.TotalBitrate = mediaInfo.Bitrate; - // video.FormatName = (mediaInfo.Container ?? string.Empty) - // .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); - - // For DVDs this may not always be accurate, so don't set the runtime if the item already has one - var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks is null || video.RunTimeTicks.Value == 0; - - if (needToSetRuntime) - { - video.RunTimeTicks = mediaInfo.RunTimeTicks; - } - + video.RunTimeTicks = mediaInfo.RunTimeTicks; video.Size = mediaInfo.Size; if (video.VideoType == VideoType.VideoFile) @@ -321,8 +323,6 @@ namespace MediaBrowser.Providers.MediaInfo { var video = (Video)item; - // video.PlayableStreamFileNames = blurayInfo.Files.ToList(); - // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output if (blurayInfo.Files.Length > 1) { -- cgit v1.2.3 From edf3909157a5ef10436b7ebf5717b36a6bac9e7c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 4 Feb 2023 00:08:51 +0100 Subject: Use FFmpeg concat for DVD and BD folder playback --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 5 +++ .../MediaEncoding/EncodingHelper.cs | 14 ++++++-- .../MediaEncoding/IMediaEncoder.cs | 7 ++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 40 ++++++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 29cb3d2f9..cb73ad765 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -323,6 +323,11 @@ public class TranscodingJobHelper : IDisposable if (delete(job.Path!)) { await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); + if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) + { + var path = Path.GetDirectoryName(job.Path) + "/" + job.MediaSource.Id + ".concat"; + File.Delete(path); + } } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b3a1b2a99..7c4892ea5 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -941,8 +941,18 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append(canvasArgs); } - arg.Append(" -i ") - .Append(GetInputPathArgument(state)); + if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) + { + var tmpConcatPath = options.TranscodingTempPath + "/" + state.MediaSource.Id + ".concat"; + _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); + arg.Append(" -f concat -safe 0 "); + arg.Append(" -i " + tmpConcatPath + " "); + } + else + { + arg.Append(" -i ") + .Append(GetInputPathArgument(state)); + } // sub2video for external graphical subtitles if (state.SubtitleStream is not null diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index c34ce5d29..716adc8f0 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -211,5 +211,12 @@ namespace MediaBrowser.Controller.MediaEncoding /// The title number to start with. /// A playlist. IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber); + + /// + /// Generates a FFmpeg concat config for the source. + /// + /// The . + /// The path the config should be written to. + void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index df31ba83e..f4e6ea428 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -918,6 +918,46 @@ namespace MediaBrowser.MediaEncoding.Encoder .Select(f => f.FullName); } + public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) + { + var files = new List(); + var videoType = source.VideoType; + if (videoType == VideoType.Dvd) + { + files = GetPrimaryPlaylistVobFiles(source.Path, null).ToList(); + } + else if (videoType == VideoType.BluRay) + { + files = GetPrimaryPlaylistM2TsFiles(source.Path, null).ToList(); + } + + var lines = new List(); + + foreach (var path in files) + { + var fileinfo = _fileSystem.GetFileInfo(path); + var mediaInfoResult = GetMediaInfo( + new MediaInfoRequest + { + MediaType = DlnaProfileType.Video, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File, + VideoType = videoType + } + }, + CancellationToken.None).GetAwaiter().GetResult(); + + var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; + + lines.Add("file " + "'" + path + "'"); + lines.Add("duration " + duration); + } + + File.WriteAllLinesAsync(concatFilePath, lines, CancellationToken.None).GetAwaiter().GetResult(); + } + public bool CanExtractSubtitles(string codec) { // TODO is there ever a case when a subtitle can't be extracted?? -- cgit v1.2.3 From d89cd188eb015cff3450e3c4f7f1e48adec52e8c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 4 Feb 2023 12:33:22 +0100 Subject: Fix BD ISO playback --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index f4e6ea428..0bf89ec47 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -444,7 +444,13 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) { - return EncodingUtils.GetInputArgument("file", new List() { inputFile }, mediaSource.Protocol); + var prefix = "file"; + if (mediaSource.IsoType == IsoType.BluRay) + { + prefix = "bluray"; + } + + return EncodingUtils.GetInputArgument(prefix, new List() { inputFile }, mediaSource.Protocol); } /// -- cgit v1.2.3 From f2b7f664aa9b3ade38a2402faf95ba9b6989fc41 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 4 Feb 2023 20:16:45 +0100 Subject: Apply review suggestions --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 +++++--- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index cb73ad765..1ba739550 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -325,7 +325,7 @@ public class TranscodingJobHelper : IDisposable await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) { - var path = Path.GetDirectoryName(job.Path) + "/" + job.MediaSource.Id + ".concat"; + var path = Path.Join(job.Path, "/" + job.MediaSource.Id + ".concat"); File.Delete(path); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 7c4892ea5..075e33cb8 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -943,10 +943,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var tmpConcatPath = options.TranscodingTempPath + "/" + state.MediaSource.Id + ".concat"; + var tmpConcatPath = Path.Join(options.TranscodingTempPath, "/" + state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); - arg.Append(" -f concat -safe 0 "); - arg.Append(" -i " + tmpConcatPath + " "); + arg.Append(" -f concat -safe 0 ") + .Append(" -i ") + .Append(tmpConcatPath) + .Append(' '); } else { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 0bf89ec47..347e1de50 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -450,7 +450,7 @@ namespace MediaBrowser.MediaEncoding.Encoder prefix = "bluray"; } - return EncodingUtils.GetInputArgument(prefix, new List() { inputFile }, mediaSource.Protocol); + return EncodingUtils.GetInputArgument(prefix, new[] { inputFile }, mediaSource.Protocol); } /// @@ -458,7 +458,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { const string Prefix = "file"; - return EncodingUtils.GetInputArgument(Prefix, new List { inputFile }, MediaProtocol.File); + return EncodingUtils.GetInputArgument(Prefix, new[] { inputFile }, MediaProtocol.File); } /// -- cgit v1.2.3 From 2403a0a36792d060d913abbd86bec03816da750b Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 5 Feb 2023 17:24:13 +0100 Subject: Apply review suggestions --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 12 ++-- MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 - .../MediaInfo/FFProbeVideoInfo.cs | 81 +++++++++++----------- 5 files changed, 48 insertions(+), 50 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 1ba739550..3bb3ad358 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -325,7 +325,7 @@ public class TranscodingJobHelper : IDisposable await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) { - var path = Path.Join(job.Path, "/" + job.MediaSource.Id + ".concat"); + var path = Path.Join(job.Path, job.MediaSource.Id + ".concat"); File.Delete(path); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 075e33cb8..3789bcac9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -943,7 +943,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var tmpConcatPath = Path.Join(options.TranscodingTempPath, "/" + state.MediaSource.Id + ".concat"); + var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); arg.Append(" -f concat -safe 0 ") .Append(" -i ") diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 347e1de50..49c81923b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common; @@ -896,7 +897,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Check for multiple big titles (> 900 MB) var titles = allVobs .Where(vob => vob.Length >= 900 * 1024 * 1024) - .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1]) + .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()) .GroupBy(x => x) .Select(y => y.First()) .ToList(); @@ -904,12 +905,12 @@ namespace MediaBrowser.MediaEncoding.Encoder // Fall back to first title if no big title is found if (titles.FirstOrDefault() == null) { - titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).Split('_')[1]); + titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); } // Aggregate all VOBs of the titles return allVobs - .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1])) + .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString())) .Select(i => i.FullName) .ToList(); } @@ -917,7 +918,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber) { var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; - var directoryFiles = _fileSystem.GetFiles(path + "/BDMV/STREAM/"); + var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM")); return directoryFiles .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) @@ -941,7 +942,6 @@ namespace MediaBrowser.MediaEncoding.Encoder foreach (var path in files) { - var fileinfo = _fileSystem.GetFileInfo(path); var mediaInfoResult = GetMediaInfo( new MediaInfoRequest { @@ -961,7 +961,7 @@ namespace MediaBrowser.MediaEncoding.Encoder lines.Add("duration " + duration); } - File.WriteAllLinesAsync(concatFilePath, lines, CancellationToken.None).GetAwaiter().GetResult(); + File.WriteAllLines(concatFilePath, lines); } public bool CanExtractSubtitles(string codec) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index f6c930f5c..ef73096b4 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -639,7 +639,6 @@ namespace MediaBrowser.Model.Dlna if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay) { isEligibleForDirectPlay = false; - isEligibleForDirectStream = false; } if (bitrateLimitExceeded) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 0ec7b368b..f75de47f3 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -328,54 +328,56 @@ namespace MediaBrowser.Providers.MediaInfo { var video = (Video)item; - // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output - if (blurayInfo.Files.Length > 1) + if (blurayInfo.Files.Length <= 1) { - int? currentHeight = null; - int? currentWidth = null; - int? currentBitRate = null; + return; + } - var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output + int? currentHeight = null; + int? currentWidth = null; + int? currentBitRate = null; - // Grab the values that ffprobe recorded - if (videoStream is not null) - { - currentBitRate = videoStream.BitRate; - currentWidth = videoStream.Width; - currentHeight = videoStream.Height; - } + var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); - // Fill video properties from the BDInfo result - mediaStreams.Clear(); - mediaStreams.AddRange(blurayInfo.MediaStreams); + // Grab the values that ffprobe recorded + if (videoStream is not null) + { + currentBitRate = videoStream.BitRate; + currentWidth = videoStream.Width; + currentHeight = videoStream.Height; + } - if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0) - { - video.RunTimeTicks = blurayInfo.RunTimeTicks; - } + // Fill video properties from the BDInfo result + mediaStreams.Clear(); + mediaStreams.AddRange(blurayInfo.MediaStreams); - if (blurayInfo.Chapters is not null) + if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0) + { + video.RunTimeTicks = blurayInfo.RunTimeTicks; + } + + if (blurayInfo.Chapters is not null) + { + double[] brChapter = blurayInfo.Chapters; + chapters = new ChapterInfo[brChapter.Length]; + for (int i = 0; i < brChapter.Length; i++) { - double[] brChapter = blurayInfo.Chapters; - chapters = new ChapterInfo[brChapter.Length]; - for (int i = 0; i < brChapter.Length; i++) + chapters[i] = new ChapterInfo { - chapters[i] = new ChapterInfo - { - StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks - }; - } + StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks + }; } + } - videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); - // Use the ffprobe values if these are empty - if (videoStream is not null) - { - videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; - videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; - videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; - } + // Use the ffprobe values if these are empty + if (videoStream is not null) + { + videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; + videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; + videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; } } @@ -391,10 +393,7 @@ namespace MediaBrowser.Providers.MediaInfo /// VideoStream. private BlurayDiscInfo GetBDInfo(string path) { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } + ArgumentException.ThrowIfNullOrEmpty(path); try { -- cgit v1.2.3 From cd852d43c16f0e038ba547053bd4c80794ba990c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 5 Feb 2023 22:59:58 +0100 Subject: Add more comments and logging, streamline code --- .../MediaEncoding/EncodingHelper.cs | 2 +- .../MediaEncoding/IMediaEncoder.cs | 3 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 21 +++++++++---- .../MediaInfo/FFProbeVideoInfo.cs | 35 +++++++++++++++------- 4 files changed, 42 insertions(+), 19 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 3789bcac9..2b39c74e2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -542,7 +542,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.BluRay) { - return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2TsFiles(state.MediaPath, null).ToList(), state.MediaSource); + return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource); } return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 716adc8f0..83be267a7 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -208,9 +208,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets the primary playlist of .m2ts files. /// /// The to the .m2ts files. - /// The title number to start with. /// A playlist. - IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber); + IEnumerable GetPrimaryPlaylistM2tsFiles(string path); /// /// Generates a FFmpeg concat config for the source. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 49c81923b..05ea7a86d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -873,7 +873,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) { - // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title VOBs ending with _0.VOB + // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title .vob files ending with _0.VOB var allVobs = _fileSystem.GetFiles(path, true) .Where(file => string.Equals(file.Extension, ".VOB", StringComparison.OrdinalIgnoreCase)) .Where(file => !string.Equals(file.Name, "VIDEO_TS.VOB", StringComparison.OrdinalIgnoreCase)) @@ -891,7 +891,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return vobs.Select(i => i.FullName); } - _logger.LogWarning("Could not determine VOB file list for title {Title} of {Path}.", titleNumber, path); + _logger.LogWarning("Could not determine .vob files for title {Title} of {Path}.", titleNumber, path); } // Check for multiple big titles (> 900 MB) @@ -908,18 +908,22 @@ namespace MediaBrowser.MediaEncoding.Encoder titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); } - // Aggregate all VOBs of the titles + // Aggregate all .vob files of the titles return allVobs .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString())) .Select(i => i.FullName) .ToList(); } - public IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber) + public IEnumerable GetPrimaryPlaylistM2tsFiles(string path) { + // Get all playable .m2ts files var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; + + // Get all files from the BDMV/STREAMING directory var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM")); + // Only return playable local .m2ts files return directoryFiles .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) .Select(f => f.FullName); @@ -927,6 +931,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) { + // Get all playable files var files = new List(); var videoType = source.VideoType; if (videoType == VideoType.Dvd) @@ -935,11 +940,11 @@ namespace MediaBrowser.MediaEncoding.Encoder } else if (videoType == VideoType.BluRay) { - files = GetPrimaryPlaylistM2TsFiles(source.Path, null).ToList(); + files = GetPrimaryPlaylistM2tsFiles(source.Path).ToList(); } + // Generate concat configuration entries for each file var lines = new List(); - foreach (var path in files) { var mediaInfoResult = GetMediaInfo( @@ -957,10 +962,14 @@ namespace MediaBrowser.MediaEncoding.Encoder var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; + // Add file path stanza to concat configuration lines.Add("file " + "'" + path + "'"); + + // Add duration stanza to concat configuration lines.Add("duration " + duration); } + // Write concat configuration File.WriteAllLines(concatFilePath, lines); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index f75de47f3..7200d674c 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -91,8 +91,17 @@ namespace MediaBrowser.Providers.MediaInfo { if (item.VideoType == VideoType.Dvd) { - // Fetch metadata of first VOB + // Get list of playable .vob files var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null).ToList(); + + // Return if no playable .vob files are found + if (vobs.Count == 0) + { + _logger.LogError("No playable .vob files found in DVD structure, skipping FFprobe."); + return ItemUpdateType.MetadataImport; + } + + // Fetch metadata of first .vob file mediaInfoResult = await GetMediaInfo( new Video { @@ -100,10 +109,10 @@ namespace MediaBrowser.Providers.MediaInfo }, cancellationToken).ConfigureAwait(false); - // Remove first VOB + // Remove first .vob file vobs.RemoveAt(0); - // Add runtime from all other VOBs + // Sum up the runtime of all .vob files foreach (var vob in vobs) { var tmpMediaInfo = await GetMediaInfo( @@ -118,20 +127,26 @@ namespace MediaBrowser.Providers.MediaInfo } else if (item.VideoType == VideoType.BluRay) { + // Get BD disc information blurayDiscInfo = GetBDInfo(item.Path); - var m2ts = _mediaEncoder.GetPrimaryPlaylistM2TsFiles(item.Path, null).ToList(); + + // Get playable .m2ts files + var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path).ToList(); + + // Return if no playable .m2ts files are found + if (blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0) + { + _logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe."); + return ItemUpdateType.MetadataImport; + } + + // Fetch metadata of first .m2ts file mediaInfoResult = await GetMediaInfo( new Video { Path = m2ts.First() }, cancellationToken).ConfigureAwait(false); - - if (blurayDiscInfo.Files.Length == 0) - { - _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe."); - return ItemUpdateType.MetadataImport; - } } else { -- cgit v1.2.3 From 0da5255f1291ba510f829d36a3ca1a9eb65590dc Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 18 Feb 2023 14:42:35 +0100 Subject: Apply review suggestions --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 23 +- .../MediaEncoding/IMediaEncoder.cs | 4 +- .../BdInfo/BdInfoDirectoryInfo.cs | 160 +++++++----- .../BdInfo/BdInfoExaminer.cs | 283 ++++++++++----------- .../BdInfo/BdInfoFileInfo.cs | 81 ++++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 75 +++--- MediaBrowser.Model/Dlna/StreamInfo.cs | 7 +- MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs | 56 ++-- MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs | 21 +- .../MediaInfo/FFProbeVideoInfo.cs | 30 +-- 11 files changed, 395 insertions(+), 347 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index ee210117e..73e8f34ad 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -329,7 +329,7 @@ public class TranscodingJobHelper : IDisposable if (File.Exists(concatFilePath)) { _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); - _fileSystem.DeleteFile(concatFilePath); + File.Delete(concatFilePath); } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2b39c74e2..a4e4648b1 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -533,19 +533,12 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetInputPathArgument(EncodingJobInfo state) { - var mediaPath = state.MediaPath ?? string.Empty; - - if (state.MediaSource.VideoType == VideoType.Dvd) + return state.MediaSource.VideoType switch { - return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource); - } - - if (state.MediaSource.VideoType == VideoType.BluRay) - { - return _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource); - } - - return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); + VideoType.Dvd => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource), + VideoType.BluRay => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource), + _ => _mediaEncoder.GetInputArgument(state.MediaPath, state.MediaSource) + }; } /// @@ -945,10 +938,8 @@ namespace MediaBrowser.Controller.MediaEncoding { var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); - arg.Append(" -f concat -safe 0 ") - .Append(" -i ") - .Append(tmpConcatPath) - .Append(' '); + arg.Append(" -f concat -safe 0 -i ") + .Append(tmpConcatPath); } else { diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 83be267a7..f830b9f29 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -202,14 +202,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// The to the .vob files. /// The title number to start with. /// A playlist. - IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); + IReadOnlyList GetPrimaryPlaylistVobFiles(string path, uint? titleNumber); /// /// Gets the primary playlist of .m2ts files. /// /// The to the .m2ts files. /// A playlist. - IEnumerable GetPrimaryPlaylistM2tsFiles(string path); + IReadOnlyList GetPrimaryPlaylistM2tsFiles(string path); /// /// Generates a FFmpeg concat config for the source. diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs index 7e026b42e..ea520b1d6 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -1,83 +1,123 @@ -#pragma warning disable CS1591 - using System; +using System.IO; using System.Linq; using BDInfo.IO; using MediaBrowser.Model.IO; -namespace MediaBrowser.MediaEncoding.BdInfo +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// +/// Class BdInfoDirectoryInfo. +/// +public class BdInfoDirectoryInfo : IDirectoryInfo { - public class BdInfoDirectoryInfo : IDirectoryInfo - { - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; - private readonly FileSystemMetadata _impl; + private readonly FileSystemMetadata _impl; - public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) - { - _fileSystem = fileSystem; - _impl = _fileSystem.GetDirectoryInfo(path); - } + /// + /// Initializes a new instance of the class. + /// + /// The filesystem. + /// The path. + public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) + { + _fileSystem = fileSystem; + _impl = _fileSystem.GetDirectoryInfo(path); + } - private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl) - { - _fileSystem = fileSystem; - _impl = impl; - } + private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl) + { + _fileSystem = fileSystem; + _impl = impl; + } - public string Name => _impl.Name; + /// + /// Gets the name. + /// + public string Name => _impl.Name; - public string FullName => _impl.FullName; + /// + /// Gets the full name. + /// + public string FullName => _impl.FullName; - public IDirectoryInfo? Parent + /// + /// Gets the parent directory information. + /// + public IDirectoryInfo? Parent + { + get { - get + var parentFolder = Path.GetDirectoryName(_impl.FullName); + if (parentFolder is not null) { - var parentFolder = System.IO.Path.GetDirectoryName(_impl.FullName); - if (parentFolder is not null) - { - return new BdInfoDirectoryInfo(_fileSystem, parentFolder); - } - - return null; + return new BdInfoDirectoryInfo(_fileSystem, parentFolder); } - } - public IDirectoryInfo[] GetDirectories() - { - return Array.ConvertAll( - _fileSystem.GetDirectories(_impl.FullName).ToArray(), - x => new BdInfoDirectoryInfo(_fileSystem, x)); + return null; } + } - public IFileInfo[] GetFiles() - { - return Array.ConvertAll( - _fileSystem.GetFiles(_impl.FullName).ToArray(), - x => new BdInfoFileInfo(x)); - } + /// + /// Gets the directories. + /// + /// An array with all directories. + public IDirectoryInfo[] GetDirectories() + { + return _fileSystem.GetDirectories(_impl.FullName) + .Select(x => new BdInfoDirectoryInfo(_fileSystem, x)) + .ToArray(); + } - public IFileInfo[] GetFiles(string searchPattern) - { - return Array.ConvertAll( - _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(), - x => new BdInfoFileInfo(x)); - } + /// + /// Gets the files. + /// + /// All files of the directory. + public IFileInfo[] GetFiles() + { + return _fileSystem.GetFiles(_impl.FullName) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } - public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption) - { - return Array.ConvertAll( - _fileSystem.GetFiles( - _impl.FullName, - new[] { searchPattern }, - false, - (searchOption & System.IO.SearchOption.AllDirectories) == System.IO.SearchOption.AllDirectories).ToArray(), - x => new BdInfoFileInfo(x)); - } + /// + /// Gets the files matching a pattern. + /// + /// The search pattern. + /// All files of the directory matchign the search pattern. + public IFileInfo[] GetFiles(string searchPattern) + { + return _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } - public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path) - { - return new BdInfoDirectoryInfo(fs, path); - } + /// + /// Gets the files matching a pattern and search options. + /// + /// The search pattern. + /// The search optin. + /// All files of the directory matchign the search pattern and options. + public IFileInfo[] GetFiles(string searchPattern, SearchOption searchOption) + { + return _fileSystem.GetFiles( + _impl.FullName, + new[] { searchPattern }, + false, + (searchOption & SearchOption.AllDirectories) == SearchOption.AllDirectories) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } + + /// + /// Gets the bdinfo of a file system path. + /// + /// The file system. + /// The path. + /// The BD directory information of the path on the file system. + public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path) + { + return new BdInfoDirectoryInfo(fs, path); } } diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index 3e53cbf29..8ebb59c59 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -6,189 +6,182 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -namespace MediaBrowser.MediaEncoding.BdInfo +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// +/// Class BdInfoExaminer. +/// +public class BdInfoExaminer : IBlurayExaminer { + private readonly IFileSystem _fileSystem; + /// - /// Class BdInfoExaminer. + /// Initializes a new instance of the class. /// - public class BdInfoExaminer : IBlurayExaminer + /// The filesystem. + public BdInfoExaminer(IFileSystem fileSystem) { - private readonly IFileSystem _fileSystem; + _fileSystem = fileSystem; + } - /// - /// Initializes a new instance of the class. - /// - /// The filesystem. - public BdInfoExaminer(IFileSystem fileSystem) + /// + /// Gets the disc info. + /// + /// The path. + /// BlurayDiscInfo. + public BlurayDiscInfo GetDiscInfo(string path) + { + if (string.IsNullOrWhiteSpace(path)) { - _fileSystem = fileSystem; + throw new ArgumentNullException(nameof(path)); } - /// - /// Gets the disc info. - /// - /// The path. - /// BlurayDiscInfo. - public BlurayDiscInfo GetDiscInfo(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path)); + var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path)); - bdrom.Scan(); + bdrom.Scan(); - // Get the longest playlist - var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); + // Get the longest playlist + var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); - var outputStream = new BlurayDiscInfo - { - MediaStreams = Array.Empty() - }; + var outputStream = new BlurayDiscInfo + { + MediaStreams = Array.Empty() + }; - if (playlist is null) - { - return outputStream; - } + if (playlist is null) + { + return outputStream; + } - outputStream.Chapters = playlist.Chapters.ToArray(); + outputStream.Chapters = playlist.Chapters.ToArray(); - outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; + outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; - var mediaStreams = new List(); + var sortedStreams = playlist.SortedStreams; + var mediaStreams = new List(sortedStreams.Count); - foreach (var stream in playlist.SortedStreams) + foreach (var stream in sortedStreams) + { + switch (stream) { - if (stream is TSVideoStream videoStream) - { + case TSVideoStream videoStream: AddVideoStream(mediaStreams, videoStream); - continue; - } - - if (stream is TSAudioStream audioStream) - { + break; + case TSAudioStream audioStream: AddAudioStream(mediaStreams, audioStream); - continue; - } - - if (stream is TSTextStream textStream) - { + break; + case TSTextStream textStream: AddSubtitleStream(mediaStreams, textStream); - continue; - } - - if (stream is TSGraphicsStream graphicsStream) - { - AddSubtitleStream(mediaStreams, graphicsStream); - } + break; + case TSGraphicsStream graphicStream: + AddSubtitleStream(mediaStreams, graphicStream); + break; } + } - outputStream.MediaStreams = mediaStreams.ToArray(); - - outputStream.PlaylistName = playlist.Name; + outputStream.MediaStreams = mediaStreams.ToArray(); - if (playlist.StreamClips is not null && playlist.StreamClips.Any()) - { - // Get the files in the playlist - outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray(); - } + outputStream.PlaylistName = playlist.Name; - return outputStream; + if (playlist.StreamClips is not null && playlist.StreamClips.Count > 0) + { + // Get the files in the playlist + outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray(); } - /// - /// Adds the video stream. - /// - /// The streams. - /// The video stream. - private void AddVideoStream(List streams, TSVideoStream videoStream) - { - var mediaStream = new MediaStream - { - BitRate = Convert.ToInt32(videoStream.BitRate), - Width = videoStream.Width, - Height = videoStream.Height, - Codec = videoStream.CodecShortName, - IsInterlaced = videoStream.IsInterlaced, - Type = MediaStreamType.Video, - Index = streams.Count - }; - - if (videoStream.FrameRateDenominator > 0) - { - float frameRateEnumerator = videoStream.FrameRateEnumerator; - float frameRateDenominator = videoStream.FrameRateDenominator; + return outputStream; + } - mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; - } + /// + /// Adds the video stream. + /// + /// The streams. + /// The video stream. + private void AddVideoStream(List streams, TSVideoStream videoStream) + { + var mediaStream = new MediaStream + { + BitRate = Convert.ToInt32(videoStream.BitRate), + Width = videoStream.Width, + Height = videoStream.Height, + Codec = videoStream.CodecShortName, + IsInterlaced = videoStream.IsInterlaced, + Type = MediaStreamType.Video, + Index = streams.Count + }; + + if (videoStream.FrameRateDenominator > 0) + { + float frameRateEnumerator = videoStream.FrameRateEnumerator; + float frameRateDenominator = videoStream.FrameRateDenominator; - streams.Add(mediaStream); + mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; } - /// - /// Adds the audio stream. - /// - /// The streams. - /// The audio stream. - private void AddAudioStream(List streams, TSAudioStream audioStream) - { - var stream = new MediaStream - { - Codec = audioStream.CodecShortName, - Language = audioStream.LanguageCode, - Channels = audioStream.ChannelCount, - SampleRate = audioStream.SampleRate, - Type = MediaStreamType.Audio, - Index = streams.Count - }; - - var bitrate = Convert.ToInt32(audioStream.BitRate); + streams.Add(mediaStream); + } - if (bitrate > 0) - { - stream.BitRate = bitrate; - } + /// + /// Adds the audio stream. + /// + /// The streams. + /// The audio stream. + private void AddAudioStream(List streams, TSAudioStream audioStream) + { + var stream = new MediaStream + { + Codec = audioStream.CodecShortName, + Language = audioStream.LanguageCode, + Channels = audioStream.ChannelCount, + SampleRate = audioStream.SampleRate, + Type = MediaStreamType.Audio, + Index = streams.Count + }; - if (audioStream.LFE > 0) - { - stream.Channels = audioStream.ChannelCount + 1; - } + var bitrate = Convert.ToInt32(audioStream.BitRate); - streams.Add(stream); + if (bitrate > 0) + { + stream.BitRate = bitrate; } - /// - /// Adds the subtitle stream. - /// - /// The streams. - /// The text stream. - private void AddSubtitleStream(List streams, TSTextStream textStream) + if (audioStream.LFE > 0) { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); + stream.Channels = audioStream.ChannelCount + 1; } - /// - /// Adds the subtitle stream. - /// - /// The streams. - /// The text stream. - private void AddSubtitleStream(List streams, TSGraphicsStream textStream) + streams.Add(stream); + } + + /// + /// Adds the subtitle stream. + /// + /// The streams. + /// The text stream. + private void AddSubtitleStream(List streams, TSTextStream textStream) + { + streams.Add(new MediaStream + { + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); + } + + /// + /// Adds the subtitle stream. + /// + /// The streams. + /// The text stream. + private void AddSubtitleStream(List streams, TSGraphicsStream textStream) + { + streams.Add(new MediaStream { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); - } + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); } } diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs index d55688e3d..9e7a1d50a 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -1,41 +1,68 @@ -#pragma warning disable CS1591 - using System.IO; using MediaBrowser.Model.IO; -namespace MediaBrowser.MediaEncoding.BdInfo +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// +/// Class BdInfoFileInfo. +/// +public class BdInfoFileInfo : BDInfo.IO.IFileInfo { - public class BdInfoFileInfo : BDInfo.IO.IFileInfo - { - private FileSystemMetadata _impl; + private FileSystemMetadata _impl; - public BdInfoFileInfo(FileSystemMetadata impl) - { - _impl = impl; - } + /// + /// Initializes a new instance of the class. + /// + /// The . + public BdInfoFileInfo(FileSystemMetadata impl) + { + _impl = impl; + } - public string Name => _impl.Name; + /// + /// Gets the name. + /// + public string Name => _impl.Name; - public string FullName => _impl.FullName; + /// + /// Gets the full name. + /// + public string FullName => _impl.FullName; - public string Extension => _impl.Extension; + /// + /// Gets the extension. + /// + public string Extension => _impl.Extension; - public long Length => _impl.Length; + /// + /// Gets the length. + /// + public long Length => _impl.Length; - public bool IsDir => _impl.IsDirectory; + /// + /// Gets a value indicating whether this is a directory. + /// + public bool IsDir => _impl.IsDirectory; - public Stream OpenRead() - { - return new FileStream( - FullName, - FileMode.Open, - FileAccess.Read, - FileShare.Read); - } + /// + /// Gets a file as file stream. + /// + /// A for the file. + public Stream OpenRead() + { + return new FileStream( + FullName, + FileMode.Open, + FileAccess.Read, + FileShare.Read); + } - public StreamReader OpenText() - { - return new StreamReader(OpenRead()); - } + /// + /// Gets a files's content with a stream reader. + /// + /// A for the file's content. + public StreamReader OpenText() + { + return new StreamReader(OpenRead()); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 05ea7a86d..d2112e5dc 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -871,7 +871,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } /// - public IEnumerable GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) + public IReadOnlyList GetPrimaryPlaylistVobFiles(string path, uint? titleNumber) { // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title .vob files ending with _0.VOB var allVobs = _fileSystem.GetFiles(path, true) @@ -888,7 +888,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (vobs.Count > 0) { - return vobs.Select(i => i.FullName); + return vobs.Select(i => i.FullName).ToList(); } _logger.LogWarning("Could not determine .vob files for title {Title} of {Path}.", titleNumber, path); @@ -898,12 +898,11 @@ namespace MediaBrowser.MediaEncoding.Encoder var titles = allVobs .Where(vob => vob.Length >= 900 * 1024 * 1024) .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()) - .GroupBy(x => x) - .Select(y => y.First()) + .Distinct() .ToList(); // Fall back to first title if no big title is found - if (titles.FirstOrDefault() == null) + if (titles.Count == 0) { titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); } @@ -915,7 +914,8 @@ namespace MediaBrowser.MediaEncoding.Encoder .ToList(); } - public IEnumerable GetPrimaryPlaylistM2tsFiles(string path) + /// + public IReadOnlyList GetPrimaryPlaylistM2tsFiles(string path) { // Get all playable .m2ts files var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; @@ -926,51 +926,56 @@ namespace MediaBrowser.MediaEncoding.Encoder // Only return playable local .m2ts files return directoryFiles .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) - .Select(f => f.FullName); + .Select(f => f.FullName) + .ToList(); } + /// public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) { // Get all playable files - var files = new List(); + IReadOnlyList files; var videoType = source.VideoType; if (videoType == VideoType.Dvd) { - files = GetPrimaryPlaylistVobFiles(source.Path, null).ToList(); + files = GetPrimaryPlaylistVobFiles(source.Path, null); } else if (videoType == VideoType.BluRay) { - files = GetPrimaryPlaylistM2tsFiles(source.Path).ToList(); + files = GetPrimaryPlaylistM2tsFiles(source.Path); + } + else + { + return; } - // Generate concat configuration entries for each file - var lines = new List(); - foreach (var path in files) + // Generate concat configuration entries for each file and write to file + using (StreamWriter sw = new StreamWriter(concatFilePath)) { - var mediaInfoResult = GetMediaInfo( - new MediaInfoRequest - { - MediaType = DlnaProfileType.Video, - MediaSource = new MediaSourceInfo + foreach (var path in files) + { + var mediaInfoResult = GetMediaInfo( + new MediaInfoRequest { - Path = path, - Protocol = MediaProtocol.File, - VideoType = videoType - } - }, - CancellationToken.None).GetAwaiter().GetResult(); - - var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; - - // Add file path stanza to concat configuration - lines.Add("file " + "'" + path + "'"); - - // Add duration stanza to concat configuration - lines.Add("duration " + duration); + MediaType = DlnaProfileType.Video, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File, + VideoType = videoType + } + }, + CancellationToken.None).GetAwaiter().GetResult(); + + var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; + + // Add file path stanza to concat configuration + sw.WriteLine("file '{0}'", path); + + // Add duration stanza to concat configuration + sw.WriteLine("duration {0}", duration); + } } - - // Write concat configuration - File.WriteAllLines(concatFilePath, lines); } public bool CanExtractSubtitles(string codec) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index fdf3afe97..0e814f036 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -107,11 +107,8 @@ namespace MediaBrowser.Model.Dlna public string MediaSourceId => MediaSource?.Id; - public bool IsDirectStream => - !(MediaSource?.VideoType == VideoType.Dvd - || MediaSource?.VideoType == VideoType.BluRay) - && (PlayMethod == PlayMethod.DirectStream - || PlayMethod == PlayMethod.DirectPlay); + public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) + && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; /// /// Gets the audio stream that will be used. diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs index 83f982a5c..d546ffccd 100644 --- a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs +++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs @@ -1,39 +1,41 @@ #nullable disable -#pragma warning disable CS1591 using MediaBrowser.Model.Entities; -namespace MediaBrowser.Model.MediaInfo +namespace MediaBrowser.Model.MediaInfo; + +/// +/// Represents the result of BDInfo output. +/// +public class BlurayDiscInfo { /// - /// Represents the result of BDInfo output. + /// Gets or sets the media streams. /// - public class BlurayDiscInfo - { - /// - /// Gets or sets the media streams. - /// - /// The media streams. - public MediaStream[] MediaStreams { get; set; } + /// The media streams. + public MediaStream[] MediaStreams { get; set; } - /// - /// Gets or sets the run time ticks. - /// - /// The run time ticks. - public long? RunTimeTicks { get; set; } + /// + /// Gets or sets the run time ticks. + /// + /// The run time ticks. + public long? RunTimeTicks { get; set; } - /// - /// Gets or sets the files. - /// - /// The files. - public string[] Files { get; set; } + /// + /// Gets or sets the files. + /// + /// The files. + public string[] Files { get; set; } - public string PlaylistName { get; set; } + /// + /// Gets or sets the playlist name. + /// + /// The playlist name. + public string PlaylistName { get; set; } - /// - /// Gets or sets the chapters. - /// - /// The chapters. - public double[] Chapters { get; set; } - } + /// + /// Gets or sets the chapters. + /// + /// The chapters. + public double[] Chapters { get; set; } } diff --git a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs index 5b7d1d03c..d39725301 100644 --- a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs +++ b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs @@ -1,15 +1,14 @@ -namespace MediaBrowser.Model.MediaInfo +namespace MediaBrowser.Model.MediaInfo; + +/// +/// Interface IBlurayExaminer. +/// +public interface IBlurayExaminer { /// - /// Interface IBlurayExaminer. + /// Gets the disc info. /// - public interface IBlurayExaminer - { - /// - /// Gets the disc info. - /// - /// The path. - /// BlurayDiscInfo. - BlurayDiscInfo GetDiscInfo(string path); - } + /// The path. + /// BlurayDiscInfo. + BlurayDiscInfo GetDiscInfo(string path); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 7200d674c..e199db7f2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.MediaInfo if (item.VideoType == VideoType.Dvd) { // Get list of playable .vob files - var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null).ToList(); + var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null); // Return if no playable .vob files are found if (vobs.Count == 0) @@ -105,22 +105,19 @@ namespace MediaBrowser.Providers.MediaInfo mediaInfoResult = await GetMediaInfo( new Video { - Path = vobs.First() + Path = vobs[0] }, cancellationToken).ConfigureAwait(false); - // Remove first .vob file - vobs.RemoveAt(0); - - // Sum up the runtime of all .vob files - foreach (var vob in vobs) + // Sum up the runtime of all .vob files skipping the first .vob + for (var i = 1; i < vobs.Count; i++) { var tmpMediaInfo = await GetMediaInfo( - new Video - { - Path = vob - }, - cancellationToken).ConfigureAwait(false); + new Video + { + Path = vobs[i] + }, + cancellationToken).ConfigureAwait(false); mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks; } @@ -131,7 +128,7 @@ namespace MediaBrowser.Providers.MediaInfo blurayDiscInfo = GetBDInfo(item.Path); // Get playable .m2ts files - var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path).ToList(); + var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path); // Return if no playable .m2ts files are found if (blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0) @@ -144,14 +141,13 @@ namespace MediaBrowser.Providers.MediaInfo mediaInfoResult = await GetMediaInfo( new Video { - Path = m2ts.First() + Path = m2ts[0] }, cancellationToken).ConfigureAwait(false); } else { mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); } cancellationToken.ThrowIfCancellationRequested(); @@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.MediaInfo } } - private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List mediaStreams, BlurayDiscInfo blurayInfo) + private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List mediaStreams, BlurayDiscInfo blurayInfo) { - var video = (Video)item; - if (blurayInfo.Files.Length <= 1) { return; -- cgit v1.2.3