|
@@ -9,6 +9,7 @@ using System.IO;
|
|
|
using System.Linq;
|
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
+using DvdLib.Ifo;
|
|
|
using MediaBrowser.Common.Configuration;
|
|
|
using MediaBrowser.Controller.Chapters;
|
|
|
using MediaBrowser.Controller.Configuration;
|
|
@@ -36,6 +37,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
private readonly ILogger<FFProbeVideoInfo> _logger;
|
|
|
private readonly IMediaEncoder _mediaEncoder;
|
|
|
private readonly IItemRepository _itemRepo;
|
|
|
+ private readonly IBlurayExaminer _blurayExaminer;
|
|
|
private readonly ILocalizationManager _localization;
|
|
|
private readonly IEncodingManager _encodingManager;
|
|
|
private readonly IServerConfigurationManager _config;
|
|
@@ -51,6 +53,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
IMediaSourceManager mediaSourceManager,
|
|
|
IMediaEncoder mediaEncoder,
|
|
|
IItemRepository itemRepo,
|
|
|
+ IBlurayExaminer blurayExaminer,
|
|
|
ILocalizationManager localization,
|
|
|
IEncodingManager encodingManager,
|
|
|
IServerConfigurationManager config,
|
|
@@ -64,6 +67,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
_mediaSourceManager = mediaSourceManager;
|
|
|
_mediaEncoder = mediaEncoder;
|
|
|
_itemRepo = itemRepo;
|
|
|
+ _blurayExaminer = blurayExaminer;
|
|
|
_localization = localization;
|
|
|
_encodingManager = encodingManager;
|
|
|
_config = config;
|
|
@@ -80,16 +84,47 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
CancellationToken cancellationToken)
|
|
|
where T : Video
|
|
|
{
|
|
|
+ BlurayDiscInfo blurayDiscInfo = null;
|
|
|
+
|
|
|
Model.MediaInfo.MediaInfo mediaInfoResult = null;
|
|
|
|
|
|
if (!item.IsShortcut || options.EnableRemoteContentProbe)
|
|
|
{
|
|
|
+ string[] streamFileNames = null;
|
|
|
+
|
|
|
+ if (item.VideoType == VideoType.Dvd)
|
|
|
+ {
|
|
|
+ streamFileNames = FetchFromDvdLib(item);
|
|
|
+
|
|
|
+ if (streamFileNames.Length == 0)
|
|
|
+ {
|
|
|
+ _logger.LogError("No playable vobs found in dvd structure, skipping ffprobe.");
|
|
|
+ return ItemUpdateType.MetadataImport;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (item.VideoType == VideoType.BluRay)
|
|
|
+ {
|
|
|
+ var inputPath = item.Path;
|
|
|
+
|
|
|
+ blurayDiscInfo = GetBDInfo(inputPath);
|
|
|
+
|
|
|
+ streamFileNames = blurayDiscInfo.Files;
|
|
|
+
|
|
|
+ if (streamFileNames.Length == 0)
|
|
|
+ {
|
|
|
+ _logger.LogError("No playable vobs found in bluray structure, skipping ffprobe.");
|
|
|
+ return ItemUpdateType.MetadataImport;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ streamFileNames ??= Array.Empty<string>();
|
|
|
+
|
|
|
mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
}
|
|
|
|
|
|
- await Fetch(item, cancellationToken, mediaInfoResult, options).ConfigureAwait(false);
|
|
|
+ await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false);
|
|
|
|
|
|
return ItemUpdateType.MetadataImport;
|
|
|
}
|
|
@@ -129,6 +164,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
Video video,
|
|
|
CancellationToken cancellationToken,
|
|
|
Model.MediaInfo.MediaInfo mediaInfo,
|
|
|
+ BlurayDiscInfo blurayInfo,
|
|
|
MetadataRefreshOptions options)
|
|
|
{
|
|
|
List<MediaStream> mediaStreams;
|
|
@@ -182,6 +218,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
video.Container = mediaInfo.Container;
|
|
|
|
|
|
chapters = mediaInfo.Chapters ?? Array.Empty<ChapterInfo>();
|
|
|
+ if (blurayInfo is not null)
|
|
|
+ {
|
|
|
+ FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
|
|
|
+ }
|
|
|
}
|
|
|
else
|
|
|
{
|
|
@@ -277,6 +317,91 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
|
|
|
+ {
|
|
|
+ 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)
|
|
|
+ {
|
|
|
+ int? currentHeight = null;
|
|
|
+ int? currentWidth = null;
|
|
|
+ int? currentBitRate = null;
|
|
|
+
|
|
|
+ var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
|
|
|
+
|
|
|
+ // Grab the values that ffprobe recorded
|
|
|
+ if (videoStream is not null)
|
|
|
+ {
|
|
|
+ currentBitRate = videoStream.BitRate;
|
|
|
+ currentWidth = videoStream.Width;
|
|
|
+ currentHeight = videoStream.Height;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fill video properties from the BDInfo result
|
|
|
+ mediaStreams.Clear();
|
|
|
+ mediaStreams.AddRange(blurayInfo.MediaStreams);
|
|
|
+
|
|
|
+ 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++)
|
|
|
+ {
|
|
|
+ chapters[i] = new ChapterInfo
|
|
|
+ {
|
|
|
+ StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool IsEmpty(int? num)
|
|
|
+ {
|
|
|
+ return !num.HasValue || num.Value == 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets information about the longest playlist on a bdrom.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="path">The path.</param>
|
|
|
+ /// <returns>VideoStream.</returns>
|
|
|
+ private BlurayDiscInfo GetBDInfo(string path)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(path))
|
|
|
+ {
|
|
|
+ throw new ArgumentNullException(nameof(path));
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ return _blurayExaminer.GetDiscInfo(path);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogError(ex, "Error getting BDInfo");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOptions, LibraryOptions libraryOptions)
|
|
|
{
|
|
|
var replaceData = refreshOptions.ReplaceAllMetadata;
|
|
@@ -558,5 +683,33 @@ namespace MediaBrowser.Providers.MediaInfo
|
|
|
|
|
|
return chapters;
|
|
|
}
|
|
|
+
|
|
|
+ private string[] FetchFromDvdLib(Video item)
|
|
|
+ {
|
|
|
+ var path = item.Path;
|
|
|
+ var dvd = new Dvd(path);
|
|
|
+
|
|
|
+ var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
|
|
|
+
|
|
|
+ byte? titleNumber = null;
|
|
|
+
|
|
|
+ if (primaryTitle is not null)
|
|
|
+ {
|
|
|
+ titleNumber = primaryTitle.VideoTitleSetNumber;
|
|
|
+ item.RunTimeTicks = GetRuntime(primaryTitle);
|
|
|
+ }
|
|
|
+
|
|
|
+ return _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, titleNumber)
|
|
|
+ .Select(Path.GetFileName)
|
|
|
+ .ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private long GetRuntime(Title title)
|
|
|
+ {
|
|
|
+ return title.ProgramChains
|
|
|
+ .Select(i => (TimeSpan)i.PlaybackTime)
|
|
|
+ .Select(i => i.Ticks)
|
|
|
+ .Sum();
|
|
|
+ }
|
|
|
}
|
|
|
}
|