Merge branch 'master' into sort-nfo-data

This commit is contained in:
Marc Brooks
2025-02-03 19:48:59 -06:00
committed by GitHub
468 changed files with 25086 additions and 9927 deletions

View File

@@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.BoxSets
protected override bool EnableUpdatingPremiereDateFromChildren => true;
/// <inheritdoc />
protected override IList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item)
protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item)
{
return item.GetLinkedChildren();
}
@@ -54,7 +54,14 @@ namespace MediaBrowser.Providers.BoxSets
if (mergeMetadataSettings)
{
targetItem.LinkedChildren = sourceItem.LinkedChildren;
if (replaceData || targetItem.LinkedChildren.Length == 0)
{
targetItem.LinkedChildren = sourceItem.LinkedChildren;
}
else
{
targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray();
}
}
}

View File

@@ -1,26 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Providers.Chapters
{
public class ChapterManager : IChapterManager
{
private readonly IItemRepository _itemRepo;
public ChapterManager(IItemRepository itemRepo)
{
_itemRepo = itemRepo;
}
/// <inheritdoc />
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
{
_itemRepo.SaveChapters(itemId, chapters);
}
}
}

View File

@@ -162,7 +162,7 @@ public class LyricScheduledTask : IScheduledTask
[
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerInterval,
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromHours(24).Ticks
}
];

View File

@@ -291,6 +291,7 @@ namespace MediaBrowser.Providers.Manager
var fileStreamOptions = AsyncFile.WriteOptions;
fileStreamOptions.Mode = FileMode.Create;
fileStreamOptions.Options = FileOptions.WriteThrough;
if (source.CanSeek)
{
fileStreamOptions.PreallocationSize = source.Length;

View File

@@ -229,9 +229,7 @@ namespace MediaBrowser.Providers.Manager
{
var mimeType = MimeTypes.GetMimeType(response.Path);
var stream = AsyncFile.OpenRead(response.Path);
await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, null, cancellationToken).ConfigureAwait(false);
}
}
@@ -387,8 +385,8 @@ namespace MediaBrowser.Providers.Manager
item.RemoveImages(images);
// Cleanup old metadata directory for episodes if empty
if (item is Episode)
// Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item
if (item is Episode && !item.IsVirtualItem)
{
var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any())

View File

