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(prefix, inputFile, mediaSource.Protocol);
+ return EncodingUtils.GetInputArgument("file", inputFiles, mediaSource.Protocol);
}
- ///
- /// Gets the input argument for an external subtitle file.
- ///
- /// The input file.
- /// System.String.
- /// Unrecognized InputType.
+ ///
+ public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
+ {
+ return EncodingUtils.GetInputArgument("file", new List() { inputFile }, mediaSource.Protocol);
+ }
+
+ ///
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('_');
+ // Aggregate all VOBs of the titles
+ return allVobs
+ .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1]))
+ .Select(i => i.FullName)
+ .ToList();
+ }
- if (parts.Length == 3)
- {
- var title = parts[1];
+ public IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber)
+ {
+ var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files;
+ var directoryFiles = _fileSystem.GetFiles(path + "/BDMV/STREAM/");
- 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);
+ 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,37 +90,50 @@ 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);
+
+ 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);
+ cancellationToken.ThrowIfCancellationRequested();
}
- streamFileNames ??= Array.Empty();
-
- mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
-
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)
{