2019-01-13 21:03:10 +01:00
|
|
|
using System;
|
2019-01-13 20:26:31 +01:00
|
|
|
using System.Collections.Generic;
|
2024-06-01 18:41:07 -04:00
|
|
|
using System.Globalization;
|
2019-01-13 20:26:31 +01:00
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2024-05-30 16:09:50 +08:00
|
|
|
using ATL;
|
2023-03-25 11:52:02 -06:00
|
|
|
using Jellyfin.Data.Enums;
|
2013-02-20 20:33:05 -05:00
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
|
using MediaBrowser.Controller.Entities.Audio;
|
2014-02-05 23:39:16 -05:00
|
|
|
using MediaBrowser.Controller.Library;
|
2024-02-28 17:29:44 -07:00
|
|
|
using MediaBrowser.Controller.Lyrics;
|
2014-02-20 11:37:41 -05:00
|
|
|
using MediaBrowser.Controller.MediaEncoding;
|
2013-12-05 22:39:44 -05:00
|
|
|
using MediaBrowser.Controller.Persistence;
|
2019-01-13 20:26:31 +01:00
|
|
|
using MediaBrowser.Controller.Providers;
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
using MediaBrowser.Controller.Sorting;
|
2015-04-05 11:01:57 -04:00
|
|
|
using MediaBrowser.Model.Dlna;
|
2019-01-13 20:26:31 +01:00
|
|
|
using MediaBrowser.Model.Dto;
|
2013-02-20 20:33:05 -05:00
|
|
|
using MediaBrowser.Model.Entities;
|
2024-09-24 12:36:05 +08:00
|
|
|
using MediaBrowser.Model.Extensions;
|
2014-06-16 21:56:23 -04:00
|
|
|
using MediaBrowser.Model.MediaInfo;
|
2024-05-17 13:51:37 -04:00
|
|
|
using Microsoft.Extensions.Logging;
|
2013-02-20 20:33:05 -05:00
|
|
|
|
2013-06-09 12:47:28 -04:00
|
|
|
namespace MediaBrowser.Providers.MediaInfo
|
2013-02-20 20:33:05 -05:00
|
|
|
{
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Probes audio files for metadata.
|
|
|
|
|
/// </summary>
|
2024-04-28 15:18:53 +02:00
|
|
|
public class AudioFileProber
|
2013-02-20 20:33:05 -05:00
|
|
|
{
|
2024-07-17 03:40:07 +08:00
|
|
|
private const char InternalValueSeparator = '\u001F';
|
2024-09-28 22:52:05 +08:00
|
|
|
|
2014-02-05 23:39:16 -05:00
|
|
|
private readonly IMediaEncoder _mediaEncoder;
|
2013-12-05 22:39:44 -05:00
|
|
|
private readonly IItemRepository _itemRepo;
|
2015-06-28 13:00:36 -04:00
|
|
|
private readonly ILibraryManager _libraryManager;
|
2024-05-17 13:51:37 -04:00
|
|
|
private readonly ILogger<AudioFileProber> _logger;
|
2018-09-12 19:26:21 +02:00
|
|
|
private readonly IMediaSourceManager _mediaSourceManager;
|
2024-02-26 05:09:40 -07:00
|
|
|
private readonly LyricResolver _lyricResolver;
|
2024-02-28 17:29:44 -07:00
|
|
|
private readonly ILyricManager _lyricManager;
|
2024-10-09 10:36:08 +00:00
|
|
|
private readonly IMediaStreamRepository _mediaStreamRepository;
|
2013-12-05 22:39:44 -05:00
|
|
|
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="AudioFileProber"/> class.
|
|
|
|
|
/// </summary>
|
2024-05-17 13:51:37 -04:00
|
|
|
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
|
|
|
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
|
|
|
|
/// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
|
|
|
|
|
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
2024-02-26 05:09:40 -07:00
|
|
|
/// <param name="lyricResolver">Instance of the <see cref="LyricResolver"/> interface.</param>
|
2024-02-28 17:29:44 -07:00
|
|
|
/// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
|
2024-10-09 10:36:08 +00:00
|
|
|
/// <param name="mediaStreamRepository">Instance of the <see cref="IMediaStreamRepository"/>.</param>
|
2022-03-29 17:06:30 +02:00
|
|
|
public AudioFileProber(
|
2024-05-17 13:51:37 -04:00
|
|
|
ILogger<AudioFileProber> logger,
|
2020-08-07 19:26:28 +02:00
|
|
|
IMediaSourceManager mediaSourceManager,
|
|
|
|
|
IMediaEncoder mediaEncoder,
|
|
|
|
|
IItemRepository itemRepo,
|
2024-02-26 05:09:40 -07:00
|
|
|
ILibraryManager libraryManager,
|
2024-02-28 17:29:44 -07:00
|
|
|
LyricResolver lyricResolver,
|
2024-10-09 10:36:08 +00:00
|
|
|
ILyricManager lyricManager,
|
|
|
|
|
IMediaStreamRepository mediaStreamRepository)
|
2013-03-02 12:59:15 -05:00
|
|
|
{
|
2014-02-05 23:39:16 -05:00
|
|
|
_mediaEncoder = mediaEncoder;
|
2013-12-05 22:39:44 -05:00
|
|
|
_itemRepo = itemRepo;
|
2015-06-28 13:00:36 -04:00
|
|
|
_libraryManager = libraryManager;
|
2024-05-17 13:51:37 -04:00
|
|
|
_logger = logger;
|
2018-09-12 19:26:21 +02:00
|
|
|
_mediaSourceManager = mediaSourceManager;
|
2024-02-26 05:09:40 -07:00
|
|
|
_lyricResolver = lyricResolver;
|
2024-02-28 17:29:44 -07:00
|
|
|
_lyricManager = lyricManager;
|
2024-10-09 10:36:08 +00:00
|
|
|
_mediaStreamRepository = mediaStreamRepository;
|
2024-07-17 03:40:07 +08:00
|
|
|
ATL.Settings.DisplayValueSeparator = InternalValueSeparator;
|
2024-09-28 22:52:05 +08:00
|
|
|
ATL.Settings.UseFileNameWhenNoTitle = false;
|
2024-09-30 21:15:52 +08:00
|
|
|
ATL.Settings.ID3v2_separatev2v3Values = false;
|
2013-03-02 12:59:15 -05:00
|
|
|
}
|
|
|
|
|
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Probes the specified item for metadata.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="item">The item to probe.</param>
|
|
|
|
|
/// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
|
|
|
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
|
|
|
|
|
/// <typeparam name="T">The type of item to resolve.</typeparam>
|
|
|
|
|
/// <returns>A <see cref="Task"/> probing the item for metadata.</returns>
|
2020-09-07 13:20:39 +02:00
|
|
|
public async Task<ItemUpdateType> Probe<T>(
|
|
|
|
|
T item,
|
|
|
|
|
MetadataRefreshOptions options,
|
2018-09-12 19:26:21 +02:00
|
|
|
CancellationToken cancellationToken)
|
2014-02-05 23:39:16 -05:00
|
|
|
where T : Audio
|
2013-06-18 15:16:27 -04:00
|
|
|
{
|
2018-09-12 19:26:21 +02:00
|
|
|
var path = item.Path;
|
|
|
|
|
var protocol = item.PathProtocol ?? MediaProtocol.File;
|
2013-06-18 15:16:27 -04:00
|
|
|
|
2018-09-12 19:26:21 +02:00
|
|
|
if (!item.IsShortcut || options.EnableRemoteContentProbe)
|
|
|
|
|
{
|
|
|
|
|
if (item.IsShortcut)
|
|
|
|
|
{
|
|
|
|
|
path = item.ShortcutPath;
|
|
|
|
|
protocol = _mediaSourceManager.GetPathProtocol(path);
|
|
|
|
|
}
|
2013-06-18 15:16:27 -04:00
|
|
|
|
2020-09-07 13:20:39 +02:00
|
|
|
var result = await _mediaEncoder.GetMediaInfo(
|
|
|
|
|
new MediaInfoRequest
|
2018-09-12 19:26:21 +02:00
|
|
|
{
|
2020-09-07 13:20:39 +02:00
|
|
|
MediaType = DlnaProfileType.Audio,
|
|
|
|
|
MediaSource = new MediaSourceInfo
|
|
|
|
|
{
|
|
|
|
|
Path = path,
|
|
|
|
|
Protocol = protocol
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
cancellationToken).ConfigureAwait(false);
|
2014-02-05 23:39:16 -05:00
|
|
|
|
2018-09-12 19:26:21 +02:00
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
2014-02-09 16:11:11 -05:00
|
|
|
|
2024-02-28 17:29:44 -07:00
|
|
|
await FetchAsync(item, result, options, cancellationToken).ConfigureAwait(false);
|
2018-09-12 19:26:21 +02:00
|
|
|
}
|
2014-02-09 16:11:11 -05:00
|
|
|
|
2018-09-12 19:26:21 +02:00
|
|
|
return ItemUpdateType.MetadataImport;
|
2013-06-18 15:16:27 -04:00
|
|
|
}
|
|
|
|
|
|
2013-02-20 20:33:05 -05:00
|
|
|
/// <summary>
|
|
|
|
|
/// Fetches the specified audio.
|
|
|
|
|
/// </summary>
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <param name="audio">The <see cref="Audio"/>.</param>
|
|
|
|
|
/// <param name="mediaInfo">The <see cref="Model.MediaInfo.MediaInfo"/>.</param>
|
2023-12-06 23:55:36 -06:00
|
|
|
/// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
|
2024-02-28 17:29:44 -07:00
|
|
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
|
|
|
|
private async Task FetchAsync(
|
2024-02-26 05:09:40 -07:00
|
|
|
Audio audio,
|
|
|
|
|
Model.MediaInfo.MediaInfo mediaInfo,
|
|
|
|
|
MetadataRefreshOptions options,
|
|
|
|
|
CancellationToken cancellationToken)
|
2013-02-20 20:33:05 -05:00
|
|
|
{
|
2017-08-04 16:29:34 -04:00
|
|
|
audio.Container = mediaInfo.Container;
|
2015-04-04 15:35:29 -04:00
|
|
|
audio.TotalBitrate = mediaInfo.Bitrate;
|
2013-12-05 22:39:44 -05:00
|
|
|
|
2015-04-04 15:35:29 -04:00
|
|
|
audio.RunTimeTicks = mediaInfo.RunTimeTicks;
|
|
|
|
|
audio.Size = mediaInfo.Size;
|
2013-05-15 00:05:52 -04:00
|
|
|
|
2024-05-05 22:22:48 +08:00
|
|
|
// Add external lyrics first to prevent the lrc file get overwritten on first scan
|
|
|
|
|
var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams);
|
|
|
|
|
AddExternalLyrics(audio, mediaStreams, options);
|
|
|
|
|
var tryExtractEmbeddedLyrics = mediaStreams.All(s => s.Type != MediaStreamType.Lyric);
|
|
|
|
|
|
2023-02-12 21:59:58 -05:00
|
|
|
if (!audio.IsLocked)
|
|
|
|
|
{
|
2024-05-05 22:22:48 +08:00
|
|
|
await FetchDataFromTags(audio, mediaInfo, options, tryExtractEmbeddedLyrics).ConfigureAwait(false);
|
2024-06-24 20:29:05 -04:00
|
|
|
if (tryExtractEmbeddedLyrics)
|
|
|
|
|
{
|
|
|
|
|
AddExternalLyrics(audio, mediaStreams, options);
|
|
|
|
|
}
|
2023-02-12 21:59:58 -05:00
|
|
|
}
|
2013-12-05 22:39:44 -05:00
|
|
|
|
2024-02-26 05:09:40 -07:00
|
|
|
audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric);
|
|
|
|
|
|
2024-10-09 10:36:08 +00:00
|
|
|
_mediaStreamRepository.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
|
2013-02-20 20:33:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2022-03-31 16:17:37 +02:00
|
|
|
/// Fetches data from the tags.
|
2013-02-20 20:33:05 -05:00
|
|
|
/// </summary>
|
2022-03-31 16:17:37 +02:00
|
|
|
/// <param name="audio">The <see cref="Audio"/>.</param>
|
2024-04-23 15:08:49 +02:00
|
|
|
/// <param name="mediaInfo">The <see cref="Model.MediaInfo.MediaInfo"/>.</param>
|
2023-12-06 23:55:36 -06:00
|
|
|
/// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
|
2024-05-05 22:22:48 +08:00
|
|
|
/// <param name="tryExtractEmbeddedLyrics">Whether to extract embedded lyrics to lrc file. </param>
|
|
|
|
|
private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, MetadataRefreshOptions options, bool tryExtractEmbeddedLyrics)
|
2013-02-20 20:33:05 -05:00
|
|
|
{
|
2024-09-08 11:10:59 +08:00
|
|
|
var libraryOptions = _libraryManager.GetLibraryOptions(audio);
|
2024-05-30 16:09:50 +08:00
|
|
|
Track track = new Track(audio.Path);
|
2013-08-29 17:00:27 -04:00
|
|
|
|
2024-09-30 21:15:52 +08:00
|
|
|
if (track.MetadataFormats
|
|
|
|
|
.All(mf => string.Equals(mf.ShortName, "ID3v1", StringComparison.OrdinalIgnoreCase)))
|
2024-05-30 18:59:26 +08:00
|
|
|
{
|
2024-09-30 21:15:52 +08:00
|
|
|
_logger.LogWarning("File {File} only has ID3v1 tags, some fields may be truncated", audio.Path);
|
2024-05-30 18:59:26 +08:00
|
|
|
}
|
|
|
|
|
|
2024-09-28 22:52:05 +08:00
|
|
|
track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
|
2024-05-30 16:09:50 +08:00
|
|
|
track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
|
|
|
|
|
track.Year ??= mediaInfo.ProductionYear;
|
|
|
|
|
track.TrackNumber ??= mediaInfo.IndexNumber;
|
|
|
|
|
track.DiscNumber ??= mediaInfo.ParentIndexNumber;
|
2024-06-01 18:41:07 -04:00
|
|
|
|
|
|
|
|
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
var people = new List<PersonInfo>();
|
2025-02-03 16:55:56 -05:00
|
|
|
var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? [] : track.AlbumArtist.Split(InternalValueSeparator);
|
2024-09-08 11:10:59 +08:00
|
|
|
|
|
|
|
|
if (libraryOptions.UseCustomTagDelimiters)
|
|
|
|
|
{
|
2024-09-24 05:15:46 +08:00
|
|
|
albumArtists = albumArtists.SelectMany(a => SplitWithCustomDelimiter(a, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
|
2024-09-08 11:10:59 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
foreach (var albumArtist in albumArtists)
|
2013-08-03 20:59:23 -04:00
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
if (!string.IsNullOrWhiteSpace(albumArtist))
|
2013-02-20 20:33:05 -05:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
PeopleHelper.AddPerson(people, new PersonInfo
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
Name = albumArtist.Trim(),
|
2024-06-01 18:41:07 -04:00
|
|
|
Type = PersonKind.AlbumArtist
|
|
|
|
|
});
|
2022-03-28 23:11:21 +02:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2014-06-23 12:05:19 -04:00
|
|
|
|
2024-09-08 11:10:59 +08:00
|
|
|
string[]? performers = null;
|
|
|
|
|
if (libraryOptions.PreferNonstandardArtistsTag)
|
|
|
|
|
{
|
|
|
|
|
track.AdditionalFields.TryGetValue("ARTISTS", out var artistsTagString);
|
|
|
|
|
if (artistsTagString is not null)
|
|
|
|
|
{
|
|
|
|
|
performers = artistsTagString.Split(InternalValueSeparator);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (performers is null || performers.Length == 0)
|
|
|
|
|
{
|
2025-02-03 16:55:56 -05:00
|
|
|
performers = string.IsNullOrEmpty(track.Artist) ? [] : track.Artist.Split(InternalValueSeparator);
|
2024-09-08 11:10:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (libraryOptions.UseCustomTagDelimiters)
|
|
|
|
|
{
|
2024-09-24 05:15:46 +08:00
|
|
|
performers = performers.SelectMany(p => SplitWithCustomDelimiter(p, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
|
2024-09-08 11:10:59 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
foreach (var performer in performers)
|
|
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
if (!string.IsNullOrWhiteSpace(performer))
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
PeopleHelper.AddPerson(people, new PersonInfo
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
Name = performer.Trim(),
|
2024-06-01 18:41:07 -04:00
|
|
|
Type = PersonKind.Artist
|
|
|
|
|
});
|
2022-03-28 23:11:21 +02:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2013-02-20 20:33:05 -05:00
|
|
|
|
2024-07-17 03:40:07 +08:00
|
|
|
foreach (var composer in track.Composer.Split(InternalValueSeparator))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
if (!string.IsNullOrWhiteSpace(composer))
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
PeopleHelper.AddPerson(people, new PersonInfo
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
Name = composer.Trim(),
|
2024-06-01 18:41:07 -04:00
|
|
|
Type = PersonKind.Composer
|
|
|
|
|
});
|
2024-02-28 17:18:52 -07:00
|
|
|
}
|
2022-03-28 23:11:21 +02:00
|
|
|
}
|
2013-02-20 20:33:05 -05:00
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
_libraryManager.UpdatePeople(audio, people);
|
2023-12-06 23:55:36 -06:00
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
if (options.ReplaceAllMetadata && performers.Length != 0)
|
2023-12-06 23:55:36 -06:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
audio.Artists = performers;
|
2023-12-06 23:55:36 -06:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
else if (!options.ReplaceAllMetadata
|
|
|
|
|
&& (audio.Artists is null || audio.Artists.Count == 0))
|
2023-12-06 23:55:36 -06:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
audio.Artists = performers;
|
2023-12-06 23:55:36 -06:00
|
|
|
}
|
2023-05-15 12:12:24 +01:00
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
if (albumArtists.Length == 0)
|
2022-03-28 23:11:21 +02:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
// Album artists not provided, fall back to performers (artists).
|
|
|
|
|
albumArtists = performers;
|
2022-03-28 23:11:21 +02:00
|
|
|
}
|
2013-04-28 01:44:45 -04:00
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
if (options.ReplaceAllMetadata && albumArtists.Length != 0)
|
2013-04-28 01:44:45 -04:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
audio.AlbumArtists = albumArtists;
|
2013-04-28 01:44:45 -04:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
else if (!options.ReplaceAllMetadata
|
|
|
|
|
&& (audio.AlbumArtists is null || audio.AlbumArtists.Count == 0))
|
2024-04-24 16:09:01 +02:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
audio.AlbumArtists = albumArtists;
|
2023-07-10 00:28:06 -05:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2023-07-10 00:28:06 -05:00
|
|
|
|
2024-05-30 16:09:50 +08:00
|
|
|
if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(track.Title))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-05-30 16:09:50 +08:00
|
|
|
audio.Name = track.Title;
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2023-07-10 00:28:06 -05:00
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
if (options.ReplaceAllMetadata)
|
|
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
audio.Album = track.Album.Trim();
|
2024-05-30 16:09:50 +08:00
|
|
|
audio.IndexNumber = track.TrackNumber;
|
|
|
|
|
audio.ParentIndexNumber = track.DiscNumber;
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
audio.Album ??= track.Album.Trim();
|
2024-05-30 16:09:50 +08:00
|
|
|
audio.IndexNumber ??= track.TrackNumber;
|
|
|
|
|
audio.ParentIndexNumber ??= track.DiscNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (track.Date.HasValue)
|
|
|
|
|
{
|
|
|
|
|
audio.PremiereDate = track.Date;
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2023-07-10 00:28:06 -05:00
|
|
|
|
2024-05-30 16:09:50 +08:00
|
|
|
if (track.Year.HasValue)
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-05-30 16:09:50 +08:00
|
|
|
var year = track.Year.Value;
|
2024-06-01 18:41:07 -04:00
|
|
|
audio.ProductionYear = year;
|
2023-07-10 00:28:06 -05:00
|
|
|
|
2024-06-01 18:41:07 -04:00
|
|
|
if (!audio.PremiereDate.HasValue)
|
2023-07-10 00:28:06 -05:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
try
|
2024-04-13 01:44:30 +02:00
|
|
|
{
|
2024-06-01 18:41:07 -04:00
|
|
|
audio.PremiereDate = new DateTime(year, 01, 01);
|
|
|
|
|
}
|
|
|
|
|
catch (ArgumentOutOfRangeException ex)
|
|
|
|
|
{
|
2024-05-30 16:09:50 +08:00
|
|
|
_logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, track.Year);
|
2024-04-13 01:44:30 +02:00
|
|
|
}
|
2023-07-10 00:28:06 -05:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!audio.LockedFields.Contains(MetadataField.Genres))
|
|
|
|
|
{
|
2025-02-03 16:55:56 -05:00
|
|
|
var genres = string.IsNullOrEmpty(track.Genre) ? [] : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
2024-09-08 11:10:59 +08:00
|
|
|
|
|
|
|
|
if (libraryOptions.UseCustomTagDelimiters)
|
|
|
|
|
{
|
2024-09-24 05:15:46 +08:00
|
|
|
genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray();
|
2024-09-08 11:10:59 +08:00
|
|
|
}
|
|
|
|
|
|
Sort embedded collections in Nfo files
Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.
In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.
Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)
BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)
AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)
ArtistNfo: Albums (by Production Year>SortName>Name)
MovieNfo: Artists
Fix Debug build lint
Fix CI debug build lint issue.
Fix review issues
Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.
Don't emit actors for MusicAlbums or MusicArtists
Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback
Can't use ReadOnlySpan in an async method
Removed now-unused namespace
2023-05-15 00:38:27 -05:00
|
|
|
genres = genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
|
|
|
|
|
2024-09-17 20:29:43 +02:00
|
|
|
audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0
|
2024-05-30 16:09:50 +08:00
|
|
|
? genres
|
2024-06-01 18:41:07 -04:00
|
|
|
: audio.Genres;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 16:09:50 +08:00
|
|
|
track.AdditionalFields.TryGetValue("REPLAYGAIN_TRACK_GAIN", out var trackGainTag);
|
|
|
|
|
|
|
|
|
|
if (trackGainTag is not null)
|
|
|
|
|
{
|
2024-07-17 03:29:16 +08:00
|
|
|
if (trackGainTag.EndsWith("db", StringComparison.OrdinalIgnoreCase))
|
2024-05-30 16:09:50 +08:00
|
|
|
{
|
|
|
|
|
trackGainTag = trackGainTag[..^2].Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (float.TryParse(trackGainTag, NumberStyles.Float, CultureInfo.InvariantCulture, out var value))
|
|
|
|
|
{
|
2024-07-18 01:59:16 +08:00
|
|
|
audio.NormalizationGain = value;
|
2024-05-30 16:09:50 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-18 01:59:16 +08:00
|
|
|
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-07-17 03:29:16 +08:00
|
|
|
if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ARTISTID", out var musicBrainzArtistTag)
|
|
|
|
|
|| track.AdditionalFields.TryGetValue("MusicBrainz Artist Id", out musicBrainzArtistTag))
|
|
|
|
|
&& !string.IsNullOrEmpty(musicBrainzArtistTag))
|
2024-05-30 16:09:50 +08:00
|
|
|
{
|
2024-11-19 15:43:28 -05:00
|
|
|
var id = GetFirstMusicBrainzId(musicBrainzArtistTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
|
|
|
|
|
audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, id);
|
2024-05-30 16:09:50 +08:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-18 01:59:16 +08:00
|
|
|
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-07-17 03:29:16 +08:00
|
|
|
if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ALBUMARTISTID", out var musicBrainzReleaseArtistIdTag)
|
|
|
|
|
|| track.AdditionalFields.TryGetValue("MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag))
|
|
|
|
|
&& !string.IsNullOrEmpty(musicBrainzReleaseArtistIdTag))
|
2024-05-30 16:09:50 +08:00
|
|
|
{
|
2024-11-19 15:43:28 -05:00
|
|
|
var id = GetFirstMusicBrainzId(musicBrainzReleaseArtistIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
|
|
|
|
|
audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, id);
|
2024-05-30 16:09:50 +08:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-18 01:59:16 +08:00
|
|
|
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-07-17 03:29:16 +08:00
|
|
|
if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ALBUMID", out var musicBrainzReleaseIdTag)
|
|
|
|
|
|| track.AdditionalFields.TryGetValue("MusicBrainz Album Id", out musicBrainzReleaseIdTag))
|
|
|
|
|
&& !string.IsNullOrEmpty(musicBrainzReleaseIdTag))
|
2024-05-30 16:09:50 +08:00
|
|
|
{
|
2024-11-19 15:43:28 -05:00
|
|
|
var id = GetFirstMusicBrainzId(musicBrainzReleaseIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
|
|
|
|
|
audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, id);
|
2024-05-30 16:09:50 +08:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-18 01:59:16 +08:00
|
|
|
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-07-17 03:29:16 +08:00
|
|
|
if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_RELEASEGROUPID", out var musicBrainzReleaseGroupIdTag)
|
|
|
|
|
|| track.AdditionalFields.TryGetValue("MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag))
|
|
|
|
|
&& !string.IsNullOrEmpty(musicBrainzReleaseGroupIdTag))
|
2024-05-30 16:09:50 +08:00
|
|
|
{
|
2024-11-19 15:43:28 -05:00
|
|
|
var id = GetFirstMusicBrainzId(musicBrainzReleaseGroupIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
|
|
|
|
|
audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, id);
|
2024-05-30 16:09:50 +08:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2024-02-28 17:29:44 -07:00
|
|
|
|
2024-05-30 16:09:50 +08:00
|
|
|
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _))
|
2024-06-01 18:41:07 -04:00
|
|
|
{
|
2024-07-17 03:29:16 +08:00
|
|
|
if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_RELEASETRACKID", out var trackMbId)
|
|
|
|
|
|| track.AdditionalFields.TryGetValue("MusicBrainz Release Track Id", out trackMbId))
|
|
|
|
|
&& !string.IsNullOrEmpty(trackMbId))
|
2024-05-30 16:09:50 +08:00
|
|
|
{
|
2024-11-19 15:43:28 -05:00
|
|
|
var id = GetFirstMusicBrainzId(trackMbId, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
|
|
|
|
|
audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, id);
|
2024-05-30 16:09:50 +08:00
|
|
|
}
|
2013-02-20 20:33:05 -05:00
|
|
|
}
|
2024-06-01 18:41:07 -04:00
|
|
|
|
|
|
|
|
// Save extracted lyrics if they exist,
|
|
|
|
|
// and if the audio doesn't yet have lyrics.
|
2024-05-30 16:09:50 +08:00
|
|
|
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(lyrics)
|
2024-06-01 18:41:07 -04:00
|
|
|
&& tryExtractEmbeddedLyrics)
|
|
|
|
|
{
|
2024-05-30 16:09:50 +08:00
|
|
|
await _lyricManager.SaveLyricAsync(audio, "lrc", lyrics).ConfigureAwait(false);
|
2024-06-01 18:41:07 -04:00
|
|
|
}
|
2013-02-20 20:33:05 -05:00
|
|
|
}
|
2024-02-26 05:09:40 -07:00
|
|
|
|
|
|
|
|
private void AddExternalLyrics(
|
|
|
|
|
Audio audio,
|
|
|
|
|
List<MediaStream> currentStreams,
|
|
|
|
|
MetadataRefreshOptions options)
|
|
|
|
|
{
|
|
|
|
|
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
|
|
|
|
|
var externalLyricFiles = _lyricResolver.GetExternalStreams(audio, startIndex, options.DirectoryService, false);
|
|
|
|
|
|
|
|
|
|
audio.LyricFiles = externalLyricFiles.Select(i => i.Path).Distinct().ToArray();
|
2024-06-24 20:29:05 -04:00
|
|
|
if (externalLyricFiles.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
currentStreams.Add(externalLyricFiles[0]);
|
|
|
|
|
}
|
2024-02-26 05:09:40 -07:00
|
|
|
}
|
2024-09-08 11:10:59 +08:00
|
|
|
|
|
|
|
|
private List<string> SplitWithCustomDelimiter(string val, char[] tagDelimiters, string[] whitelist)
|
|
|
|
|
{
|
|
|
|
|
var items = new List<string>();
|
|
|
|
|
var temp = val;
|
|
|
|
|
foreach (var whitelistItem in whitelist)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(whitelistItem))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var originalTemp = temp;
|
|
|
|
|
temp = temp.Replace(whitelistItem, string.Empty, StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
if (!string.Equals(temp, originalTemp, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
items.Add(whitelistItem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var items2 = temp.Split(tagDelimiters, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).DistinctNames();
|
|
|
|
|
items.AddRange(items2);
|
|
|
|
|
|
|
|
|
|
return items;
|
|
|
|
|
}
|
2024-11-19 15:43:28 -05:00
|
|
|
|
|
|
|
|
// MusicBrainz IDs are multi-value tags, so we need to split them
|
|
|
|
|
// However, our current provider can only have one single ID, which means we need to pick the first one
|
|
|
|
|
private string? GetFirstMusicBrainzId(string tag, bool useCustomTagDelimiters, char[] tagDelimiters, string[] whitelist)
|
|
|
|
|
{
|
|
|
|
|
var val = tag.Split(InternalValueSeparator).FirstOrDefault();
|
|
|
|
|
if (val is not null && useCustomTagDelimiters)
|
|
|
|
|
{
|
|
|
|
|
val = SplitWithCustomDelimiter(val, tagDelimiters, whitelist).FirstOrDefault();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return val;
|
|
|
|
|
}
|
2014-02-05 23:39:16 -05:00
|
|
|
}
|
2013-02-20 20:33:05 -05:00
|
|
|
}
|