2021-07-22 20:16:38 -07:00
|
|
|
#pragma warning disable CA1068, CS1591
|
2020-02-23 10:53:51 +01:00
|
|
|
|
2019-01-13 21:03:10 +01:00
|
|
|
using System;
|
2019-01-13 20:26:31 +01:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2025-07-05 05:22:27 +12:00
|
|
|
using Jellyfin.Data.Enums;
|
2025-02-05 18:13:28 -06:00
|
|
|
using Jellyfin.Extensions;
|
2014-02-09 16:11:11 -05:00
|
|
|
using MediaBrowser.Common.Configuration;
|
2014-06-09 15:16:14 -04:00
|
|
|
using MediaBrowser.Controller.Chapters;
|
2014-05-06 22:28:19 -04:00
|
|
|
using MediaBrowser.Controller.Configuration;
|
2014-02-04 15:19:29 -05:00
|
|
|
using MediaBrowser.Controller.Entities;
|
2014-05-06 22:28:19 -04:00
|
|
|
using MediaBrowser.Controller.Entities.Movies;
|
|
|
|
|
using MediaBrowser.Controller.Entities.TV;
|
2014-02-04 15:19:29 -05:00
|
|
|
using MediaBrowser.Controller.Library;
|
2014-02-20 11:37:41 -05:00
|
|
|
using MediaBrowser.Controller.MediaEncoding;
|
2014-02-04 15:19:29 -05:00
|
|
|
using MediaBrowser.Controller.Persistence;
|
2014-02-10 13:39:41 -05:00
|
|
|
using MediaBrowser.Controller.Providers;
|
2014-05-06 22:28:19 -04:00
|
|
|
using MediaBrowser.Controller.Subtitles;
|
2014-08-10 18:13:17 -04:00
|
|
|
using MediaBrowser.Model.Configuration;
|
2019-01-13 20:26:31 +01:00
|
|
|
using MediaBrowser.Model.Dlna;
|
|
|
|
|
using MediaBrowser.Model.Dto;
|
2014-02-04 15:19:29 -05:00
|
|
|
using MediaBrowser.Model.Entities;
|
2019-01-13 20:26:31 +01:00
|
|
|
using MediaBrowser.Model.Globalization;
|
2014-02-04 15:19:29 -05:00
|
|
|
using MediaBrowser.Model.MediaInfo;
|
2014-08-10 18:13:17 -04:00
|
|
|
using MediaBrowser.Model.Providers;
|
2019-01-13 20:26:31 +01:00
|
|
|
using Microsoft.Extensions.Logging;
|
2014-02-04 15:19:29 -05:00
|
|
|
|
|
|
|
|
namespace MediaBrowser.Providers.MediaInfo
|
|
|
|
|
{
|
|
|
|
|
public class FFProbeVideoInfo
|
|
|
|
|
{
|
2022-11-27 14:35:07 +01:00
|
|
|
private readonly ILogger<FFProbeVideoInfo> _logger;
|
2024-10-09 10:36:08 +00:00
|
|
|
private readonly IMediaSourceManager _mediaSourceManager;
|
2014-02-04 15:19:29 -05:00
|
|
|
private readonly IMediaEncoder _mediaEncoder;
|
2023-02-03 18:49:23 +01:00
|
|
|
private readonly IBlurayExaminer _blurayExaminer;
|
2014-02-04 15:19:29 -05:00
|
|
|
private readonly ILocalizationManager _localization;
|
2025-04-26 14:01:12 +02:00
|
|
|
private readonly IChapterManager _chapterManager;
|
2014-05-06 22:28:19 -04:00
|
|
|
private readonly IServerConfigurationManager _config;
|
|
|
|
|
private readonly ISubtitleManager _subtitleManager;
|
2015-06-28 13:00:36 -04:00
|
|
|
private readonly ILibraryManager _libraryManager;
|
2022-02-17 09:03:08 +01:00
|
|
|
private readonly AudioResolver _audioResolver;
|
|
|
|
|
private readonly SubtitleResolver _subtitleResolver;
|
2024-10-09 10:36:08 +00:00
|
|
|
private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
|
|
|
|
|
private readonly IMediaStreamRepository _mediaStreamRepository;
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2020-02-23 10:53:51 +01:00
|
|
|
public FFProbeVideoInfo(
|
2022-11-27 14:35:07 +01:00
|
|
|
ILogger<FFProbeVideoInfo> logger,
|
2020-02-23 10:53:51 +01:00
|
|
|
IMediaSourceManager mediaSourceManager,
|
|
|
|
|
IMediaEncoder mediaEncoder,
|
2023-02-03 18:49:23 +01:00
|
|
|
IBlurayExaminer blurayExaminer,
|
2020-02-23 10:53:51 +01:00
|
|
|
ILocalizationManager localization,
|
2025-04-26 14:01:12 +02:00
|
|
|
IChapterManager chapterManager,
|
2020-02-23 10:53:51 +01:00
|
|
|
IServerConfigurationManager config,
|
|
|
|
|
ISubtitleManager subtitleManager,
|
2021-11-27 12:10:57 +01:00
|
|
|
ILibraryManager libraryManager,
|
2022-02-17 09:03:08 +01:00
|
|
|
AudioResolver audioResolver,
|
2024-10-09 10:36:08 +00:00
|
|
|
SubtitleResolver subtitleResolver,
|
|
|
|
|
IMediaAttachmentRepository mediaAttachmentRepository,
|
|
|
|
|
IMediaStreamRepository mediaStreamRepository)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
|
|
|
|
_logger = logger;
|
2022-02-17 09:03:08 +01:00
|
|
|
_mediaSourceManager = mediaSourceManager;
|
2014-02-04 15:19:29 -05:00
|
|
|
_mediaEncoder = mediaEncoder;
|
2023-02-03 18:49:23 +01:00
|
|
|
_blurayExaminer = blurayExaminer;
|
2014-02-04 15:19:29 -05:00
|
|
|
_localization = localization;
|
2025-04-26 14:01:12 +02:00
|
|
|
_chapterManager = chapterManager;
|
2014-05-06 22:28:19 -04:00
|
|
|
_config = config;
|
|
|
|
|
_subtitleManager = subtitleManager;
|
2015-06-28 13:00:36 -04:00
|
|
|
_libraryManager = libraryManager;
|
2021-11-28 14:03:52 +01:00
|
|
|
_audioResolver = audioResolver;
|
2022-01-27 14:21:53 +01:00
|
|
|
_subtitleResolver = subtitleResolver;
|
2024-10-09 10:36:08 +00:00
|
|
|
_mediaAttachmentRepository = mediaAttachmentRepository;
|
|
|
|
|
_mediaStreamRepository = mediaStreamRepository;
|
|
|
|
|
_mediaStreamRepository = mediaStreamRepository;
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2020-04-11 12:29:04 +02:00
|
|
|
public async Task<ItemUpdateType> ProbeVideo<T>(
|
|
|
|
|
T item,
|
2014-06-09 15:16:14 -04:00
|
|
|
MetadataRefreshOptions options,
|
|
|
|
|
CancellationToken cancellationToken)
|
2014-02-04 15:19:29 -05:00
|
|
|
where T : Video
|
|
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
BlurayDiscInfo? blurayDiscInfo = null;
|
2023-02-03 18:49:23 +01:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
Model.MediaInfo.MediaInfo? mediaInfoResult = null;
|
2018-09-12 19:26:21 +02:00
|
|
|
|
|
|
|
|
if (!item.IsShortcut || options.EnableRemoteContentProbe)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2023-02-03 18:49:23 +01:00
|
|
|
if (item.VideoType == VideoType.Dvd)
|
|
|
|
|
{
|
2023-02-05 22:59:58 +01:00
|
|
|
// Get list of playable .vob files
|
2023-02-18 14:42:35 +01:00
|
|
|
var vobs = _mediaEncoder.GetPrimaryPlaylistVobFiles(item.Path, null);
|
2023-02-05 22:59:58 +01:00
|
|
|
|
|
|
|
|
// 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
|
2023-02-03 20:03:55 +01:00
|
|
|
mediaInfoResult = await GetMediaInfo(
|
|
|
|
|
new Video
|
|
|
|
|
{
|
2023-02-18 14:42:35 +01:00
|
|
|
Path = vobs[0]
|
2023-02-03 20:03:55 +01:00
|
|
|
},
|
|
|
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
2023-02-18 14:42:35 +01:00
|
|
|
// Sum up the runtime of all .vob files skipping the first .vob
|
|
|
|
|
for (var i = 1; i < vobs.Count; i++)
|
2023-02-03 18:49:23 +01:00
|
|
|
{
|
2023-02-03 20:03:55 +01:00
|
|
|
var tmpMediaInfo = await GetMediaInfo(
|
2023-02-18 14:42:35 +01:00
|
|
|
new Video
|
|
|
|
|
{
|
|
|
|
|
Path = vobs[i]
|
|
|
|
|
},
|
|
|
|
|
cancellationToken).ConfigureAwait(false);
|
2023-02-03 20:03:55 +01:00
|
|
|
|
|
|
|
|
mediaInfoResult.RunTimeTicks += tmpMediaInfo.RunTimeTicks;
|
2023-02-03 18:49:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-02-04 12:34:24 +01:00
|
|
|
else if (item.VideoType == VideoType.BluRay)
|
2023-02-03 18:49:23 +01:00
|
|
|
{
|
2023-02-05 22:59:58 +01:00
|
|
|
// Get BD disc information
|
2023-02-04 12:34:24 +01:00
|
|
|
blurayDiscInfo = GetBDInfo(item.Path);
|
2023-02-05 22:59:58 +01:00
|
|
|
|
|
|
|
|
// Return if no playable .m2ts files are found
|
2024-07-01 00:21:06 +02:00
|
|
|
if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0)
|
2023-02-05 22:59:58 +01:00
|
|
|
{
|
|
|
|
|
_logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
|
|
|
|
|
return ItemUpdateType.MetadataImport;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch metadata of first .m2ts file
|
2023-02-04 12:34:24 +01:00
|
|
|
mediaInfoResult = await GetMediaInfo(
|
|
|
|
|
new Video
|
2023-02-03 20:03:55 +01:00
|
|
|
{
|
2024-07-01 00:21:06 +02:00
|
|
|
Path = blurayDiscInfo.Files[0]
|
2023-02-04 12:34:24 +01:00
|
|
|
},
|
|
|
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-02-03 20:03:55 +01:00
|
|
|
mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
}
|
2018-09-12 19:26:21 +02:00
|
|
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2023-02-03 18:49:23 +01:00
|
|
|
await Fetch(item, cancellationToken, mediaInfoResult, blurayDiscInfo, options).ConfigureAwait(false);
|
2018-09-12 19:26:21 +02:00
|
|
|
|
2014-02-04 15:19:29 -05:00
|
|
|
return ItemUpdateType.MetadataImport;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 12:29:04 +02:00
|
|
|
private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
|
|
|
|
|
Video item,
|
2014-06-09 15:16:14 -04:00
|
|
|
CancellationToken cancellationToken)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
2018-09-12 19:26:21 +02:00
|
|
|
var path = item.Path;
|
|
|
|
|
var protocol = item.PathProtocol ?? MediaProtocol.File;
|
|
|
|
|
|
|
|
|
|
if (item.IsShortcut)
|
|
|
|
|
{
|
|
|
|
|
path = item.ShortcutPath;
|
|
|
|
|
protocol = _mediaSourceManager.GetPathProtocol(path);
|
|
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2020-04-11 12:29:04 +02:00
|
|
|
return _mediaEncoder.GetMediaInfo(
|
|
|
|
|
new MediaInfoRequest
|
2018-09-12 19:26:21 +02:00
|
|
|
{
|
2020-04-11 12:29:04 +02:00
|
|
|
ExtractChapters = true,
|
|
|
|
|
MediaType = DlnaProfileType.Video,
|
|
|
|
|
MediaSource = new MediaSourceInfo
|
|
|
|
|
{
|
|
|
|
|
Path = path,
|
|
|
|
|
Protocol = protocol,
|
2021-06-10 21:07:28 +03:00
|
|
|
VideoType = item.VideoType,
|
|
|
|
|
IsoType = item.IsoType
|
2020-04-11 12:29:04 +02:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
cancellationToken);
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2020-04-11 12:29:04 +02:00
|
|
|
protected async Task Fetch(
|
|
|
|
|
Video video,
|
2014-06-09 15:16:14 -04:00
|
|
|
CancellationToken cancellationToken,
|
2023-08-22 18:09:31 +02:00
|
|
|
Model.MediaInfo.MediaInfo? mediaInfo,
|
|
|
|
|
BlurayDiscInfo? blurayInfo,
|
2014-06-09 15:16:14 -04:00
|
|
|
MetadataRefreshOptions options)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
List<MediaStream> mediaStreams = new List<MediaStream>();
|
2019-12-26 23:20:31 +01:00
|
|
|
IReadOnlyList<MediaAttachment> mediaAttachments;
|
2020-02-23 10:53:51 +01:00
|
|
|
ChapterInfo[] chapters;
|
2014-04-22 13:25:54 -04:00
|
|
|
|
2022-05-04 08:20:48 -06:00
|
|
|
// Add external streams before adding the streams from the file to preserve stream IDs on remote videos
|
|
|
|
|
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
|
|
|
|
|
|
2022-12-05 15:01:13 +01:00
|
|
|
if (mediaInfo is not null)
|
2017-12-03 17:12:46 -05:00
|
|
|
{
|
2022-05-04 08:20:48 -06:00
|
|
|
foreach (var mediaStream in mediaInfo.MediaStreams)
|
|
|
|
|
{
|
|
|
|
|
mediaStream.Index = startIndex++;
|
|
|
|
|
mediaStreams.Add(mediaStream);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 13:23:08 -04:00
|
|
|
mediaAttachments = mediaInfo.MediaAttachments;
|
2017-12-03 17:12:46 -05:00
|
|
|
video.TotalBitrate = mediaInfo.Bitrate;
|
2023-02-03 20:03:55 +01:00
|
|
|
video.RunTimeTicks = mediaInfo.RunTimeTicks;
|
2017-12-03 17:12:46 -05:00
|
|
|
video.Container = mediaInfo.Container;
|
2025-05-05 05:21:44 +02:00
|
|
|
var videoType = video.VideoType;
|
|
|
|
|
if (videoType == VideoType.BluRay || videoType == VideoType.Dvd)
|
|
|
|
|
{
|
|
|
|
|
video.Size = mediaInfo.Size;
|
|
|
|
|
}
|
2014-04-18 01:03:01 -04:00
|
|
|
|
2025-05-05 05:21:44 +02:00
|
|
|
chapters = mediaInfo.Chapters ?? [];
|
2023-02-03 18:49:23 +01:00
|
|
|
if (blurayInfo is not null)
|
|
|
|
|
{
|
|
|
|
|
FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
|
|
|
|
|
}
|
2015-04-04 15:35:29 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
foreach (var mediaStream in video.GetMediaStreams())
|
2022-05-04 08:20:48 -06:00
|
|
|
{
|
|
|
|
|
if (!mediaStream.IsExternal)
|
|
|
|
|
{
|
|
|
|
|
mediaStream.Index = startIndex++;
|
|
|
|
|
mediaStreams.Add(mediaStream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-05 05:21:44 +02:00
|
|
|
mediaAttachments = [];
|
|
|
|
|
chapters = [];
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2017-02-21 17:12:32 -05:00
|
|
|
var libraryOptions = _libraryManager.GetLibraryOptions(video);
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2022-12-05 15:01:13 +01:00
|
|
|
if (mediaInfo is not null)
|
2017-12-03 17:12:46 -05:00
|
|
|
{
|
|
|
|
|
FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions);
|
|
|
|
|
FetchPeople(video, mediaInfo, options);
|
|
|
|
|
video.Timestamp = mediaInfo.Timestamp;
|
2019-12-26 23:20:31 +01:00
|
|
|
video.Video3DFormat ??= mediaInfo.Video3DFormat;
|
2017-12-03 17:12:46 -05:00
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2022-01-26 16:09:05 +00:00
|
|
|
if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowText || libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowNone)
|
2021-12-26 16:18:25 +00:00
|
|
|
{
|
2022-01-14 11:24:25 +00:00
|
|
|
_logger.LogDebug("Disabling embedded image subtitles for {Path} due to DisableEmbeddedImageSubtitles setting", video.Path);
|
|
|
|
|
mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && !i.IsTextSubtitleStream);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-26 16:09:05 +00:00
|
|
|
if (libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowImage || libraryOptions.AllowEmbeddedSubtitles == EmbeddedSubtitleOptions.AllowNone)
|
2022-01-14 11:24:25 +00:00
|
|
|
{
|
|
|
|
|
_logger.LogDebug("Disabling embedded text subtitles for {Path} due to DisableEmbeddedTextSubtitles setting", video.Path);
|
|
|
|
|
mediaStreams.RemoveAll(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal && i.IsTextSubtitleStream);
|
2021-12-26 16:18:25 +00:00
|
|
|
}
|
|
|
|
|
|
2014-02-04 15:19:29 -05:00
|
|
|
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
|
|
|
|
|
2019-12-26 23:20:31 +01:00
|
|
|
video.Height = videoStream?.Height ?? 0;
|
|
|
|
|
video.Width = videoStream?.Width ?? 0;
|
2018-09-12 19:26:21 +02:00
|
|
|
|
2022-01-22 15:40:05 +01:00
|
|
|
video.DefaultVideoStreamIndex = videoStream?.Index;
|
2014-02-04 15:19:29 -05:00
|
|
|
|
|
|
|
|
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
|
2016-08-09 01:08:36 -04:00
|
|
|
|
2024-10-09 10:36:08 +00:00
|
|
|
_mediaStreamRepository.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
|
2022-05-04 08:20:48 -06:00
|
|
|
|
2025-07-15 08:40:37 +08:00
|
|
|
_mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
|
2014-06-09 15:16:14 -04:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
|
|
|
|
|
|| options.MetadataRefreshMode == MetadataRefreshMode.Default)
|
2014-02-20 11:37:41 -05:00
|
|
|
{
|
2023-03-14 23:20:12 +01:00
|
|
|
if (_config.Configuration.DummyChapterDuration > 0 && chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
|
2014-06-09 15:16:14 -04:00
|
|
|
{
|
2020-04-01 19:05:41 +02:00
|
|
|
chapters = CreateDummyChapters(video);
|
2014-06-09 15:16:14 -04:00
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2014-07-11 22:31:08 -04:00
|
|
|
NormalizeChapterNames(chapters);
|
|
|
|
|
|
2016-10-02 00:31:47 -04:00
|
|
|
var extractDuringScan = false;
|
2022-12-05 15:01:13 +01:00
|
|
|
if (libraryOptions is not null)
|
2016-08-29 17:06:24 -04:00
|
|
|
{
|
|
|
|
|
extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 14:01:12 +02:00
|
|
|
await _chapterManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2025-04-26 14:01:12 +02:00
|
|
|
_chapterManager.SaveChapters(video, chapters);
|
2014-06-09 15:16:14 -04:00
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2020-02-23 10:53:51 +01:00
|
|
|
private void NormalizeChapterNames(ChapterInfo[] chapters)
|
2014-07-11 22:31:08 -04:00
|
|
|
{
|
2020-02-23 10:53:51 +01:00
|
|
|
for (int i = 0; i < chapters.Length; i++)
|
2014-07-11 22:31:08 -04:00
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
string? name = chapters[i].Name;
|
2014-07-11 22:31:08 -04:00
|
|
|
// Check if the name is empty and/or if the name is a time
|
|
|
|
|
// Some ripping programs do that.
|
2023-08-22 18:09:31 +02:00
|
|
|
if (string.IsNullOrWhiteSpace(name)
|
|
|
|
|
|| TimeSpan.TryParse(name, out _))
|
2014-07-11 22:31:08 -04:00
|
|
|
{
|
2020-02-23 10:53:51 +01:00
|
|
|
chapters[i].Name = string.Format(
|
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
|
_localization.GetLocalizedString("ChapterNameValue"),
|
|
|
|
|
(i + 1).ToString(CultureInfo.InvariantCulture));
|
2014-07-11 22:31:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-18 14:42:35 +01:00
|
|
|
private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
|
2023-02-03 18:49:23 +01:00
|
|
|
{
|
2024-02-28 14:10:44 -07:00
|
|
|
var ffmpegVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
|
2025-05-09 14:35:54 +00:00
|
|
|
var externalStreams = mediaStreams.Where(s => s.IsExternal).ToList();
|
2023-02-03 18:49:23 +01:00
|
|
|
|
2023-02-05 17:24:13 +01:00
|
|
|
// Fill video properties from the BDInfo result
|
|
|
|
|
mediaStreams.Clear();
|
2025-05-09 14:35:54 +00:00
|
|
|
|
|
|
|
|
// Rebuild the list with external streams first
|
|
|
|
|
int index = 0;
|
|
|
|
|
foreach (var stream in externalStreams.Concat(blurayInfo.MediaStreams))
|
|
|
|
|
{
|
|
|
|
|
stream.Index = index++;
|
|
|
|
|
mediaStreams.Add(stream);
|
|
|
|
|
}
|
2023-02-03 18:49:23 +01:00
|
|
|
|
2023-02-05 17:24:13 +01:00
|
|
|
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++)
|
2023-02-03 18:49:23 +01:00
|
|
|
{
|
2023-02-05 17:24:13 +01:00
|
|
|
chapters[i] = new ChapterInfo
|
2023-02-03 18:49:23 +01:00
|
|
|
{
|
2023-02-05 17:24:13 +01:00
|
|
|
StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
|
|
|
|
|
};
|
2023-02-03 18:49:23 +01:00
|
|
|
}
|
2023-02-05 17:24:13 +01:00
|
|
|
}
|
2023-02-03 18:49:23 +01:00
|
|
|
|
2024-02-28 14:10:44 -07:00
|
|
|
var blurayVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
|
2023-02-03 18:49:23 +01:00
|
|
|
|
2023-02-05 17:24:13 +01:00
|
|
|
// Use the ffprobe values if these are empty
|
2024-02-28 14:10:44 -07:00
|
|
|
if (blurayVideoStream is not null && ffmpegVideoStream is not null)
|
2023-02-05 17:24:13 +01:00
|
|
|
{
|
2024-02-28 14:10:44 -07:00
|
|
|
// Always use ffmpeg's detected codec since that is what the rest of the codebase expects.
|
|
|
|
|
blurayVideoStream.Codec = ffmpegVideoStream.Codec;
|
|
|
|
|
blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRate : blurayVideoStream.BitRate;
|
|
|
|
|
blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Width;
|
2025-01-22 17:31:52 +01:00
|
|
|
blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Height : blurayVideoStream.Height;
|
2024-06-24 20:29:06 -04:00
|
|
|
blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
|
|
|
|
|
blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
|
|
|
|
|
blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
|
|
|
|
|
blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
|
2023-02-03 18:49:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets information about the longest playlist on a bdrom.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="path">The path.</param>
|
|
|
|
|
/// <returns>VideoStream.</returns>
|
2023-08-22 18:09:31 +02:00
|
|
|
private BlurayDiscInfo? GetBDInfo(string path)
|
2023-02-03 18:49:23 +01:00
|
|
|
{
|
2023-02-05 17:24:13 +01:00
|
|
|
ArgumentException.ThrowIfNullOrEmpty(path);
|
2023-02-03 18:49:23 +01:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _blurayExaminer.GetDiscInfo(path);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error getting BDInfo");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-21 17:12:32 -05:00
|
|
|
private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions refreshOptions, LibraryOptions libraryOptions)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
var replaceData = refreshOptions.ReplaceAllMetadata;
|
2015-04-20 14:04:02 -04:00
|
|
|
|
2020-06-09 23:12:53 +01:00
|
|
|
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (string.IsNullOrWhiteSpace(video.OfficialRating) || replaceData)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2015-04-04 15:35:29 -04:00
|
|
|
video.OfficialRating = data.OfficialRating;
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-09 23:12:53 +01:00
|
|
|
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
|
2015-03-05 01:34:36 -05:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (video.Genres.Length == 0 || replaceData)
|
2015-03-05 01:34:36 -05:00
|
|
|
{
|
2025-05-05 05:21:44 +02:00
|
|
|
video.Genres = [];
|
2015-04-20 14:04:02 -04:00
|
|
|
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
foreach (var genre in data.Genres.Trimmed())
|
2015-04-20 14:04:02 -04:00
|
|
|
{
|
|
|
|
|
video.AddGenre(genre);
|
|
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-09 23:12:53 +01:00
|
|
|
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (video.Studios.Length == 0 || replaceData)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2017-08-09 15:56:38 -04:00
|
|
|
video.SetStudios(data.Studios);
|
2015-03-05 01:34:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-22 21:59:17 +01:00
|
|
|
if (!video.IsLocked && video is MusicVideo musicVideo)
|
2021-04-04 23:34:29 +02:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (string.IsNullOrEmpty(musicVideo.Album) || replaceData)
|
|
|
|
|
{
|
|
|
|
|
musicVideo.Album = data.Album;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (musicVideo.Artists.Count == 0 || replaceData)
|
|
|
|
|
{
|
|
|
|
|
musicVideo.Artists = data.Artists;
|
|
|
|
|
}
|
2021-04-04 23:34:29 +02:00
|
|
|
}
|
|
|
|
|
|
2015-04-04 15:35:29 -04:00
|
|
|
if (data.ProductionYear.HasValue)
|
2015-03-05 01:34:36 -05:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (!video.ProductionYear.HasValue || replaceData)
|
2015-04-20 14:04:02 -04:00
|
|
|
{
|
|
|
|
|
video.ProductionYear = data.ProductionYear;
|
|
|
|
|
}
|
2015-04-04 15:35:29 -04:00
|
|
|
}
|
2020-06-16 09:43:52 +12:00
|
|
|
|
2015-04-04 15:35:29 -04:00
|
|
|
if (data.PremiereDate.HasValue)
|
|
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (!video.PremiereDate.HasValue || replaceData)
|
2015-04-20 14:04:02 -04:00
|
|
|
{
|
|
|
|
|
video.PremiereDate = data.PremiereDate;
|
|
|
|
|
}
|
2015-04-04 15:35:29 -04:00
|
|
|
}
|
2020-06-16 09:43:52 +12:00
|
|
|
|
2015-04-04 15:35:29 -04:00
|
|
|
if (data.IndexNumber.HasValue)
|
|
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (!video.IndexNumber.HasValue || replaceData)
|
2015-04-20 14:04:02 -04:00
|
|
|
{
|
|
|
|
|
video.IndexNumber = data.IndexNumber;
|
|
|
|
|
}
|
2015-04-04 15:35:29 -04:00
|
|
|
}
|
2020-06-16 09:43:52 +12:00
|
|
|
|
2015-04-04 15:35:29 -04:00
|
|
|
if (data.ParentIndexNumber.HasValue)
|
|
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (!video.ParentIndexNumber.HasValue || replaceData)
|
2015-04-20 14:04:02 -04:00
|
|
|
{
|
|
|
|
|
video.ParentIndexNumber = data.ParentIndexNumber;
|
|
|
|
|
}
|
2015-04-04 15:35:29 -04:00
|
|
|
}
|
2017-02-21 13:04:18 -05:00
|
|
|
|
2020-06-09 23:12:53 +01:00
|
|
|
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
|
2016-02-17 21:55:15 -05:00
|
|
|
{
|
2017-02-21 17:12:32 -05:00
|
|
|
if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
|
2016-02-17 21:55:15 -05:00
|
|
|
{
|
2022-05-17 23:00:13 +02:00
|
|
|
// Separate option to use the embedded name for extras because it will often be the same name as the movie
|
|
|
|
|
if (!video.ExtraType.HasValue || libraryOptions.EnableEmbeddedExtrasTitles)
|
2016-05-01 16:56:26 -04:00
|
|
|
{
|
2017-02-21 17:12:32 -05:00
|
|
|
video.Name = data.Name;
|
2016-05-01 16:56:26 -04:00
|
|
|
}
|
2016-02-17 21:55:15 -05:00
|
|
|
}
|
2021-04-04 23:34:29 +02:00
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
|
|
|
|
|
{
|
|
|
|
|
video.ForcedSortName = data.ForcedSortName;
|
|
|
|
|
}
|
2016-02-17 21:55:15 -05:00
|
|
|
}
|
2015-03-05 01:34:36 -05:00
|
|
|
|
2015-04-04 15:35:29 -04:00
|
|
|
// If we don't have a ProductionYear try and get it from PremiereDate
|
|
|
|
|
if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
|
|
|
|
|
{
|
|
|
|
|
video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
2015-03-05 01:34:36 -05:00
|
|
|
|
2020-06-09 23:12:53 +01:00
|
|
|
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
|
2015-03-05 01:34:36 -05:00
|
|
|
{
|
2022-01-22 21:59:17 +01:00
|
|
|
if (string.IsNullOrWhiteSpace(video.Overview) || replaceData)
|
2015-03-05 01:34:36 -05:00
|
|
|
{
|
2015-04-04 15:35:29 -04:00
|
|
|
video.Overview = data.Overview;
|
2015-03-05 01:34:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2017-08-26 20:32:33 -04:00
|
|
|
private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
|
2015-06-28 13:00:36 -04:00
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
if (video.IsLocked
|
|
|
|
|
|| video.LockedFields.Contains(MetadataField.Cast)
|
|
|
|
|
|| data.People.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-06-28 13:00:36 -04:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
|
2015-06-28 13:00:36 -04:00
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
var people = new List<PersonInfo>();
|
2015-06-28 13:00:36 -04:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
foreach (var person in data.People)
|
|
|
|
|
{
|
2025-07-05 05:22:27 +12:00
|
|
|
if (!string.IsNullOrWhiteSpace(person.Name))
|
2015-06-28 13:00:36 -04:00
|
|
|
{
|
2025-07-05 05:22:27 +12:00
|
|
|
PeopleHelper.AddPerson(people, new PersonInfo
|
|
|
|
|
{
|
|
|
|
|
Name = person.Name,
|
|
|
|
|
Type = person.Type,
|
2025-11-17 14:08:59 -05:00
|
|
|
Role = person.Role?.Trim()
|
2025-07-05 05:22:27 +12:00
|
|
|
});
|
|
|
|
|
}
|
2015-06-28 13:00:36 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
_libraryManager.UpdatePeople(video, people);
|
|
|
|
|
}
|
2014-08-10 18:13:17 -04:00
|
|
|
}
|
|
|
|
|
|
2014-02-04 15:19:29 -05:00
|
|
|
/// <summary>
|
|
|
|
|
/// Adds the external subtitles.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="video">The video.</param>
|
|
|
|
|
/// <param name="currentStreams">The current streams.</param>
|
2017-02-21 17:12:32 -05:00
|
|
|
/// <param name="options">The refreshOptions.</param>
|
2014-05-17 00:24:10 -04:00
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
|
|
|
/// <returns>Task.</returns>
|
2022-01-27 14:21:53 +01:00
|
|
|
private async Task AddExternalSubtitlesAsync(
|
2020-04-11 12:29:04 +02:00
|
|
|
Video video,
|
2014-06-09 15:16:14 -04:00
|
|
|
List<MediaStream> currentStreams,
|
|
|
|
|
MetadataRefreshOptions options,
|
|
|
|
|
CancellationToken cancellationToken)
|
2014-05-06 22:28:19 -04:00
|
|
|
{
|
2015-09-23 00:00:30 -04:00
|
|
|
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
|
2023-01-11 10:36:18 +01:00
|
|
|
var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken).ConfigureAwait(false);
|
2014-06-09 15:16:14 -04:00
|
|
|
|
2014-07-03 22:22:57 -04:00
|
|
|
var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
|
2014-06-09 15:16:14 -04:00
|
|
|
options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
|
2014-05-06 22:28:19 -04:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
var subtitleOptions = _config.GetConfiguration<SubtitleOptions>("subtitles");
|
2014-08-10 18:13:17 -04:00
|
|
|
|
2018-09-12 19:26:21 +02:00
|
|
|
var libraryOptions = _libraryManager.GetLibraryOptions(video);
|
|
|
|
|
|
|
|
|
|
string[] subtitleDownloadLanguages;
|
2020-02-23 10:53:51 +01:00
|
|
|
bool skipIfEmbeddedSubtitlesPresent;
|
|
|
|
|
bool skipIfAudioTrackMatches;
|
|
|
|
|
bool requirePerfectMatch;
|
2018-09-12 19:26:21 +02:00
|
|
|
bool enabled;
|
|
|
|
|
|
2022-12-05 15:00:20 +01:00
|
|
|
if (libraryOptions.SubtitleDownloadLanguages is null)
|
2018-09-12 19:26:21 +02:00
|
|
|
{
|
|
|
|
|
subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
|
2020-02-23 10:53:51 +01:00
|
|
|
skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
|
|
|
|
|
skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
|
|
|
|
|
requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
|
2018-09-12 19:26:21 +02:00
|
|
|
enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
|
2014-05-06 22:28:19 -04:00
|
|
|
video is Episode) ||
|
2014-08-10 18:13:17 -04:00
|
|
|
(subtitleOptions.DownloadMovieSubtitles &&
|
2018-09-12 19:26:21 +02:00
|
|
|
video is Movie);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
|
2020-02-23 10:53:51 +01:00
|
|
|
skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
|
|
|
|
skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
|
|
|
|
requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
2018-09-12 19:26:21 +02:00
|
|
|
enabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (enableSubtitleDownloading && enabled)
|
2014-05-06 22:28:19 -04:00
|
|
|
{
|
2020-09-07 13:20:39 +02:00
|
|
|
var downloadedLanguages = await new SubtitleDownloader(
|
|
|
|
|
_logger,
|
|
|
|
|
_subtitleManager).DownloadSubtitles(
|
|
|
|
|
video,
|
|
|
|
|
currentStreams.Concat(externalSubtitleStreams).ToList(),
|
|
|
|
|
skipIfEmbeddedSubtitlesPresent,
|
|
|
|
|
skipIfAudioTrackMatches,
|
|
|
|
|
requirePerfectMatch,
|
|
|
|
|
subtitleDownloadLanguages,
|
|
|
|
|
libraryOptions.DisabledSubtitleFetchers,
|
|
|
|
|
libraryOptions.SubtitleFetcherOrder,
|
2021-10-19 21:06:05 +02:00
|
|
|
true,
|
2020-09-07 13:20:39 +02:00
|
|
|
cancellationToken).ConfigureAwait(false);
|
2014-05-06 22:28:19 -04:00
|
|
|
|
|
|
|
|
// Rescan
|
|
|
|
|
if (downloadedLanguages.Count > 0)
|
|
|
|
|
{
|
2023-01-11 10:36:18 +01:00
|
|
|
externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, true, cancellationToken).ConfigureAwait(false);
|
2014-05-06 22:28:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 22:02:46 +01:00
|
|
|
video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).Distinct().ToArray();
|
2014-05-06 22:28:19 -04:00
|
|
|
|
|
|
|
|
currentStreams.AddRange(externalSubtitleStreams);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-22 21:47:52 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Adds the external audio.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="video">The video.</param>
|
|
|
|
|
/// <param name="currentStreams">The current streams.</param>
|
|
|
|
|
/// <param name="options">The refreshOptions.</param>
|
|
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
2021-12-08 16:49:20 +01:00
|
|
|
private async Task AddExternalAudioAsync(
|
2021-11-22 21:47:52 +01:00
|
|
|
Video video,
|
|
|
|
|
List<MediaStream> currentStreams,
|
|
|
|
|
MetadataRefreshOptions options,
|
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
2021-11-24 21:53:36 +01:00
|
|
|
var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1;
|
2022-02-17 09:03:08 +01:00
|
|
|
var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken).ConfigureAwait(false);
|
2021-11-22 21:47:52 +01:00
|
|
|
|
2022-03-10 22:02:46 +01:00
|
|
|
video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
|
2021-11-22 21:47:52 +01:00
|
|
|
|
2022-03-10 22:02:46 +01:00
|
|
|
currentStreams.AddRange(externalAudioStreams);
|
2021-11-22 21:47:52 +01:00
|
|
|
}
|
|
|
|
|
|
2014-02-04 15:19:29 -05:00
|
|
|
/// <summary>
|
2020-04-01 19:05:41 +02:00
|
|
|
/// Creates dummy chapters.
|
2014-02-04 15:19:29 -05:00
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="video">The video.</param>
|
2020-06-19 20:24:13 +02:00
|
|
|
/// <returns>An array of dummy chapters.</returns>
|
2023-08-22 18:09:31 +02:00
|
|
|
internal ChapterInfo[] CreateDummyChapters(Video video)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2023-08-22 18:09:31 +02:00
|
|
|
var runtime = video.RunTimeTicks.GetValueOrDefault();
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2025-01-28 05:27:34 -05:00
|
|
|
// Only process files with a runtime greater than 0 and less than 12h. The latter are likely corrupted.
|
2023-03-14 23:20:12 +01:00
|
|
|
if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2020-02-23 10:53:51 +01:00
|
|
|
throw new ArgumentException(
|
|
|
|
|
string.Format(
|
|
|
|
|
CultureInfo.InvariantCulture,
|
2023-03-14 23:20:12 +01:00
|
|
|
"{0} has an invalid runtime of {1} minutes",
|
2020-02-23 10:53:51 +01:00
|
|
|
video.Name,
|
2023-08-22 18:09:31 +02:00
|
|
|
TimeSpan.FromTicks(runtime).TotalMinutes));
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
|
2023-03-14 23:20:12 +01:00
|
|
|
long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
|
2023-08-22 18:09:31 +02:00
|
|
|
if (runtime <= dummyChapterDuration)
|
2014-02-04 15:19:29 -05:00
|
|
|
{
|
2025-05-05 05:21:44 +02:00
|
|
|
return [];
|
2023-08-22 18:09:31 +02:00
|
|
|
}
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
int chapterCount = (int)(runtime / dummyChapterDuration);
|
|
|
|
|
var chapters = new ChapterInfo[chapterCount];
|
2023-03-14 23:20:12 +01:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
long currentChapterTicks = 0;
|
|
|
|
|
for (int i = 0; i < chapterCount; i++)
|
|
|
|
|
{
|
|
|
|
|
chapters[i] = new ChapterInfo
|
|
|
|
|
{
|
|
|
|
|
StartPositionTicks = currentChapterTicks
|
|
|
|
|
};
|
2014-02-04 15:19:29 -05:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
currentChapterTicks += dummyChapterDuration;
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
2020-04-01 19:05:41 +02:00
|
|
|
|
2023-08-22 18:09:31 +02:00
|
|
|
return chapters;
|
2014-02-04 15:19:29 -05:00
|
|
|
}
|
|
|
|
|
}
|
2018-12-13 14:18:25 +01:00
|
|
|
}
|