Merge branch 'master' into trickplay

This commit is contained in:
Nick
2023-10-18 19:27:05 -07:00
462 changed files with 8749 additions and 10207 deletions

View File

@@ -0,0 +1,69 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Providers.Lyric;
/// <inheritdoc />
public class DefaultLyricProvider : ILyricProvider
{
private static readonly string[] _lyricExtensions = { ".lrc", ".elrc", ".txt" };
/// <inheritdoc />
public string Name => "DefaultLyricProvider";
/// <inheritdoc />
public ResolverPriority Priority => ResolverPriority.First;
/// <inheritdoc />
public bool HasLyrics(BaseItem item)
{
var path = GetLyricsPath(item);
return path is not null;
}
/// <inheritdoc />
public async Task<LyricFile?> GetLyrics(BaseItem item)
{
var path = GetLyricsPath(item);
if (path is not null)
{
var content = await File.ReadAllTextAsync(path).ConfigureAwait(false);
if (!string.IsNullOrEmpty(content))
{
return new LyricFile(path, content);
}
}
return null;
}
private string? GetLyricsPath(BaseItem item)
{
// Ensure the path to the item is not null
string? itemDirectoryPath = Path.GetDirectoryName(item.Path);
if (itemDirectoryPath is null)
{
return null;
}
// Ensure the directory path exists
if (!Directory.Exists(itemDirectoryPath))
{
return null;
}
foreach (var lyricFilePath in Directory.GetFiles(itemDirectoryPath, $"{Path.GetFileNameWithoutExtension(item.Path)}.*"))
{
if (_lyricExtensions.Contains(Path.GetExtension(lyricFilePath.AsSpan()), StringComparison.OrdinalIgnoreCase))
{
return lyricFilePath;
}
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Providers.Lyric;
/// <summary>
/// Interface ILyricsProvider.
/// </summary>
public interface ILyricProvider
{
/// <summary>
/// Gets a value indicating the provider name.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
ResolverPriority Priority { get; }
/// <summary>
/// Checks if an item has lyrics available.
/// </summary>
/// <param name="item">The media item.</param>
/// <returns>Whether lyrics where found or not.</returns>
bool HasLyrics(BaseItem item);
/// <summary>
/// Gets the lyrics.
/// </summary>
/// <param name="item">The media item.</param>
/// <returns>A task representing found lyrics.</returns>
Task<LyricFile?> GetLyrics(BaseItem item);
}

View File

@@ -3,34 +3,29 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using LrcParser.Model;
using LrcParser.Parser;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.Resolvers;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Lyric;
/// <summary>
/// LRC Lyric Provider.
/// LRC Lyric Parser.
/// </summary>
public class LrcLyricProvider : ILyricProvider
public class LrcLyricParser : ILyricParser
{
private readonly ILogger<LrcLyricProvider> _logger;
private readonly LyricParser _lrcLyricParser;
private static readonly string[] _supportedMediaTypes = { ".lrc", ".elrc" };
private static readonly string[] _acceptedTimeFormats = { "HH:mm:ss", "H:mm:ss", "mm:ss", "m:ss" };
/// <summary>
/// Initializes a new instance of the <see cref="LrcLyricProvider"/> class.
/// Initializes a new instance of the <see cref="LrcLyricParser"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
public LrcLyricProvider(ILogger<LrcLyricProvider> logger)
public LrcLyricParser()
{
_logger = logger;
_lrcLyricParser = new LrcParser.Parser.Lrc.LrcParser();
}
@@ -41,37 +36,25 @@ public class LrcLyricProvider : ILyricProvider
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public ResolverPriority Priority => ResolverPriority.First;
public ResolverPriority Priority => ResolverPriority.Fourth;
/// <inheritdoc />
public IReadOnlyCollection<string> SupportedMediaTypes { get; } = new[] { "lrc", "elrc" };
/// <summary>
/// Opens lyric file for the requested item, and processes it for API return.
/// </summary>
/// <param name="item">The item to to process.</param>
/// <returns>If provider can determine lyrics, returns a <see cref="LyricResponse"/> with or without metadata; otherwise, null.</returns>
public async Task<LyricResponse?> GetLyrics(BaseItem item)
public LyricResponse? ParseLyrics(LyricFile lyrics)
{
string? lyricFilePath = this.GetLyricFilePath(item.Path);
if (string.IsNullOrEmpty(lyricFilePath))
if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan()), StringComparison.OrdinalIgnoreCase))
{
return null;
}
var fileMetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string lrcFileContent = await File.ReadAllTextAsync(lyricFilePath).ConfigureAwait(false);
Song lyricData;
try
{
lyricData = _lrcLyricParser.Decode(lrcFileContent);
lyricData = _lrcLyricParser.Decode(lyrics.Content);
}
catch (Exception ex)
catch (Exception)
{
_logger.LogError(ex, "Error parsing lyric file {LyricFilePath} from {Provider}", lyricFilePath, Name);
// Failed to parse, return null so the next parser will be tried
return null;
}
@@ -84,6 +67,7 @@ public class LrcLyricProvider : ILyricProvider
.Select(x => x.Text)
.ToList();
var fileMetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (string metaDataRow in metaDataRows)
{
var index = metaDataRow.IndexOf(':', StringComparison.OrdinalIgnoreCase);
@@ -130,17 +114,10 @@ public class LrcLyricProvider : ILyricProvider
// Map metaData values from LRC file to LyricMetadata properties
LyricMetadata lyricMetadata = MapMetadataValues(fileMetaData);
return new LyricResponse
{
Metadata = lyricMetadata,
Lyrics = lyricList
};
return new LyricResponse { Metadata = lyricMetadata, Lyrics = lyricList };
}
return new LyricResponse
{
Lyrics = lyricList
};
return new LyricResponse { Lyrics = lyricList };
}
/// <summary>

View File

@@ -12,14 +12,17 @@ namespace MediaBrowser.Providers.Lyric;
public class LyricManager : ILyricManager
{
private readonly ILyricProvider[] _lyricProviders;
private readonly ILyricParser[] _lyricParsers;
/// <summary>
/// Initializes a new instance of the <see cref="LyricManager"/> class.
/// </summary>
/// <param name="lyricProviders">All found lyricProviders.</param>
public LyricManager(IEnumerable<ILyricProvider> lyricProviders)
/// <param name="lyricParsers">All found lyricParsers.</param>
public LyricManager(IEnumerable<ILyricProvider> lyricProviders, IEnumerable<ILyricParser> lyricParsers)
{
_lyricProviders = lyricProviders.OrderBy(i => i.Priority).ToArray();
_lyricParsers = lyricParsers.OrderBy(i => i.Priority).ToArray();
}
/// <inheritdoc />
@@ -27,10 +30,19 @@ public class LyricManager : ILyricManager
{
foreach (ILyricProvider provider in _lyricProviders)
{
var results = await provider.GetLyrics(item).ConfigureAwait(false);
if (results is not null)
var lyrics = await provider.GetLyrics(item).ConfigureAwait(false);
if (lyrics is null)
{
return results;
continue;
}
foreach (ILyricParser parser in _lyricParsers)
{
var result = parser.ParseLyrics(lyrics);
if (result is not null)
{
return result;
}
}
}
@@ -47,7 +59,7 @@ public class LyricManager : ILyricManager
continue;
}
if (provider.GetLyricFilePath(item.Path) is not null)
if (provider.HasLyrics(item))
{
return true;
}

View File

@@ -0,0 +1,44 @@
using System;
using System.IO;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Providers.Lyric;
/// <summary>
/// TXT Lyric Parser.
/// </summary>
public class TxtLyricParser : ILyricParser
{
private static readonly string[] _supportedMediaTypes = { ".lrc", ".elrc", ".txt" };
private static readonly string[] _lineBreakCharacters = { "\r\n", "\r", "\n" };
/// <inheritdoc />
public string Name => "TxtLyricProvider";
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public ResolverPriority Priority => ResolverPriority.Fifth;
/// <inheritdoc />
public LyricResponse? ParseLyrics(LyricFile lyrics)
{
if (!_supportedMediaTypes.Contains(Path.GetExtension(lyrics.Name.AsSpan()), StringComparison.OrdinalIgnoreCase))
{
return null;
}
string[] lyricTextLines = lyrics.Content.Split(_lineBreakCharacters, StringSplitOptions.None);
LyricLine[] lyricList = new LyricLine[lyricTextLines.Length];
for (int lyricLineIndex = 0; lyricLineIndex < lyricTextLines.Length; lyricLineIndex++)
{
lyricList[lyricLineIndex] = new LyricLine(lyricTextLines[lyricLineIndex]);
}
return new LyricResponse { Lyrics = lyricList };
}
}

View File

@@ -1,60 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Providers.Lyric;
/// <summary>
/// TXT Lyric Provider.
/// </summary>
public class TxtLyricProvider : ILyricProvider
{
/// <inheritdoc />
public string Name => "TxtLyricProvider";
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public ResolverPriority Priority => ResolverPriority.Second;
/// <inheritdoc />
public IReadOnlyCollection<string> SupportedMediaTypes { get; } = new[] { "lrc", "elrc", "txt" };
/// <summary>
/// Opens lyric file for the requested item, and processes it for API return.
/// </summary>
/// <param name="item">The item to to process.</param>
/// <returns>If provider can determine lyrics, returns a <see cref="LyricResponse"/>; otherwise, null.</returns>
public async Task<LyricResponse?> GetLyrics(BaseItem item)
{
string? lyricFilePath = this.GetLyricFilePath(item.Path);
if (string.IsNullOrEmpty(lyricFilePath))
{
return null;
}
string[] lyricTextLines = await File.ReadAllLinesAsync(lyricFilePath).ConfigureAwait(false);
if (lyricTextLines.Length == 0)
{
return null;
}
LyricLine[] lyricList = new LyricLine[lyricTextLines.Length];
for (int lyricLineIndex = 0; lyricLineIndex < lyricTextLines.Length; lyricLineIndex++)
{
lyricList[lyricLineIndex] = new LyricLine(lyricTextLines[lyricLineIndex]);
}
return new LyricResponse
{
Lyrics = lyricList
};
}
}

View File

@@ -263,7 +263,11 @@ namespace MediaBrowser.Providers.Manager
var fileStreamOptions = AsyncFile.WriteOptions;
fileStreamOptions.Mode = FileMode.Create;
fileStreamOptions.PreallocationSize = source.Length;
if (source.CanSeek)
{
fileStreamOptions.PreallocationSize = source.Length;
}
var fs = new FileStream(path, fileStreamOptions);
await using (fs.ConfigureAwait(false))
{

View File

@@ -12,7 +12,6 @@ using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
@@ -720,7 +719,7 @@ namespace MediaBrowser.Providers.Manager
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
MergeData(localItem, temp, Array.Empty<MetadataField>(), options.ReplaceAllMetadata, true);
refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
// Only one local provider allowed per item

View File

@@ -765,10 +765,12 @@ namespace MediaBrowser.Providers.Manager
{
try
{
var results = await GetSearchResults(provider, searchInfo.SearchInfo, cancellationToken).ConfigureAwait(false);
var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwait(false);
foreach (var result in results)
{
result.SearchProviderName = provider.Name;
var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.GetProviderId(p.Key), p.Value, StringComparison.OrdinalIgnoreCase)));
if (existingMatch is null)
@@ -800,22 +802,6 @@ namespace MediaBrowser.Providers.Manager
return resultList;
}
private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(
IRemoteSearchProvider<TLookupType> provider,
TLookupType searchInfo,
CancellationToken cancellationToken)
where TLookupType : ItemLookupInfo
{
var results = await provider.GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
foreach (var item in results)
{
item.SearchProviderName = provider.Name;
}
return results;
}
private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
{
return _externalIds.Where(i =>
@@ -957,6 +943,12 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
{
ArgumentNullException.ThrowIfNull(itemId);
if (itemId.Equals(default))
{
throw new ArgumentException("Guid can't be empty", nameof(itemId));
}
if (_disposed)
{
return;

View File

@@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Probes audio files for metadata.
/// </summary>
public class AudioFileProber
public partial class AudioFileProber
{
// Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain).
private const float DefaultLUFSValue = -18;
@@ -58,6 +58,9 @@ namespace MediaBrowser.Providers.MediaInfo
_mediaSourceManager = mediaSourceManager;
}
[GeneratedRegex(@"I:\s+(.*?)\s+LUFS")]
private static partial Regex LUFSRegex();
/// <summary>
/// Probes the specified item for metadata.
/// </summary>
@@ -104,7 +107,6 @@ namespace MediaBrowser.Providers.MediaInfo
if (libraryOptions.EnableLUFSScan)
{
string output;
using (var process = new Process()
{
StartInfo = new ProcessStartInfo
@@ -127,9 +129,10 @@ namespace MediaBrowser.Providers.MediaInfo
throw;
}
output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
using var reader = process.StandardError;
var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
MatchCollection split = Regex.Matches(output, @"I:\s+(.*?)\s+LUFS");
MatchCollection split = LUFSRegex().Matches(output);
if (split.Count != 0)
{
@@ -220,30 +223,39 @@ namespace MediaBrowser.Providers.MediaInfo
var albumArtists = tags.AlbumArtists;
foreach (var albumArtist in albumArtists)
{
PeopleHelper.AddPerson(people, new PersonInfo
if (!string.IsNullOrEmpty(albumArtist))
{
Name = albumArtist,
Type = PersonKind.AlbumArtist
});
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = albumArtist,
Type = PersonKind.AlbumArtist
});
}
}
var performers = tags.Performers;
foreach (var performer in performers)
{
PeopleHelper.AddPerson(people, new PersonInfo
if (!string.IsNullOrEmpty(performer))
{
Name = performer,
Type = PersonKind.Artist
});
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = performer,
Type = PersonKind.Artist
});
}
}
foreach (var composer in tags.Composers)
{
PeopleHelper.AddPerson(people, new PersonInfo
if (!string.IsNullOrEmpty(composer))
{
Name = composer,
Type = PersonKind.Composer
});
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = composer,
Type = PersonKind.Composer
});
}
}
_libraryManager.UpdatePeople(audio, people);