@@ -74,10 +74,11 @@ namespace MediaBrowser.Providers.Manager
public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
var itemOfType = (TItemType)item;
var updateType = ItemUpdateType.None;
var libraryOptions = LibraryManager.GetLibraryOptions(item);
var isFirstRefresh = item.DateLastRefreshed == default;
var hasRefreshedMetadata = true;
var hasRefreshedImages = true;
var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays;
@@ -131,9 +132,10 @@ namespace MediaBrowser.Providers.Manager
People = LibraryManager.GetPeople(item)
};
bool hasRefreshedMetadata = true;
bool hasRefreshedImages = true;
var isFirstRefresh = item.DateLastRefreshed == default;
var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
updateType |= beforeSaveResult;
updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false);
// Next run metadata providers
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
@@ -188,43 +190,43 @@ namespace MediaBrowser.Providers.Manager
}
}
var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
updateType |= beforeSaveResult;
// Save if changes were made, or it's never been saved before
if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
if (hasRefreshedMetadata && hasRefreshedImages)
{
if (item.IsFileProtocol)
{
var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
if (file is not null)
{
item.DateModified = file.LastWriteTimeUtc;
}
}
// If any of these properties are set then make sure the updateType is not None, just to force everything to save
if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
{
updateType |= ItemUpdateType.MetadataDownload;
}
if (hasRefreshedMetadata && hasRefreshedImages)
{
item.DateLastRefreshed = DateTime.UtcNow;
}
else
{
item.DateLastRefreshed = default;
}
// Save to database
await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
item.DateLastRefreshed = DateTime.UtcNow;
}
updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false);
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
return updateType;
async Task<ItemUpdateType> SaveInternal(BaseItem item, MetadataRefreshOptions refreshOptions, ItemUpdateType updateType, bool isFirstRefresh, bool requiresRefresh, MetadataResult<TItemType> metadataResult, CancellationToken cancellationToken)
{
// Save if changes were made, or it's never been saved before
if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
{
if (item.IsFileProtocol)
{
var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
if (file is not null)
{
item.DateModified = file.LastWriteTimeUtc;
}
}
// If any of these properties are set then make sure the updateType is not None, just to force everything to save
if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
{
updateType |= ItemUpdateType.MetadataDownload;
}
// Save to database
await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
}
return updateType;
}
}
private void ApplySearchResult(ItemLookupInfo lookupInfo, RemoteSearchResult result)
@@ -322,17 +324,17 @@ namespace MediaBrowser.Providers.Manager
return false;
}
protected virtual IList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
protected virtual IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
{
if (item is Folder folder)
{
return folder.GetRecursiveChildren();
}
return Array.Empty<BaseItem>();
return [];
}
protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = ItemUpdateType.None;
@@ -371,7 +373,7 @@ namespace MediaBrowser.Providers.Manager
return updateType;
}
private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IList<BaseItem> children)
private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IReadOnlyList<BaseItem> children)
{
if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks)
{
@@ -395,7 +397,7 @@ namespace MediaBrowser.Providers.Manager
return ItemUpdateType.None;
}
private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IList<BaseItem> children)
private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IReadOnlyList<BaseItem> children)
{
var updateType = ItemUpdateType.None;
@@ -429,7 +431,7 @@ namespace MediaBrowser.Providers.Manager
return updateType;
}
private ItemUpdateType UpdatePremiereDate(TItemType item, IList<BaseItem> children)
private ItemUpdateType UpdatePremiereDate(TItemType item, IReadOnlyList<BaseItem> children)
{
var updateType = ItemUpdateType.None;
@@ -467,7 +469,7 @@ namespace MediaBrowser.Providers.Manager
return updateType;
}
private ItemUpdateType UpdateGenres(TItemType item, IList<BaseItem> children)
private ItemUpdateType UpdateGenres(TItemType item, IReadOnlyList<BaseItem> children)
{
var updateType = ItemUpdateType.None;
@@ -488,7 +490,7 @@ namespace MediaBrowser.Providers.Manager
return updateType;
}
private ItemUpdateType UpdateStudios(TItemType item, IList<BaseItem> children)
private ItemUpdateType UpdateStudios(TItemType item, IReadOnlyList<BaseItem> children)
{
var updateType = ItemUpdateType.None;
@@ -509,7 +511,7 @@ namespace MediaBrowser.Providers.Manager
return updateType;
}
private ItemUpdateType UpdateOfficialRating(TItemType item, IList<BaseItem> children)
private ItemUpdateType UpdateOfficialRating(TItemType item, IReadOnlyList<BaseItem> children)
{
var updateType = ItemUpdateType.None;
@@ -675,6 +677,7 @@ namespace MediaBrowser.Providers.Manager
};
temp.Item.Path = item.Path;
temp.Item.Id = item.Id;
temp.Item.ParentIndexNumber = item.ParentIndexNumber;
temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
@@ -728,7 +731,7 @@ namespace MediaBrowser.Providers.Manager
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
MergeData(localItem, temp, Array.Empty<MetadataField>(), false, true);
MergeData(localItem, temp, [], false, true);
refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
break;
@@ -768,7 +771,7 @@ namespace MediaBrowser.Providers.Manager
if (!options.RemoveOldMetadata)
{
// Add existing metadata to provider result if it does not exist there
MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false);
MergeData(metadata, temp, [], false, false);
}
if (isLocalLocked)
@@ -837,7 +840,7 @@ namespace MediaBrowser.Providers.Manager
{
result.Provider = provider.Name;
MergeData(result, temp, Array.Empty<MetadataField>(), replaceData, false);
MergeData(result, temp, [], replaceData, false);
MergeNewData(temp.Item, id);
refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
@@ -1141,13 +1144,8 @@ namespace MediaBrowser.Providers.Manager
}
}
private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target)
private static void MergePeople(IReadOnlyList<PersonInfo> source, IReadOnlyList<PersonInfo> target)
{
if (target is null)
{
target = new List<PersonInfo>();
}
foreach (var person in target)
{
var normalizedName = person.Name.RemoveDiacritics();

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using AsyncKeyedLock;
@@ -47,7 +48,7 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
public class ProviderManager : IProviderManager, IDisposable
{
private readonly object _refreshQueueLock = new();
private readonly Lock _refreshQueueLock = new();
private readonly ILogger<ProviderManager> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryMonitor _libraryMonitor;
@@ -199,11 +200,19 @@ namespace MediaBrowser.Providers.Manager
// TODO: Isolate this hack into the tvh plugin
if (string.IsNullOrEmpty(contentType))
{
// Special case for imagecache
if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
{
contentType = MediaTypeNames.Image.Png;
}
else
{
// Deduce content type from file extension
contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
}
// Throw if we still can't determine the content type
if (string.IsNullOrEmpty(contentType))
{
throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode);
}
@@ -251,15 +260,31 @@ namespace MediaBrowser.Providers.Manager
}
/// <inheritdoc/>
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(source))
{
throw new ArgumentNullException(nameof(source));
}
var fileStream = AsyncFile.OpenRead(source);
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
try
{
var fileStream = AsyncFile.OpenRead(source);
await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
.SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken)
.ConfigureAwait(false);
}
finally
{
try
{
File.Delete(source);
}
catch (Exception ex)
{
_logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
}
}
}
/// <inheritdoc/>
@@ -1016,7 +1041,6 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
{
ArgumentNullException.ThrowIfNull(itemId);
if (itemId.IsEmpty())
{
throw new ArgumentException("Guid can't be empty", nameof(itemId));

View File

@@ -23,15 +23,14 @@
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="PlaylistsNET" />
<PackageReference Include="z440.atl.core"/>
<PackageReference Include="z440.atl.core" />
<PackageReference Include="TMDbLib" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<!-- Code Analyzers -->

View File

@@ -17,6 +17,7 @@ using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@@ -28,6 +29,7 @@ namespace MediaBrowser.Providers.MediaInfo
public class AudioFileProber
{
private const char InternalValueSeparator = '\u001F';
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
private readonly ILibraryManager _libraryManager;
@@ -35,6 +37,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IMediaSourceManager _mediaSourceManager;
private readonly LyricResolver _lyricResolver;
private readonly ILyricManager _lyricManager;
private readonly IMediaStreamRepository _mediaStreamRepository;
/// <summary>
/// Initializes a new instance of the <see cref="AudioFileProber"/> class.
@@ -46,6 +49,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="lyricResolver">Instance of the <see cref="LyricResolver"/> interface.</param>
/// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
/// <param name="mediaStreamRepository">Instance of the <see cref="IMediaStreamRepository"/>.</param>
public AudioFileProber(
ILogger<AudioFileProber> logger,
IMediaSourceManager mediaSourceManager,
@@ -53,7 +57,8 @@ namespace MediaBrowser.Providers.MediaInfo
IItemRepository itemRepo,
ILibraryManager libraryManager,
LyricResolver lyricResolver,
ILyricManager lyricManager)
ILyricManager lyricManager,
IMediaStreamRepository mediaStreamRepository)
{
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo;
@@ -62,7 +67,10 @@ namespace MediaBrowser.Providers.MediaInfo
_mediaSourceManager = mediaSourceManager;
_lyricResolver = lyricResolver;
_lyricManager = lyricManager;
_mediaStreamRepository = mediaStreamRepository;
ATL.Settings.DisplayValueSeparator = InternalValueSeparator;
ATL.Settings.UseFileNameWhenNoTitle = false;
ATL.Settings.ID3v2_separatev2v3Values = false;
}
/// <summary>
@@ -146,7 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo
audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric);
_itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
_mediaStreamRepository.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
}
/// <summary>
@@ -161,12 +169,13 @@ namespace MediaBrowser.Providers.MediaInfo
var libraryOptions = _libraryManager.GetLibraryOptions(audio);
Track track = new Track(audio.Path);
// ATL will fall back to filename as title when it does not understand the metadata
if (track.MetadataFormats.All(mf => mf.Equals(ATL.Factory.UNKNOWN_FORMAT)))
if (track.MetadataFormats
.All(mf => string.Equals(mf.ShortName, "ID3v1", StringComparison.OrdinalIgnoreCase)))
{
track.Title = mediaInfo.Name;
_logger.LogWarning("File {File} only has ID3v1 tags, some fields may be truncated", audio.Path);
}
track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
track.Year ??= mediaInfo.ProductionYear;
track.TrackNumber ??= mediaInfo.IndexNumber;
@@ -175,11 +184,11 @@ namespace MediaBrowser.Providers.MediaInfo
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{
var people = new List<PersonInfo>();
var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? mediaInfo.AlbumArtists : track.AlbumArtist.Split(InternalValueSeparator);
var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? [] : track.AlbumArtist.Split(InternalValueSeparator);
if (libraryOptions.UseCustomTagDelimiters)
{
albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray();
albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
}
foreach (var albumArtist in albumArtists)
@@ -206,12 +215,12 @@ namespace MediaBrowser.Providers.MediaInfo
if (performers is null || performers.Length == 0)
{
performers = string.IsNullOrEmpty(track.Artist) ? mediaInfo.Artists : track.Artist.Split(InternalValueSeparator);
performers = string.IsNullOrEmpty(track.Artist) ? [] : track.Artist.Split(InternalValueSeparator);
}
if (libraryOptions.UseCustomTagDelimiters)
{
performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray();
performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
}
foreach (var performer in performers)
@@ -310,11 +319,11 @@ namespace MediaBrowser.Providers.MediaInfo
if (!audio.LockedFields.Contains(MetadataField.Genres))
{
var genres = string.IsNullOrEmpty(track.Genre) ? mediaInfo.Genres : track.Genre.Split(InternalValueSeparator);
var genres = string.IsNullOrEmpty(track.Genre) ? [] : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
if (libraryOptions.UseCustomTagDelimiters)
{
genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray();
genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
}
genres = genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
@@ -345,7 +354,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Artist Id", out musicBrainzArtistTag))
&& !string.IsNullOrEmpty(musicBrainzArtistTag))
{
audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzArtistTag);
var id = GetFirstMusicBrainzId(musicBrainzArtistTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, id);
}
}
@@ -355,7 +365,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag))
&& !string.IsNullOrEmpty(musicBrainzReleaseArtistIdTag))
{
audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, musicBrainzReleaseArtistIdTag);
var id = GetFirstMusicBrainzId(musicBrainzReleaseArtistIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, id);
}
}
@@ -365,7 +376,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Album Id", out musicBrainzReleaseIdTag))
&& !string.IsNullOrEmpty(musicBrainzReleaseIdTag))
{
audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, musicBrainzReleaseIdTag);
var id = GetFirstMusicBrainzId(musicBrainzReleaseIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, id);
}
}
@@ -375,7 +387,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag))
&& !string.IsNullOrEmpty(musicBrainzReleaseGroupIdTag))
{
audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, musicBrainzReleaseGroupIdTag);
var id = GetFirstMusicBrainzId(musicBrainzReleaseGroupIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, id);
}
}
@@ -385,7 +398,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Release Track Id", out trackMbId))
&& !string.IsNullOrEmpty(trackMbId))
{
audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, trackMbId);
var id = GetFirstMusicBrainzId(trackMbId, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, id);
}
}
@@ -439,5 +453,18 @@ namespace MediaBrowser.Providers.MediaInfo
return items;
}
// MusicBrainz IDs are multi-value tags, so we need to split them
// However, our current provider can only have one single ID, which means we need to pick the first one
private string? GetFirstMusicBrainzId(string tag, bool useCustomTagDelimiters, char[] tagDelimiters, string[] whitelist)
{
var val = tag.Split(InternalValueSeparator).FirstOrDefault();
if (val is not null && useCustomTagDelimiters)
{
val = SplitWithCustomDelimiter(val, tagDelimiters, whitelist).FirstOrDefault();
}
return val;
}
}
}

