From 88abe759277c03920ca328caca95b7cb944679a8 Mon Sep 17 00:00:00 2001 From: zhangdoa Date: Sun, 5 Nov 2023 13:13:33 +0100 Subject: [PATCH] Add multi-performer support to the cue sheet parser. Fix an incorrect early-out in the cue sheet line parsing function. Fix a crash caused by the invalid artist and album on the local tracks. Add support to remove duplicated cue sheet files. (cherry picked from commit 336c62e6141d67c1f547806f09d4871d26342dca) --- src/NzbDrone.Core/MediaFiles/CueSheet.cs | 4 +- .../MediaFiles/CueSheetService.cs | 275 +++++++++++------- .../Aggregators/AggregateFilenameInfo.cs | 6 +- 3 files changed, 169 insertions(+), 116 deletions(-) diff --git a/src/NzbDrone.Core/MediaFiles/CueSheet.cs b/src/NzbDrone.Core/MediaFiles/CueSheet.cs index 3f05ba080..7ff4ee458 100644 --- a/src/NzbDrone.Core/MediaFiles/CueSheet.cs +++ b/src/NzbDrone.Core/MediaFiles/CueSheet.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.MediaFiles { public int Number { get; set; } public string Title { get; set; } - public string Performer { get; set; } + public List Performers { get; set; } = new List(); public List Indices { get; set; } = new List(); } @@ -33,6 +33,6 @@ namespace NzbDrone.Core.MediaFiles public string Date { get; set; } public string DiscID { get; set; } public string Title { get; set; } - public string Performer { get; set; } + public List Performers { get; set; } = new List(); } } diff --git a/src/NzbDrone.Core/MediaFiles/CueSheetService.cs b/src/NzbDrone.Core/MediaFiles/CueSheetService.cs index 98c048e35..7517a7769 100644 --- a/src/NzbDrone.Core/MediaFiles/CueSheetService.cs +++ b/src/NzbDrone.Core/MediaFiles/CueSheetService.cs @@ -5,7 +5,6 @@ using System.IO.Abstractions; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaFiles.TrackImport; using NzbDrone.Core.Music; using NzbDrone.Core.Parser; @@ -61,7 +60,25 @@ namespace NzbDrone.Core.MediaFiles foreach (var cueFile in cueFiles) { var cueSheetInfo = GetCueSheetInfo(cueFile, mediaFileList); - cueSheetInfos.Add(cueSheetInfo); + + var addedCueSheetInfo = cueSheetInfos.Find(existingCueSheetInfo => existingCueSheetInfo.CueSheet.DiscID == cueSheetInfo.CueSheet.DiscID); + if (addedCueSheetInfo == null) + { + cueSheetInfos.Add(cueSheetInfo); + } + + // If there are multiple cue sheet files for the same disc, then we try to keep the last one or the one with the exact same name as the media file, if there's any + else if (cueSheetInfo.CueSheet.IsSingleFileRelease && addedCueSheetInfo.CueSheet.Files.Count > 0) + { + var mediaFileName = Path.GetFileName(addedCueSheetInfo.CueSheet.Files[0].Name); + var cueSheetFileName = Path.GetFileName(cueFile.Name); + + if (mediaFileName != cueSheetFileName) + { + cueSheetInfos.Remove(addedCueSheetInfo); + cueSheetInfos.Add(cueSheetInfo); + } + } } var cueSheetInfosGroupedByDiscId = cueSheetInfos.GroupBy(x => x.CueSheet.DiscID).ToList(); @@ -120,6 +137,11 @@ namespace NzbDrone.Core.MediaFiles return; } + if (decision.Item.Release == null) + { + return; + } + var tracksFromRelease = decision.Item.Release.Tracks.Value; if (tracksFromRelease.Count == 0) { @@ -172,126 +194,144 @@ namespace NzbDrone.Core.MediaFiles return ""; } + private List ExtractPerformers(string line) + { + var delimiters = new char[] { ',', ';' }; + var performers = ExtractValue(line, _PerformerKey); + return performers.Split(delimiters, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToList(); + } + + private bool GetNewLine(ref int index, ref string newLine, string[] lines) + { + if (index < lines.Length) + { + newLine = lines[index]; + index++; + return true; + } + + return false; + } + private CueSheet ParseLines(string[] lines) { var cueSheet = new CueSheet(); var i = 0; - try + string line = null; + + while (GetNewLine(ref i, ref line, lines)) { - while (true) + if (line.StartsWith(_FileKey)) { - var line = lines[i]; - if (line.StartsWith(_FileKey)) + line = line.Trim(); + line = line.Substring(_FileKey.Length).Trim(); + var filename = line.Split('"')[1]; + var fileDetails = new CueSheet.FileEntry { Name = filename }; + + if (!GetNewLine(ref i, ref line, lines)) + { + break; + } + + while (line.StartsWith(" ")) { line = line.Trim(); - line = line.Substring(_FileKey.Length).Trim(); - var filename = line.Split('"')[1]; - var fileDetails = new CueSheet.FileEntry { Name = filename }; - - i++; - line = lines[i]; - while (line.StartsWith(" ")) + if (line.StartsWith(_TrackKey)) { - line = line.Trim(); - if (line.StartsWith(_TrackKey)) - { - line = line.Substring(_TrackKey.Length).Trim(); - } - - var trackDetails = new CueSheet.TrackEntry(); - var trackInfo = line.Split(' '); - if (trackInfo.Length > 0) - { - if (int.TryParse(trackInfo[0], out var number)) - { - trackDetails.Number = number; - } - } - - i++; - line = lines[i]; - while (line.StartsWith(" ")) - { - line = line.Trim(); - if (line.StartsWith(_IndexKey)) - { - line = line.Substring(_IndexKey.Length).Trim(); - var parts = line.Split(' '); - if (parts.Length > 1) - { - if (int.TryParse(parts[0], out var key)) - { - var value = parts[1].Trim('"'); - trackDetails.Indices.Add(new CueSheet.IndexEntry { Key = key, Time = value }); - } - } - - i++; - line = lines[i]; - } - else if (line.StartsWith(_TitleKey)) - { - trackDetails.Title = ExtractValue(line, _TitleKey); - i++; - line = lines[i]; - } - else if (line.StartsWith(_PerformerKey)) - { - trackDetails.Performer = ExtractValue(line, _PerformerKey); - i++; - line = lines[i]; - } - else - { - i++; - line = lines[i]; - } - } - - fileDetails.Tracks.Add(trackDetails); + line = line.Substring(_TrackKey.Length).Trim(); } - cueSheet.Files.Add(fileDetails); - } - else if (line.StartsWith(_GenreKey)) - { - cueSheet.Genre = ExtractValue(line, _GenreKey); - i++; - } - else if (line.StartsWith(_DateKey)) - { - cueSheet.Date = ExtractValue(line, _DateKey); - i++; - } - else if (line.StartsWith(_DiscIdKey)) - { - cueSheet.DiscID = ExtractValue(line, _DiscIdKey); - i++; - } - else if (line.StartsWith(_PerformerKey)) - { - cueSheet.Performer = ExtractValue(line, _PerformerKey); - i++; - } - else if (line.StartsWith(_TitleKey)) - { - cueSheet.Title = ExtractValue(line, _TitleKey); - i++; - } - else - { - i++; + var trackDetails = new CueSheet.TrackEntry(); + var trackInfo = line.Split(' '); + if (trackInfo.Length > 0) + { + if (int.TryParse(trackInfo[0], out var number)) + { + trackDetails.Number = number; + } + } + + if (!GetNewLine(ref i, ref line, lines)) + { + break; + } + + while (line.StartsWith(" ")) + { + line = line.Trim(); + if (line.StartsWith(_IndexKey)) + { + line = line.Substring(_IndexKey.Length).Trim(); + var parts = line.Split(' '); + if (parts.Length > 1) + { + if (int.TryParse(parts[0], out var key)) + { + var value = parts[1].Trim('"'); + trackDetails.Indices.Add(new CueSheet.IndexEntry { Key = key, Time = value }); + } + } + } + else if (line.StartsWith(_TitleKey)) + { + trackDetails.Title = ExtractValue(line, _TitleKey); + } + else if (line.StartsWith(_PerformerKey)) + { + trackDetails.Performers = ExtractPerformers(line); + } + + if (!GetNewLine(ref i, ref line, lines)) + { + break; + } + } + + fileDetails.Tracks.Add(trackDetails); } + + cueSheet.Files.Add(fileDetails); + } + else if (line.StartsWith(_GenreKey)) + { + cueSheet.Genre = ExtractValue(line, _GenreKey); + } + else if (line.StartsWith(_DateKey)) + { + cueSheet.Date = ExtractValue(line, _DateKey); + } + else if (line.StartsWith(_DiscIdKey)) + { + cueSheet.DiscID = ExtractValue(line, _DiscIdKey); + } + else if (line.StartsWith(_PerformerKey)) + { + cueSheet.Performers = ExtractPerformers(line); + } + else if (line.StartsWith(_TitleKey)) + { + cueSheet.Title = ExtractValue(line, _TitleKey); } - } - catch (IndexOutOfRangeException) - { } return cueSheet; } + private Artist GetArtist(List performers) + { + if (performers.Count == 1) + { + return _parsingService.GetArtist(performers[0]); + } + else if (performers.Count > 1) + { + return _parsingService.GetArtist("Various Artist"); + } + + return null; + } + private CueSheetInfo GetCueSheetInfo(IFileInfo cueFile, List musicFiles) { var cueSheetInfo = new CueSheetInfo(); @@ -302,22 +342,37 @@ namespace NzbDrone.Core.MediaFiles } cueSheetInfo.CueSheet = cueSheet; + cueSheetInfo.MusicFiles = musicFiles.Where(musicFile => cueSheet.Files.Any(musicFileFromCue => musicFileFromCue.Name == musicFile.Name)).ToList(); + cueSheetInfo.IdOverrides = new IdentificationOverrides(); - Artist artistFromCue = null; - if (!cueSheet.Performer.Empty()) + var artistFromCue = GetArtist(cueSheet.Performers); + + if (artistFromCue == null && cueSheet.Files.Count > 0) { - artistFromCue = _parsingService.GetArtist(cueSheet.Performer); - if (artistFromCue != null) + foreach (var fileEntry in cueSheet.Files) { - cueSheetInfo.IdOverrides.Artist = artistFromCue; + foreach (var track in fileEntry.Tracks) + { + artistFromCue = GetArtist(track.Performers); + if (artistFromCue != null) + { + break; + } + } } } + // The cue sheet file is too incomplete in this case + if (artistFromCue == null) + { + return cueSheetInfo; + } + var parsedAlbumInfo = new ParsedAlbumInfo { AlbumTitle = cueSheet.Title, - ArtistName = artistFromCue?.Name, + ArtistName = artistFromCue.Name, ReleaseDate = cueSheet.Date, }; @@ -327,8 +382,6 @@ namespace NzbDrone.Core.MediaFiles cueSheetInfo.IdOverrides.Album = albumsFromCue[0]; } - cueSheetInfo.MusicFiles = musicFiles.Where(musicFile => cueSheet.Files.Any(musicFileFromCue => musicFileFromCue.Name == musicFile.Name)).ToList(); - return cueSheetInfo; } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs index 12be78699..1b0c96a0a 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs @@ -69,11 +69,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators { for (var i = 0; i < tracks.Count; ++i) { - tracks[i].FileTrackInfo.ArtistTitle = tracks[i].Artist.Name; - tracks[i].FileTrackInfo.AlbumTitle = tracks[i].Album.Title; + tracks[i].FileTrackInfo.ArtistTitle = tracks[i].Artist?.Name; + tracks[i].FileTrackInfo.AlbumTitle = tracks[i].Album?.Title; // TODO this is too bold, the release year is not the one from the .cue file - tracks[i].FileTrackInfo.Year = (uint)tracks[i].Album.ReleaseDate.Value.Year; + tracks[i].FileTrackInfo.Year = (uint)(tracks[i].Album?.ReleaseDate?.Year ?? 0); } } else