View File

@@ -177,9 +177,11 @@ namespace MediaBrowser.Providers.MediaInfo
var format = imageStream.Codec switch
{
"bmp" => ImageFormat.Bmp,
"gif" => ImageFormat.Gif,
"mjpeg" => ImageFormat.Jpg,
"png" => ImageFormat.Png,
"gif" => ImageFormat.Gif,
"webp" => ImageFormat.Webp,
_ => ImageFormat.Jpg
};
@@ -202,16 +204,10 @@ namespace MediaBrowser.Providers.MediaInfo
? Path.GetExtension(attachmentStream.FileName)
: MimeTypes.ToExtension(attachmentStream.MimeType);
if (string.IsNullOrEmpty(extension))
{
extension = ".jpg";
}
ImageFormat format = extension switch
{
".bmp" => ImageFormat.Bmp,
".gif" => ImageFormat.Gif,
".jpg" => ImageFormat.Jpg,
".png" => ImageFormat.Png,
".webp" => ImageFormat.Webp,
_ => ImageFormat.Jpg

View File

@@ -1,11 +1,8 @@
#nullable disable
#pragma warning disable CA1068, CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -83,9 +80,9 @@ namespace MediaBrowser.Providers.MediaInfo
CancellationToken cancellationToken)
where T : Video
{
BlurayDiscInfo blurayDiscInfo = null;
BlurayDiscInfo? blurayDiscInfo = null;
Model.MediaInfo.MediaInfo mediaInfoResult = null;
Model.MediaInfo.MediaInfo? mediaInfoResult = null;
if (!item.IsShortcut || options.EnableRemoteContentProbe)
{
@@ -131,7 +128,7 @@ namespace MediaBrowser.Providers.MediaInfo
var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path);
// Return if no playable .m2ts files are found
if (blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0)
if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0)
{
_logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
return ItemUpdateType.MetadataImport;
@@ -192,16 +189,14 @@ namespace MediaBrowser.Providers.MediaInfo
protected async Task Fetch(
Video video,
CancellationToken cancellationToken,
Model.MediaInfo.MediaInfo mediaInfo,
BlurayDiscInfo blurayInfo,
Model.MediaInfo.MediaInfo? mediaInfo,
BlurayDiscInfo? blurayInfo,
MetadataRefreshOptions options)
{
List<MediaStream> mediaStreams;
List<MediaStream> mediaStreams = new List<MediaStream>();
IReadOnlyList<MediaAttachment> mediaAttachments;
ChapterInfo[] chapters;
mediaStreams = new List<MediaStream>();
// 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);
@@ -221,18 +216,6 @@ namespace MediaBrowser.Providers.MediaInfo
video.TotalBitrate = mediaInfo.Bitrate;
video.RunTimeTicks = mediaInfo.RunTimeTicks;
video.Size = mediaInfo.Size;
if (video.VideoType == VideoType.VideoFile)
{
var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
video.Container = extension;
}
else
{
video.Container = null;
}
video.Container = mediaInfo.Container;
chapters = mediaInfo.Chapters ?? Array.Empty<ChapterInfo>();
@@ -243,8 +226,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
else
{
var currentMediaStreams = video.GetMediaStreams();
foreach (var mediaStream in currentMediaStreams)
foreach (var mediaStream in video.GetMediaStreams())
{
if (!mediaStream.IsExternal)
{
@@ -295,8 +277,8 @@ namespace MediaBrowser.Providers.MediaInfo
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
}
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh
|| options.MetadataRefreshMode == MetadataRefreshMode.Default)
{
if (_config.Configuration.DummyChapterDuration > 0 && chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
{
@@ -321,11 +303,11 @@ namespace MediaBrowser.Providers.MediaInfo
{
for (int i = 0; i < chapters.Length; i++)
{
string name = chapters[i].Name;
string? name = chapters[i].Name;
// Check if the name is empty and/or if the name is a time
// Some ripping programs do that.
if (string.IsNullOrWhiteSpace(name) ||
TimeSpan.TryParse(name, out _))
if (string.IsNullOrWhiteSpace(name)
|| TimeSpan.TryParse(name, out _))
{
chapters[i].Name = string.Format(
CultureInfo.InvariantCulture,
@@ -384,23 +366,18 @@ namespace MediaBrowser.Providers.MediaInfo
// 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;
videoStream.BitRate = videoStream.BitRate.GetValueOrDefault() == 0 ? currentBitRate : videoStream.BitRate;
videoStream.Width = videoStream.Width.GetValueOrDefault() == 0 ? currentWidth : videoStream.Width;
videoStream.Height = videoStream.Height.GetValueOrDefault() == 0 ? 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)
private BlurayDiscInfo? GetBDInfo(string path)
{
ArgumentException.ThrowIfNullOrEmpty(path);
@@ -527,32 +504,29 @@ namespace MediaBrowser.Providers.MediaInfo
private void FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
{
var replaceData = options.ReplaceAllMetadata;
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Cast))
if (video.IsLocked
|| video.LockedFields.Contains(MetadataField.Cast)
|| data.People.Length == 0)
{
if (replaceData || _libraryManager.GetPeople(video).Count == 0)
{
var people = new List<PersonInfo>();
foreach (var person in data.People)
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = person.Name,
Type = person.Type,
Role = person.Role
});
}
_libraryManager.UpdatePeople(video, people);
}
return;
}
}
private SubtitleOptions GetOptions()
{
return _config.GetConfiguration<SubtitleOptions>("subtitles");
if (options.ReplaceAllMetadata || _libraryManager.GetPeople(video).Count == 0)
{
var people = new List<PersonInfo>();
foreach (var person in data.People)
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = person.Name,
Type = person.Type,
Role = person.Role
});
}
_libraryManager.UpdatePeople(video, people);
}
}
/// <summary>
@@ -575,7 +549,7 @@ namespace MediaBrowser.Providers.MediaInfo
var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
var subtitleOptions = GetOptions();
var subtitleOptions = _config.GetConfiguration<SubtitleOptions>("subtitles");
var libraryOptions = _libraryManager.GetLibraryOptions(video);
@@ -659,9 +633,9 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary>
/// <param name="video">The video.</param>
/// <returns>An array of dummy chapters.</returns>
private ChapterInfo[] CreateDummyChapters(Video video)
internal ChapterInfo[] CreateDummyChapters(Video video)
{
var runtime = video.RunTimeTicks ?? 0;
var runtime = video.RunTimeTicks.GetValueOrDefault();
// Only process files with a runtime higher than 0 and lower than 12h. The latter are likely corrupted.
if (runtime < 0 || runtime > TimeSpan.FromHours(12).Ticks)
@@ -671,30 +645,30 @@ namespace MediaBrowser.Providers.MediaInfo
CultureInfo.InvariantCulture,
"{0} has an invalid runtime of {1} minutes",
video.Name,
TimeSpan.FromTicks(runtime).Minutes));
TimeSpan.FromTicks(runtime).TotalMinutes));
}
long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks;
if (runtime > dummyChapterDuration)
if (runtime <= dummyChapterDuration)
{
int chapterCount = (int)(runtime / dummyChapterDuration);
var chapters = new ChapterInfo[chapterCount];
long currentChapterTicks = 0;
for (int i = 0; i < chapterCount; i++)
{
chapters[i] = new ChapterInfo
{
StartPositionTicks = currentChapterTicks
};
currentChapterTicks += dummyChapterDuration;
}
return chapters;
return Array.Empty<ChapterInfo>();
}
return Array.Empty<ChapterInfo>();
int chapterCount = (int)(runtime / dummyChapterDuration);
var chapters = new ChapterInfo[chapterCount];
long currentChapterTicks = 0;
for (int i = 0; i < chapterCount; i++)
{
chapters[i] = new ChapterInfo
{
StartPositionTicks = currentChapterTicks
};
currentChapterTicks += dummyChapterDuration;
}
return chapters;
}
}
}