View File

@@ -1,4 +1,4 @@
#nullable disable
#pragma warning disable CA1826 // CA1826 Do not use Enumerable methods on Indexable collections.
using System;
using System.Collections.Generic;
@@ -74,18 +74,17 @@ namespace MediaBrowser.Providers.MediaInfo
return GetImage((Audio)item, imageStreams, cancellationToken);
}
private async Task<DynamicImageResponse> GetImage(Audio item, List<MediaStream> imageStreams, CancellationToken cancellationToken)
private async Task<DynamicImageResponse> GetImage(Audio item, IReadOnlyList<MediaStream> imageStreams, CancellationToken cancellationToken)
{
var path = GetAudioImagePath(item);
if (!File.Exists(path))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
var directoryName = Path.GetDirectoryName(path) ?? throw new InvalidOperationException($"Invalid path '{path}'");
Directory.CreateDirectory(directoryName);
var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase)) ??
imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) ??
imageStreams.FirstOrDefault();
var imageStreamIndex = imageStream?.Index;
var tempFile = await _mediaEncoder.ExtractAudioImage(item.Path, imageStreamIndex, cancellationToken).ConfigureAwait(false);

View File

@@ -32,6 +32,7 @@ namespace MediaBrowser.Providers.MediaInfo
public class FFProbeVideoInfo
{
private readonly ILogger<FFProbeVideoInfo> _logger;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
private readonly IBlurayExaminer _blurayExaminer;
@@ -39,11 +40,12 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IEncodingManager _encodingManager;
private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager;
private readonly IChapterManager _chapterManager;
private readonly IChapterRepository _chapterManager;
private readonly ILibraryManager _libraryManager;
private readonly AudioResolver _audioResolver;
private readonly SubtitleResolver _subtitleResolver;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
private readonly IMediaStreamRepository _mediaStreamRepository;
public FFProbeVideoInfo(
ILogger<FFProbeVideoInfo> logger,
@@ -55,10 +57,12 @@ namespace MediaBrowser.Providers.MediaInfo
IEncodingManager encodingManager,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
IChapterManager chapterManager,
IChapterRepository chapterManager,
ILibraryManager libraryManager,
AudioResolver audioResolver,
SubtitleResolver subtitleResolver)
SubtitleResolver subtitleResolver,
IMediaAttachmentRepository mediaAttachmentRepository,
IMediaStreamRepository mediaStreamRepository)
{
_logger = logger;
_mediaSourceManager = mediaSourceManager;
@@ -73,6 +77,9 @@ namespace MediaBrowser.Providers.MediaInfo
_libraryManager = libraryManager;
_audioResolver = audioResolver;
_subtitleResolver = subtitleResolver;
_mediaAttachmentRepository = mediaAttachmentRepository;
_mediaStreamRepository = mediaStreamRepository;
_mediaStreamRepository = mediaStreamRepository;
}
public async Task<ItemUpdateType> ProbeVideo<T>(
@@ -268,11 +275,11 @@ namespace MediaBrowser.Providers.MediaInfo
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
_itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
_mediaStreamRepository.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
if (mediaAttachments.Any())
{
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
_mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
}
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
@@ -355,7 +362,7 @@ namespace MediaBrowser.Providers.MediaInfo
blurayVideoStream.Codec = ffmpegVideoStream.Codec;
blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRate : blurayVideoStream.BitRate;
blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Width;
blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Height;
blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Height : blurayVideoStream.Height;
blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
@@ -628,7 +635,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
var runtime = video.RunTimeTicks.GetValueOrDefault();
// Only process files with a runtime higher than 0 and lower than 12h. The latter are likely corrupted.
// Only process files with a runtime greater than 0 and less than 12h. The latter are likely corrupted.
if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
{
throw new ArgumentException(

View File

@@ -121,7 +121,7 @@ namespace MediaBrowser.Providers.MediaInfo
mediaStream.Index = startIndex++;
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired;
mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired.GetValueOrDefault();
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
}

View File

@@ -61,12 +61,14 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param>
/// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
/// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
/// <param name="chapterManager">Instance of the <see cref="IChapterRepository"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="namingOptions">The <see cref="NamingOptions"/>.</param>
/// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
/// <param name="mediaAttachmentRepository">Instance of the <see cref="IMediaAttachmentRepository"/> interface.</param>
/// <param name="mediaStreamRepository">Instance of the <see cref="IMediaStreamRepository"/> interface.</param>
public ProbeProvider(
IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder,
@@ -76,12 +78,14 @@ namespace MediaBrowser.Providers.MediaInfo
IEncodingManager encodingManager,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
IChapterManager chapterManager,
IChapterRepository chapterManager,
ILibraryManager libraryManager,
IFileSystem fileSystem,
ILoggerFactory loggerFactory,
NamingOptions namingOptions,
ILyricManager lyricManager)
ILyricManager lyricManager,
IMediaAttachmentRepository mediaAttachmentRepository,
IMediaStreamRepository mediaStreamRepository)
{
_logger = loggerFactory.CreateLogger<ProbeProvider>();
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
@@ -101,7 +105,9 @@ namespace MediaBrowser.Providers.MediaInfo
chapterManager,
libraryManager,
_audioResolver,
_subtitleResolver);
_subtitleResolver,
mediaAttachmentRepository,
mediaStreamRepository);
_audioProber = new AudioFileProber(
loggerFactory.CreateLogger<AudioFileProber>(),
@@ -110,7 +116,8 @@ namespace MediaBrowser.Providers.MediaInfo
itemRepo,
libraryManager,
_lyricResolver,
lyricManager);
lyricManager,
mediaStreamRepository);
}
/// <inheritdoc />

