diff options
| -rw-r--r-- | Jellyfin.Api/Controllers/LyricsController.cs | 12 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Lyrics/ILyricManager.cs | 17 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Lyric/LyricManager.cs | 51 | ||||
| -rw-r--r-- | MediaBrowser.Providers/MediaInfo/AudioFileProber.cs | 26 | ||||
| -rw-r--r-- | MediaBrowser.Providers/MediaInfo/ProbeProvider.cs | 8 |
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 /> |
