aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jellyfin.Api/Controllers/LyricsController.cs12
-rw-r--r--MediaBrowser.Controller/Lyrics/ILyricManager.cs17
-rw-r--r--MediaBrowser.Providers/Lyric/LyricManager.cs51
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs26
-rw-r--r--MediaBrowser.Providers/MediaInfo/ProbeProvider.cs8
5 files changed, 77 insertions, 37 deletions
diff --git a/Jellyfin.Api/Controllers/LyricsController.cs b/Jellyfin.Api/Controllers/LyricsController.cs
index 4fccf2cb4..f2b312b47 100644
--- a/Jellyfin.Api/Controllers/LyricsController.cs
+++ b/Jellyfin.Api/Controllers/LyricsController.cs
@@ -146,13 +146,11 @@ public class LyricsController : BaseJellyfinApiController
await using (stream.ConfigureAwait(false))
{
await Request.Body.CopyToAsync(stream).ConfigureAwait(false);
- var uploadedLyric = await _lyricManager.UploadLyricAsync(
- audio,
- new LyricResponse
- {
- Format = format,
- Stream = stream
- }).ConfigureAwait(false);
+ var uploadedLyric = await _lyricManager.SaveLyricAsync(
+ audio,
+ format,
+ stream)
+ .ConfigureAwait(false);
if (uploadedLyric is null)
{
diff --git a/MediaBrowser.Controller/Lyrics/ILyricManager.cs b/MediaBrowser.Controller/Lyrics/ILyricManager.cs
index f4376a1ee..1e71b87eb 100644
--- a/MediaBrowser.Controller/Lyrics/ILyricManager.cs
+++ b/MediaBrowser.Controller/Lyrics/ILyricManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
@@ -69,12 +70,22 @@ public interface ILyricManager
CancellationToken cancellationToken);
/// <summary>
- /// Upload new lyrics.
+ /// Saves new lyrics.
/// </summary>
/// <param name="audio">The audio file the lyrics belong to.</param>
- /// <param name="lyricResponse">The lyric response.</param>
+ /// <param name="format">The lyrics format.</param>
+ /// <param name="lyrics">The lyrics.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
- Task<LyricDto?> UploadLyricAsync(Audio audio, LyricResponse lyricResponse);
+ Task<LyricDto?> SaveLyricAsync(Audio audio, string format, string lyrics);
+
+ /// <summary>
+ /// Saves new lyrics.
+ /// </summary>
+ /// <param name="audio">The audio file the lyrics belong to.</param>
+ /// <param name="format">The lyrics format.</param>
+ /// <param name="lyrics">The lyrics.</param>
+ /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+ Task<LyricDto?> SaveLyricAsync(Audio audio, string format, Stream lyrics);
/// <summary>
/// Get the remote lyrics.
diff --git a/MediaBrowser.Providers/Lyric/LyricManager.cs b/MediaBrowser.Providers/Lyric/LyricManager.cs
index 60734b89a..f4b18a8c1 100644
--- a/MediaBrowser.Providers/Lyric/LyricManager.cs
+++ b/MediaBrowser.Providers/Lyric/LyricManager.cs
@@ -155,13 +155,13 @@ public class LyricManager : ILyricManager
return null;
}
- var parsedLyrics = await InternalParseRemoteLyricsAsync(response, cancellationToken).ConfigureAwait(false);
+ var parsedLyrics = await InternalParseRemoteLyricsAsync(response.Format, response.Stream, cancellationToken).ConfigureAwait(false);
if (parsedLyrics is null)
{
return null;
}
- await TrySaveLyric(audio, libraryOptions, response).ConfigureAwait(false);
+ await TrySaveLyric(audio, libraryOptions, response.Format, response.Stream).ConfigureAwait(false);
return parsedLyrics;
}
catch (RateLimitExceededException)
@@ -182,19 +182,33 @@ public class LyricManager : ILyricManager
}
/// <inheritdoc />
- public async Task<LyricDto?> UploadLyricAsync(Audio audio, LyricResponse lyricResponse)
+ public async Task<LyricDto?> SaveLyricAsync(Audio audio, string format, string lyrics)
{
ArgumentNullException.ThrowIfNull(audio);
- ArgumentNullException.ThrowIfNull(lyricResponse);
+ ArgumentException.ThrowIfNullOrEmpty(format);
+ ArgumentException.ThrowIfNullOrEmpty(lyrics);
+
+ var bytes = Encoding.UTF8.GetBytes(lyrics);
+ using var lyricStream = new MemoryStream(bytes, 0, bytes.Length, false, true);
+ return await SaveLyricAsync(audio, format, lyricStream).ConfigureAwait(false);
+ }
+
+ /// <inheritdoc />
+ public async Task<LyricDto?> SaveLyricAsync(Audio audio, string format, Stream lyrics)
+ {
+ ArgumentNullException.ThrowIfNull(audio);
+ ArgumentException.ThrowIfNullOrEmpty(format);
+ ArgumentNullException.ThrowIfNull(lyrics);
+
var libraryOptions = BaseItem.LibraryManager.GetLibraryOptions(audio);
- var parsed = await InternalParseRemoteLyricsAsync(lyricResponse, CancellationToken.None).ConfigureAwait(false);
+ var parsed = await InternalParseRemoteLyricsAsync(format, lyrics, CancellationToken.None).ConfigureAwait(false);
if (parsed is null)
{
return null;
}
- await TrySaveLyric(audio, libraryOptions, lyricResponse).ConfigureAwait(false);
+ await TrySaveLyric(audio, libraryOptions, format, lyrics).ConfigureAwait(false);
return parsed;
}
@@ -209,7 +223,7 @@ public class LyricManager : ILyricManager
return null;
}
- return await InternalParseRemoteLyricsAsync(lyricResponse, cancellationToken).ConfigureAwait(false);
+ return await InternalParseRemoteLyricsAsync(lyricResponse.Format, lyricResponse.Stream, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -289,12 +303,12 @@ public class LyricManager : ILyricManager
private string GetProviderId(string name)
=> name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
- private async Task<LyricDto?> InternalParseRemoteLyricsAsync(LyricResponse lyricResponse, CancellationToken cancellationToken)
+ private async Task<LyricDto?> InternalParseRemoteLyricsAsync(string format, Stream lyricStream, CancellationToken cancellationToken)
{
- lyricResponse.Stream.Seek(0, SeekOrigin.Begin);
- using var streamReader = new StreamReader(lyricResponse.Stream, leaveOpen: true);
+ lyricStream.Seek(0, SeekOrigin.Begin);
+ using var streamReader = new StreamReader(lyricStream, leaveOpen: true);
var lyrics = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
- var lyricFile = new LyricFile($"lyric.{lyricResponse.Format}", lyrics);
+ var lyricFile = new LyricFile($"lyric.{format}", lyrics);
foreach (var parser in _lyricParsers)
{
var parsedLyrics = parser.ParseLyrics(lyricFile);
@@ -334,7 +348,7 @@ public class LyricManager : ILyricManager
var parsedResults = new List<RemoteLyricInfoDto>();
foreach (var result in searchResults)
{
- var parsedLyrics = await InternalParseRemoteLyricsAsync(result.Lyrics, cancellationToken).ConfigureAwait(false);
+ var parsedLyrics = await InternalParseRemoteLyricsAsync(result.Lyrics.Format, result.Lyrics.Stream, cancellationToken).ConfigureAwait(false);
if (parsedLyrics is null)
{
continue;
@@ -361,24 +375,23 @@ public class LyricManager : ILyricManager
private async Task TrySaveLyric(
Audio audio,
LibraryOptions libraryOptions,
- LyricResponse lyricResponse)
+ string format,
+ Stream lyricStream)
{
var saveInMediaFolder = libraryOptions.SaveLyricsWithMedia;
var memoryStream = new MemoryStream();
await using (memoryStream.ConfigureAwait(false))
{
- var stream = lyricResponse.Stream;
-
- await using (stream.ConfigureAwait(false))
+ await using (lyricStream.ConfigureAwait(false))
{
- stream.Seek(0, SeekOrigin.Begin);
- await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ lyricStream.Seek(0, SeekOrigin.Begin);
+ await lyricStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Seek(0, SeekOrigin.Begin);
}
var savePaths = new List<string>();
- var saveFileName = Path.GetFileNameWithoutExtension(audio.Path) + "." + lyricResponse.Format.ReplaceLineEndings(string.Empty).ToLowerInvariant();
+ var saveFileName = Path.GetFileNameWithoutExtension(audio.Path) + "." + format.ReplaceLineEndings(string.Empty).ToLowerInvariant();
if (saveInMediaFolder)
{
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index 0df624bdb..4d4b59b8c 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -10,6 +10,7 @@ using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
@@ -36,6 +37,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly LyricResolver _lyricResolver;
+ private readonly ILyricManager _lyricManager;
/// <summary>
/// Initializes a new instance of the <see cref="AudioFileProber"/> class.
@@ -46,13 +48,15 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
/// <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>
public AudioFileProber(
ILogger<AudioFileProber> logger,
IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder,
IItemRepository itemRepo,
ILibraryManager libraryManager,
- LyricResolver lyricResolver)
+ LyricResolver lyricResolver,
+ ILyricManager lyricManager)
{
_logger = logger;
_mediaEncoder = mediaEncoder;
@@ -60,6 +64,7 @@ namespace MediaBrowser.Providers.MediaInfo
_libraryManager = libraryManager;
_mediaSourceManager = mediaSourceManager;
_lyricResolver = lyricResolver;
+ _lyricManager = lyricManager;
}
[GeneratedRegex(@"I:\s+(.*?)\s+LUFS")]
@@ -107,7 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
- Fetch(item, result, options, cancellationToken);
+ await FetchAsync(item, result, options, cancellationToken).ConfigureAwait(false);
}
var libraryOptions = _libraryManager.GetLibraryOptions(item);
@@ -211,7 +216,8 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="mediaInfo">The <see cref="Model.MediaInfo.MediaInfo"/>.</param>
/// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
- protected void Fetch(
+ /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
+ private async Task FetchAsync(
Audio audio,
Model.MediaInfo.MediaInfo mediaInfo,
MetadataRefreshOptions options,
@@ -225,7 +231,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (!audio.IsLocked)
{
- FetchDataFromTags(audio, options);
+ await FetchDataFromTags(audio, options).ConfigureAwait(false);
}
var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams);
@@ -241,9 +247,9 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary>
/// <param name="audio">The <see cref="Audio"/>.</param>
/// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
- private void FetchDataFromTags(Audio audio, MetadataRefreshOptions options)
+ private async Task FetchDataFromTags(Audio audio, MetadataRefreshOptions options)
{
- var file = TagLib.File.Create(audio.Path);
+ using var file = TagLib.File.Create(audio.Path);
var tagTypes = file.TagTypesOnDisk;
Tag? tags = null;
@@ -398,6 +404,14 @@ namespace MediaBrowser.Providers.MediaInfo
{
audio.SetProviderId(MetadataProvider.MusicBrainzTrack, tags.MusicBrainzTrackId);
}
+
+ // Save extracted lyrics if they exist,
+ // and if we are replacing all metadata or the audio doesn't yet have lyrics.
+ if (!string.IsNullOrWhiteSpace(tags.Lyrics)
+ && (options.ReplaceAllMetadata || audio.GetMediaStreams().All(s => s.Type != MediaStreamType.Lyric)))
+ {
+ await _lyricManager.SaveLyricAsync(audio, "lrc", tags.Lyrics).ConfigureAwait(false);
+ }
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
index 8bb874f0d..8bb8d5bb4 100644
--- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
@@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
@@ -64,6 +65,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <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>
public ProbeProvider(
IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder,
@@ -77,7 +79,8 @@ namespace MediaBrowser.Providers.MediaInfo
ILibraryManager libraryManager,
IFileSystem fileSystem,
ILoggerFactory loggerFactory,
- NamingOptions namingOptions)
+ NamingOptions namingOptions,
+ ILyricManager lyricManager)
{
_logger = loggerFactory.CreateLogger<ProbeProvider>();
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
@@ -105,7 +108,8 @@ namespace MediaBrowser.Providers.MediaInfo
mediaEncoder,
itemRepo,
libraryManager,
- _lyricResolver);
+ _lyricResolver,
+ lyricManager);
}
/// <inheritdoc />