View File

@@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.MediaInfo
public async Task<List<string>> DownloadSubtitles(
Video video,
List<MediaStream> mediaStreams,
IReadOnlyList<MediaStream> mediaStreams,
bool skipIfEmbeddedSubtitlesPresent,
bool skipIfAudioTrackMatches,
bool requirePerfectMatch,
@@ -68,7 +68,7 @@ namespace MediaBrowser.Providers.MediaInfo
public Task<bool> DownloadSubtitles(
Video video,
List<MediaStream> mediaStreams,
IReadOnlyList<MediaStream> mediaStreams,
bool skipIfEmbeddedSubtitlesPresent,
bool skipIfAudioTrackMatches,
bool requirePerfectMatch,
@@ -120,7 +120,7 @@ namespace MediaBrowser.Providers.MediaInfo
private async Task<bool> DownloadSubtitles(
Video video,
List<MediaStream> mediaStreams,
IReadOnlyList<MediaStream> mediaStreams,
bool skipIfEmbeddedSubtitlesPresent,
bool skipIfAudioTrackMatches,
bool requirePerfectMatch,

View File

@@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo
return new[]
{
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
};
}
}

View File

@@ -1,3 +1,5 @@
#pragma warning disable CA1826 // CA1826 Do not use Enumerable methods on Indexable collections.
using System;
using System.Collections.Generic;
using System.Linq;

View File

@@ -47,11 +47,11 @@ namespace MediaBrowser.Providers.Music
protected override bool EnableUpdatingStudiosFromChildren => true;
/// <inheritdoc />
protected override IList<BaseItem> GetChildrenForMetadataUpdates(MusicAlbum item)
protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(MusicAlbum item)
=> item.GetRecursiveChildren(i => i is Audio);
/// <inheritdoc />
protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType);