View File

@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
public string? UrlFormatString => "https://www.imdb.com/title/{0}";
public string UrlFormatString => "https://www.imdb.com/title/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)

View File

@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Movies
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
/// <inheritdoc />
public string? UrlFormatString => "https://www.imdb.com/name/{0}";
public string UrlFormatString => "https://www.imdb.com/name/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Person;

View File

@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
public string? UrlFormatString => "https://www.theaudiodb.com/album/{0}";
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is MusicAlbum;

View File

@@ -176,17 +176,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false))
var fileStreamOptions = AsyncFile.WriteOptions;
fileStreamOptions.Mode = FileMode.Create;
var fs = new FileStream(path, fileStreamOptions);
await using (fs.ConfigureAwait(false))
{
var fileStreamOptions = AsyncFile.WriteOptions;
fileStreamOptions.Mode = FileMode.Create;
fileStreamOptions.PreallocationSize = stream.Length;
var xmlFileStream = new FileStream(path, fileStreamOptions);
await using (xmlFileStream.ConfigureAwait(false))
{
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
/// <inheritdoc />
public string? UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is MusicArtist;

View File

@@ -154,20 +154,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false))
{
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
Directory.CreateDirectory(Path.GetDirectoryName(path));
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
Directory.CreateDirectory(Path.GetDirectoryName(path));
var fileStreamOptions = AsyncFile.WriteOptions;
fileStreamOptions.Mode = FileMode.Create;
fileStreamOptions.PreallocationSize = stream.Length;
var xmlFileStream = new FileStream(path, fileStreamOptions);
await using (xmlFileStream.ConfigureAwait(false))
{
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
var fileStreamOptions = AsyncFile.WriteOptions;
fileStreamOptions.Mode = FileMode.Create;
var xmlFileStream = new FileStream(path, fileStreamOptions);
await using (xmlFileStream.ConfigureAwait(false))
{
await response.Content.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
/// <inheritdoc />
public string? UrlFormatString => "https://www.theaudiodb.com/album/{0}";
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio;

View File

@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
/// <inheritdoc />
public string? UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;

View File

@@ -20,7 +20,7 @@ public class MusicBrainzAlbumArtistExternalId : IExternalId
public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
/// <inheritdoc />
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio;

View File

@@ -20,7 +20,7 @@ public class MusicBrainzAlbumExternalId : IExternalId
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
/// <inheritdoc />
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release/{0}";
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/release/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;

View File

@@ -20,7 +20,7 @@ public class MusicBrainzArtistExternalId : IExternalId
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
/// <inheritdoc />
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is MusicArtist;

View File

@@ -20,7 +20,7 @@ public class MusicBrainzOtherArtistExternalId : IExternalId
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
/// <inheritdoc />
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;

View File

@@ -20,7 +20,7 @@ public class MusicBrainzReleaseGroupExternalId : IExternalId
public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
/// <inheritdoc />
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release-group/{0}";
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/release-group/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;

View File

@@ -20,7 +20,7 @@ public class MusicBrainzTrackId : IExternalId
public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
/// <inheritdoc />
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/track/{0}";
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/track/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio;

View File

@@ -8,6 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading;
@@ -137,31 +138,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = OmdbProvider.GetOmdbUrl(urlQuery.ToString());
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using (stream.ConfigureAwait(false))
if (isSearch)
{
if (isSearch)
var searchResultList = await response.Content.ReadFromJsonAsync<SearchResultList>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (searchResultList?.Search is not null)
{
var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (searchResultList?.Search is not null)
var resultCount = searchResultList.Search.Count;
var result = new RemoteSearchResult[resultCount];
for (var i = 0; i < resultCount; i++)
{
var resultCount = searchResultList.Search.Count;
var result = new RemoteSearchResult[resultCount];
for (var i = 0; i < resultCount; i++)
{
result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd);
}
result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd);
}
return result;
}
return result;
}
else
}
else
{
var result = await response.Content.ReadFromJsonAsync<SearchResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase))
{
var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase))
{
return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) };
}
return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) };
}
}

