mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-22 18:14:42 +01:00
Merge branch 'master' into trickplay
This commit is contained in:
69
MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs
Normal file
69
MediaBrowser.Providers/Lyric/DefaultLyricProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
36
MediaBrowser.Providers/Lyric/ILyricProvider.cs
Normal file
36
MediaBrowser.Providers/Lyric/ILyricProvider.cs
Normal 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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
44
MediaBrowser.Providers/Lyric/TxtLyricParser.cs
Normal file
44
MediaBrowser.Providers/Lyric/TxtLyricParser.cs
Normal 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 };
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -200,6 +200,11 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
saveFileName += ".forced";
|
||||
}
|
||||
|
||||
if (response.IsHearingImpaired)
|
||||
{
|
||||
saveFileName += ".sdh";
|
||||
}
|
||||
|
||||
saveFileName += "." + response.Format.ToLowerInvariant();
|
||||
|
||||
if (saveInMediaFolder)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user