diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding')
8 files changed, 559 insertions, 36 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index db177ff76..80091bf5a 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -14,6 +14,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -301,10 +302,10 @@ namespace MediaBrowser.MediaEncoding.Attachments var processArgs = string.Format( CultureInfo.InvariantCulture, - "-dump_attachment:{1} {2} -i {0} -t 0 -f null null", + "-dump_attachment:{1} \"{2}\" -i {0} -t 0 -f null null", inputPath, attachmentStreamIndex, - outputPath); + EncodingUtils.NormalizePath(outputPath)); int exitCode; diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs new file mode 100644 index 000000000..ea520b1d6 --- /dev/null +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using System.Linq; +using BDInfo.IO; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// <summary> +/// Class BdInfoDirectoryInfo. +/// </summary> +public class BdInfoDirectoryInfo : IDirectoryInfo +{ + private readonly IFileSystem _fileSystem; + + private readonly FileSystemMetadata _impl; + + /// <summary> + /// Initializes a new instance of the <see cref="BdInfoDirectoryInfo" /> class. + /// </summary> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="path">The path.</param> + public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) + { + _fileSystem = fileSystem; + _impl = _fileSystem.GetDirectoryInfo(path); + } + + private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl) + { + _fileSystem = fileSystem; + _impl = impl; + } + + /// <summary> + /// Gets the name. + /// </summary> + public string Name => _impl.Name; + + /// <summary> + /// Gets the full name. + /// </summary> + public string FullName => _impl.FullName; + + /// <summary> + /// Gets the parent directory information. + /// </summary> + public IDirectoryInfo? Parent + { + get + { + var parentFolder = Path.GetDirectoryName(_impl.FullName); + if (parentFolder is not null) + { + return new BdInfoDirectoryInfo(_fileSystem, parentFolder); + } + + return null; + } + } + + /// <summary> + /// Gets the directories. + /// </summary> + /// <returns>An array with all directories.</returns> + public IDirectoryInfo[] GetDirectories() + { + return _fileSystem.GetDirectories(_impl.FullName) + .Select(x => new BdInfoDirectoryInfo(_fileSystem, x)) + .ToArray(); + } + + /// <summary> + /// Gets the files. + /// </summary> + /// <returns>All files of the directory.</returns> + public IFileInfo[] GetFiles() + { + return _fileSystem.GetFiles(_impl.FullName) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } + + /// <summary> + /// Gets the files matching a pattern. + /// </summary> + /// <param name="searchPattern">The search pattern.</param> + /// <returns>All files of the directory matchign the search pattern.</returns> + public IFileInfo[] GetFiles(string searchPattern) + { + return _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false) + .Select(x => new BdInfoFileInfo(x)) + .ToArray(); + } + + /// <summary> + /// Gets the files matching a pattern and search options. + /// </summary> + /// <param name="searchPattern">The search pattern.</param> + /// <param name="searchOption">The search optin.</param> + /// <returns>All files of the directory matchign the search pattern and options.</returns> + 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(); + } + + /// <summary> + /// Gets the bdinfo of a file system path. + /// </summary> + /// <param name="fs">The file system.</param> + /// <param name="path">The path.</param> + /// <returns>The BD directory information of the path on the file system.</returns> + 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 new file mode 100644 index 000000000..8ebb59c59 --- /dev/null +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BDInfo; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// <summary> +/// Class BdInfoExaminer. +/// </summary> +public class BdInfoExaminer : IBlurayExaminer +{ + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="BdInfoExaminer" /> class. + /// </summary> + /// <param name="fileSystem">The filesystem.</param> + public BdInfoExaminer(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + /// <summary> + /// Gets the disc info. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>BlurayDiscInfo.</returns> + public BlurayDiscInfo GetDiscInfo(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path)); + + bdrom.Scan(); + + // Get the longest playlist + var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); + + var outputStream = new BlurayDiscInfo + { + MediaStreams = Array.Empty<MediaStream>() + }; + + if (playlist is null) + { + return outputStream; + } + + outputStream.Chapters = playlist.Chapters.ToArray(); + + outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; + + var sortedStreams = playlist.SortedStreams; + var mediaStreams = new List<MediaStream>(sortedStreams.Count); + + foreach (var stream in sortedStreams) + { + switch (stream) + { + case TSVideoStream videoStream: + AddVideoStream(mediaStreams, videoStream); + break; + case TSAudioStream audioStream: + AddAudioStream(mediaStreams, audioStream); + break; + case TSTextStream textStream: + AddSubtitleStream(mediaStreams, textStream); + break; + case TSGraphicsStream graphicStream: + AddSubtitleStream(mediaStreams, graphicStream); + break; + } + } + + outputStream.MediaStreams = mediaStreams.ToArray(); + + outputStream.PlaylistName = playlist.Name; + + 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(); + } + + return outputStream; + } + + /// <summary> + /// Adds the video stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="videoStream">The video stream.</param> + private void AddVideoStream(List<MediaStream> 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; + + mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; + } + + streams.Add(mediaStream); + } + + /// <summary> + /// Adds the audio stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="audioStream">The audio stream.</param> + private void AddAudioStream(List<MediaStream> 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); + + if (bitrate > 0) + { + stream.BitRate = bitrate; + } + + if (audioStream.LFE > 0) + { + stream.Channels = audioStream.ChannelCount + 1; + } + + streams.Add(stream); + } + + /// <summary> + /// Adds the subtitle stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="textStream">The text stream.</param> + private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream) + { + streams.Add(new MediaStream + { + Language = textStream.LanguageCode, + Codec = textStream.CodecShortName, + Type = MediaStreamType.Subtitle, + Index = streams.Count + }); + } + + /// <summary> + /// Adds the subtitle stream. + /// </summary> + /// <param name="streams">The streams.</param> + /// <param name="textStream">The text stream.</param> + private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream) + { + streams.Add(new MediaStream + { + 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 new file mode 100644 index 000000000..9e7a1d50a --- /dev/null +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -0,0 +1,68 @@ +using System.IO; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.MediaEncoding.BdInfo; + +/// <summary> +/// Class BdInfoFileInfo. +/// </summary> +public class BdInfoFileInfo : BDInfo.IO.IFileInfo +{ + private FileSystemMetadata _impl; + + /// <summary> + /// Initializes a new instance of the <see cref="BdInfoFileInfo" /> class. + /// </summary> + /// <param name="impl">The <see cref="FileSystemMetadata" />.</param> + public BdInfoFileInfo(FileSystemMetadata impl) + { + _impl = impl; + } + + /// <summary> + /// Gets the name. + /// </summary> + public string Name => _impl.Name; + + /// <summary> + /// Gets the full name. + /// </summary> + public string FullName => _impl.FullName; + + /// <summary> + /// Gets the extension. + /// </summary> + public string Extension => _impl.Extension; + + /// <summary> + /// Gets the length. + /// </summary> + public long Length => _impl.Length; + + /// <summary> + /// Gets a value indicating whether this is a directory. + /// </summary> + public bool IsDir => _impl.IsDirectory; + + /// <summary> + /// Gets a file as file stream. + /// </summary> + /// <returns>A <see cref="FileStream" /> for the file.</returns> + public Stream OpenRead() + { + return new FileStream( + FullName, + FileMode.Open, + FileAccess.Read, + FileShare.Read); + } + + /// <summary> + /// Gets a files's content with a stream reader. + /// </summary> + /// <returns>A <see cref="StreamReader" /> for the file's content.</returns> + public StreamReader OpenText() + { + return new StreamReader(OpenRead()); + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index d0ea0429b..04128c911 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<string> inputFiles, MediaProtocol protocol) + { + if (protocol != MediaProtocol.File) + { + return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFiles[0]); + } + + return GetConcatInputArgument(inputFiles, inputPrefix); } /// <summary> /// Gets the concat input argument. /// </summary> - /// <param name="inputFile">The input file.</param> + /// <param name="inputFiles">The input files.</param> /// <param name="inputPrefix">The input prefix.</param> /// <returns>System.String.</returns> - private static string GetConcatInputArgument(string inputFile, string inputPrefix) + private static string GetConcatInputArgument(IReadOnlyList<string> 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); } /// <summary> @@ -56,7 +75,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// </summary> /// <param name="path">The path.</param> /// <returns>System.String.</returns> - private static string NormalizePath(string path) + public static string NormalizePath(string path) { // Quotes are valid path characters in linux and they need to be escaped here with a leading \ return path.Replace("\"", "\\\"", StringComparison.Ordinal); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index cef02d5f8..d2112e5dc 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; @@ -51,6 +52,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 +97,7 @@ namespace MediaBrowser.MediaEncoding.Encoder ILogger<MediaEncoder> logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, + IBlurayExaminer blurayExaminer, ILocalizationManager localization, IConfiguration config, IServerConfigurationManager serverConfig) @@ -102,6 +105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = logger; _configurationManager = configurationManager; _fileSystem = fileSystem; + _blurayExaminer = blurayExaminer; _localization = localization; _config = config; _serverConfig = serverConfig; @@ -117,16 +121,22 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public string ProbePath => _ffprobePath; + /// <inheritdoc /> public Version EncoderVersion => _ffmpegVersion; + /// <inheritdoc /> public bool IsPkeyPauseSupported => _isPkeyPauseSupported; + /// <inheritdoc /> public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; + /// <inheritdoc /> public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD; + /// <inheritdoc /> public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; + /// <inheritdoc /> public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier; /// <summary> @@ -344,26 +354,31 @@ namespace MediaBrowser.MediaEncoding.Encoder _ffmpegVersion = validator.GetFFmpegVersion(); } + /// <inheritdoc /> public bool SupportsEncoder(string encoder) { return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsDecoder(string decoder) { return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsHwaccel(string hwaccel) { return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsFilter(string filter) { return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase); } + /// <inheritdoc /> public bool SupportsFilterWithOption(FilterOptionType option) { if (_filtersWithOption.TryGetValue((int)option, out var val)) @@ -394,24 +409,16 @@ namespace MediaBrowser.MediaEncoding.Encoder return true; } - /// <summary> - /// Gets the media info. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> + /// <inheritdoc /> public Task<MediaInfo> 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 +426,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 +436,30 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken); } - /// <summary> - /// Gets the input argument. - /// </summary> - /// <param name="inputFile">The input file.</param> - /// <param name="mediaSource">The mediaSource.</param> - /// <returns>System.String.</returns> - /// <exception cref="ArgumentException">Unrecognized InputType.</exception> + /// <inheritdoc /> + public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaSourceInfo mediaSource) + { + return EncodingUtils.GetInputArgument("file", inputFiles, mediaSource.Protocol); + } + + /// <inheritdoc /> public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) { var prefix = "file"; - if (mediaSource.VideoType == VideoType.BluRay - || mediaSource.IsoType == IsoType.BluRay) + if (mediaSource.IsoType == IsoType.BluRay) { prefix = "bluray"; } - return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol); + return EncodingUtils.GetInputArgument(prefix, new[] { inputFile }, mediaSource.Protocol); } - /// <summary> - /// Gets the input argument for an external subtitle file. - /// </summary> - /// <param name="inputFile">The input file.</param> - /// <returns>System.String.</returns> - /// <exception cref="ArgumentException">Unrecognized InputType.</exception> + /// <inheritdoc /> public string GetExternalSubtitleInputArgument(string inputFile) { const string Prefix = "file"; - return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File); + return EncodingUtils.GetInputArgument(Prefix, new[] { inputFile }, MediaProtocol.File); } /// <summary> @@ -549,6 +550,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// <inheritdoc /> public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) { var mediaSource = new MediaSourceInfo @@ -559,11 +561,13 @@ namespace MediaBrowser.MediaEncoding.Encoder return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken); } + /// <inheritdoc /> public Task<string> 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); } + /// <inheritdoc /> public Task<string> 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 +771,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// <inheritdoc /> public string GetTimeParameter(long ticks) { var time = TimeSpan.FromTicks(ticks); @@ -865,6 +870,114 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new NotImplementedException(); } + /// <inheritdoc /> + public IReadOnlyList<string> 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) + .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 (titleNumber.HasValue) + { + 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) + { + return vobs.Select(i => i.FullName).ToList(); + } + + _logger.LogWarning("Could not determine .vob files for title {Title} of {Path}.", titleNumber, path); + } + + // Check for multiple big titles (> 900 MB) + var titles = allVobs + .Where(vob => vob.Length >= 900 * 1024 * 1024) + .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()) + .Distinct() + .ToList(); + + // Fall back to first title if no big title is found + if (titles.Count == 0) + { + titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString()); + } + + // Aggregate all .vob files of the titles + return allVobs + .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString())) + .Select(i => i.FullName) + .ToList(); + } + + /// <inheritdoc /> + public IReadOnlyList<string> 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) + .ToList(); + } + + /// <inheritdoc /> + public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) + { + // Get all playable files + IReadOnlyList<string> files; + var videoType = source.VideoType; + if (videoType == VideoType.Dvd) + { + files = GetPrimaryPlaylistVobFiles(source.Path, null); + } + else if (videoType == VideoType.BluRay) + { + files = GetPrimaryPlaylistM2tsFiles(source.Path); + } + else + { + return; + } + + // Generate concat configuration entries for each file and write to file + using (StreamWriter sw = new StreamWriter(concatFilePath)) + { + foreach (var path in files) + { + 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; + + // Add file path stanza to concat configuration + sw.WriteLine("file '{0}'", path); + + // Add duration stanza to concat configuration + sw.WriteLine("duration {0}", duration); + } + } + } + public bool CanExtractSubtitles(string codec) { // TODO is there ever a case when a subtitle can't be extracted?? diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index f4438fe19..a0624fe76 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -22,6 +22,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="BDInfo" /> <PackageReference Include="libse" /> <PackageReference Include="Microsoft.Extensions.Http" /> <PackageReference Include="System.Text.Encoding.CodePages" /> diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 8b8279588..cb482301f 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -248,12 +248,23 @@ namespace MediaBrowser.MediaEncoding.Probing return null; } + // Handle MPEG-1 container if (string.Equals(format, "mpegvideo", StringComparison.OrdinalIgnoreCase)) { return "mpeg"; } - format = format.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); + // Handle MPEG-2 container + if (string.Equals(format, "mpeg", StringComparison.OrdinalIgnoreCase)) + { + return "ts"; + } + + // Handle matroska container + if (string.Equals(format, "matroska", StringComparison.OrdinalIgnoreCase)) + { + return "mkv"; + } return format; } |