View File

@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet;
/// <inheritdoc />
public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)

View File

@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public ExternalIdMediaType? Type => ExternalIdMediaType.Movie;
/// <inheritdoc />
public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)

View File

@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
/// <inheritdoc />
public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)

View File

@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public ExternalIdMediaType? Type => ExternalIdMediaType.Series;
/// <inheritdoc />
public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)

View File

@@ -11,10 +11,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <summary>
/// Utilities for the TMDb provider.
/// </summary>
public static class TmdbUtils
public static partial class TmdbUtils
{
private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled);
/// <summary>
/// URL of the TMDb instance to use.
/// </summary>
@@ -50,6 +48,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
PersonKind.Producer
};
[GeneratedRegex(@"[\W_]+")]
private static partial Regex NonWordRegex();
/// <summary>
/// Cleans the name according to TMDb requirements.
/// </summary>
@@ -58,7 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public static string CleanName(string name)
{
// TMDb expects a space separated list of words make sure that is the case
return _nonWords.Replace(name, " ");
return NonWordRegex().Replace(name, " ");
}
/// <summary>

View File

@@ -200,6 +200,11 @@ namespace MediaBrowser.Providers.Subtitles
saveFileName += ".forced";
}
if (response.IsHearingImpaired)
{
saveFileName += ".sdh";
}
saveFileName += "." + response.Format.ToLowerInvariant();
if (saveInMediaFolder)

View File

@@ -1,5 +1,6 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -213,11 +214,10 @@ namespace MediaBrowser.Providers.TV
{
// Null season numbers will have a 'dummy' season created because seasons are always required.
var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
string? seasonName = null;
if (seasonNumber.HasValue && seasonNames.TryGetValue(seasonNumber.Value, out var tmp))
if (!seasonNumber.HasValue || !seasonNames.TryGetValue(seasonNumber.Value, out var seasonName))
{
seasonName = tmp;
seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
}
if (existingSeason is null)
@@ -225,9 +225,9 @@ namespace MediaBrowser.Providers.TV
var season = await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
series.AddChild(season);
}
else
else if (!string.Equals(existingSeason.Name, seasonName, StringComparison.Ordinal))
{
existingSeason.Name = GetValidSeasonNameForSeries(series, seasonName, seasonNumber);
existingSeason.Name = seasonName;
await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
}
}
@@ -247,7 +247,6 @@ namespace MediaBrowser.Providers.TV
int? seasonNumber,
CancellationToken cancellationToken)
{
seasonName = GetValidSeasonNameForSeries(series, seasonName, seasonNumber);
Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name);
var season = new Season

View File

@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.TV
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
public string? UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}";
public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Series;