View File

@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Collections.Immutable;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -28,7 +29,7 @@ namespace MediaBrowser.Providers.Music
protected override bool EnableUpdatingGenresFromChildren => true;
/// <inheritdoc />
protected override IList<BaseItem> GetChildrenForMetadataUpdates(MusicArtist item)
protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(MusicArtist item)
{
return item.IsAccessedByName
? item.GetTaggedItems(new InternalItemsQuery

View File

@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Playlists
protected override bool EnableUpdatingStudiosFromChildren => true;
/// <inheritdoc />
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item)
protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(Playlist item)
=> item.GetLinkedChildren();
/// <inheritdoc />

View File

@@ -148,21 +148,19 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
item.Overview = (overview ?? string.Empty).StripHtml();
}
internal Task EnsureInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken)
internal async Task EnsureInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken)
{
var xmlPath = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId);
var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
if (fileInfo.Exists)
if (fileInfo.Exists
&& (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
{
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
{
return Task.CompletedTask;
}
return;
}
return DownloadInfo(musicBrainzReleaseGroupId, cancellationToken);
await DownloadInfo(musicBrainzReleaseGroupId, cancellationToken).ConfigureAwait(false);
}
internal async Task DownloadInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken)

View File

@@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
item.Overview = (overview ?? string.Empty).StripHtml();
}
internal Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken)
internal async Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken)
{
var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
@@ -140,10 +140,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
if (fileInfo.Exists
&& (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
{
return Task.CompletedTask;
return;
}
return DownloadArtistInfo(musicBrainzId, cancellationToken);
await DownloadArtistInfo(musicBrainzId, cancellationToken).ConfigureAwait(false);
}
internal async Task DownloadArtistInfo(string musicBrainzId, CancellationToken cancellationToken)

View File

@@ -101,11 +101,11 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
}
private Task EnsureThumbsList(string file, CancellationToken cancellationToken)
private async Task EnsureThumbsList(string file, CancellationToken cancellationToken)
{
string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
return EnsureList(url, file, _fileSystem, cancellationToken);
await EnsureList(url, file, _fileSystem, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />

View File

@@ -199,7 +199,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
};
movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
movie.TrySetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
if (movieResult.BelongsToCollection is not null)
{
movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture));

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
@@ -12,8 +10,19 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Service to manage episode metadata.
/// </summary>
public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeMetadataService"/> class.
/// </summary>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public EpisodeMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger<EpisodeMetadataService> logger,
@@ -94,6 +103,11 @@ namespace MediaBrowser.Providers.TV
{
targetItem.IndexNumberEnd = sourceItem.IndexNumberEnd;
}
if (replaceData || !targetItem.ParentIndexNumber.HasValue)
{
targetItem.ParentIndexNumber = sourceItem.ParentIndexNumber;
}
}
}
}

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,8 +13,19 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Service to manage season metadata.
/// </summary>
public class SeasonMetadataService : MetadataService<Season, SeasonInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="SeasonMetadataService"/> class.
/// </summary>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public SeasonMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger<SeasonMetadataService> logger,
@@ -71,11 +80,11 @@ namespace MediaBrowser.Providers.TV
}
/// <inheritdoc />
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Season item)
protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(Season item)
=> item.GetEpisodes();
/// <inheritdoc />
protected override ItemUpdateType UpdateMetadataFromChildren(Season item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
protected override ItemUpdateType UpdateMetadataFromChildren(Season item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType);
@@ -87,7 +96,7 @@ namespace MediaBrowser.Providers.TV
return updateType;
}
private ItemUpdateType SaveIsVirtualItem(Season item, IList<BaseItem> episodes)
private ItemUpdateType SaveIsVirtualItem(Season item, IReadOnlyList<BaseItem> episodes)
{
var isVirtualItem = item.LocationType == LocationType.Virtual && (episodes.Count == 0 || episodes.All(i => i.LocationType == LocationType.Virtual));

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -20,10 +18,22 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Service to manage series metadata.
/// </summary>
public class SeriesMetadataService : MetadataService<Series, SeriesInfo>
{
private readonly ILocalizationManager _localizationManager;
/// <summary>
/// Initializes a new instance of the <see cref="SeriesMetadataService"/> class.
/// </summary>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
public SeriesMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger<SeriesMetadataService> logger,
@@ -36,6 +46,7 @@ namespace MediaBrowser.Providers.TV
_localizationManager = localizationManager;
}
/// <inheritdoc />
public override async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
if (item is Series series)
@@ -129,38 +140,39 @@ namespace MediaBrowser.Providers.TV
private void RemoveObsoleteEpisodes(Series series)
{
var episodes = series.GetEpisodes(null, new DtoOptions(), true).OfType<Episode>().ToList();
var numberOfEpisodes = episodes.Count;
// TODO: O(n^2), but can it be done faster without overcomplicating it?
for (var i = 0; i < numberOfEpisodes; i++)
var episodesBySeason = series.GetEpisodes(null, new DtoOptions(), true)
.OfType<Episode>()
.GroupBy(e => e.ParentIndexNumber)
.ToList();
foreach (var seasonEpisodes in episodesBySeason)
{
var currentEpisode = episodes[i];
// The outer loop only examines virtual episodes
if (!currentEpisode.IsVirtualItem)
List<Episode> nonPhysicalEpisodes = [];
List<Episode> physicalEpisodes = [];
foreach (var episode in seasonEpisodes)
{
continue;
if (episode.IsVirtualItem || episode.IsMissingEpisode)
{
nonPhysicalEpisodes.Add(episode);
continue;
}
physicalEpisodes.Add(episode);
}
// Virtual episodes without an episode number are practically orphaned and should be deleted
if (!currentEpisode.IndexNumber.HasValue)
// Only consider non-physical episodes
foreach (var episode in nonPhysicalEpisodes)
{
DeleteEpisode(currentEpisode);
continue;
}
// Episodes without an episode number are practically orphaned and should be deleted
// Episodes with a physical equivalent should be deleted (they are no longer missing)
var shouldKeep = episode.IndexNumber.HasValue && !physicalEpisodes.Any(e => e.ContainsEpisodeNumber(episode.IndexNumber.Value));
for (var j = i + 1; j < numberOfEpisodes; j++)
{
var comparisonEpisode = episodes[j];
// The inner loop is only for "physical" episodes
if (comparisonEpisode.IsVirtualItem
|| currentEpisode.ParentIndexNumber != comparisonEpisode.ParentIndexNumber
|| !comparisonEpisode.ContainsEpisodeNumber(currentEpisode.IndexNumber.Value))
if (shouldKeep)
{
continue;
}
DeleteEpisode(currentEpisode);
break;
DeleteEpisode(episode);
}
}
}

View File

@@ -63,7 +63,7 @@ public class TrickplayImagesTask : IScheduledTask
{
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerDaily,
Type = TaskTriggerInfoType.DailyTrigger,
TimeOfDayTicks = TimeSpan.FromHours(3).Ticks
}
};

View File

@@ -18,8 +18,6 @@ namespace MediaBrowser.Providers.Trickplay;
/// </summary>
public class TrickplayMoveImagesTask : IScheduledTask
{
private const int QueryPageLimit = 100;
private readonly ILogger<TrickplayMoveImagesTask> _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
@@ -62,32 +60,46 @@ public class TrickplayMoveImagesTask : IScheduledTask
/// <inheritdoc />
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
var trickplayItems = await _trickplayManager.GetTrickplayItemsAsync().ConfigureAwait(false);
var query = new InternalItemsQuery
const int Limit = 100;
int itemCount = 0, offset = 0, previousCount;
// This count may not be accurate, but just get something to show progress on the dashboard.
var totalVideoCount = _libraryManager.GetCount(new InternalItemsQuery
{
MediaTypes = [MediaType.Video],
SourceTypes = [SourceType.Library],
IsVirtualItem = false,
IsFolder = false,
Recursive = true,
Limit = QueryPageLimit
Recursive = true
});
var trickplayQuery = new InternalItemsQuery
{
MediaTypes = [MediaType.Video],
SourceTypes = [SourceType.Library],
IsVirtualItem = false,
IsFolder = false
};
var numberOfVideos = _libraryManager.GetCount(query);
var startIndex = 0;
var numComplete = 0;
while (startIndex < numberOfVideos)
do
{
query.StartIndex = startIndex;
var videos = _libraryManager.GetItemList(query).OfType<Video>().ToList();
videos.RemoveAll(i => !trickplayItems.Contains(i.Id));
var trickplayInfos = await _trickplayManager.GetTrickplayItemsAsync(Limit, offset).ConfigureAwait(false);
previousCount = trickplayInfos.Count;
offset += Limit;
foreach (var video in videos)
trickplayQuery.ItemIds = trickplayInfos.Select(i => i.ItemId).Distinct().ToArray();
var items = _libraryManager.GetItemList(trickplayQuery);
foreach (var trickplayInfo in trickplayInfos)
{
cancellationToken.ThrowIfCancellationRequested();
var video = items.OfType<Video>().FirstOrDefault(i => i.Id.Equals(trickplayInfo.ItemId));
if (video is null)
{
continue;
}
itemCount++;
try
{
var libraryOptions = _libraryManager.GetLibraryOptions(video);
@@ -97,13 +109,10 @@ public class TrickplayMoveImagesTask : IScheduledTask
{
_logger.LogError(ex, "Error moving trickplay files for {ItemName}", video.Name);
}
numComplete++;
progress.Report(100d * numComplete / numberOfVideos);
}
startIndex += QueryPageLimit;
}
progress.Report(100d * itemCount / totalVideoCount);
} while (previousCount == Limit);
progress.Report(100);
}