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)
This commit is contained in:
zhangdoa 2023-11-05 13:13:33 +01:00
commit 88abe75927
3 changed files with 169 additions and 116 deletions

View file

@ -15,7 +15,7 @@ namespace NzbDrone.Core.MediaFiles
{ {
public int Number { get; set; } public int Number { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Performer { get; set; } public List<string> Performers { get; set; } = new List<string>();
public List<IndexEntry> Indices { get; set; } = new List<IndexEntry>(); public List<IndexEntry> Indices { get; set; } = new List<IndexEntry>();
} }
@ -33,6 +33,6 @@ namespace NzbDrone.Core.MediaFiles
public string Date { get; set; } public string Date { get; set; }
public string DiscID { get; set; } public string DiscID { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Performer { get; set; } public List<string> Performers { get; set; } = new List<string>();
} }
} }

View file

@ -5,7 +5,6 @@ using System.IO.Abstractions;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles.TrackImport; using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -61,9 +60,27 @@ namespace NzbDrone.Core.MediaFiles
foreach (var cueFile in cueFiles) foreach (var cueFile in cueFiles)
{ {
var cueSheetInfo = GetCueSheetInfo(cueFile, mediaFileList); var cueSheetInfo = GetCueSheetInfo(cueFile, mediaFileList);
var addedCueSheetInfo = cueSheetInfos.Find(existingCueSheetInfo => existingCueSheetInfo.CueSheet.DiscID == cueSheetInfo.CueSheet.DiscID);
if (addedCueSheetInfo == null)
{
cueSheetInfos.Add(cueSheetInfo); 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(); var cueSheetInfosGroupedByDiscId = cueSheetInfos.GroupBy(x => x.CueSheet.DiscID).ToList();
foreach (var cueSheetInfoGroup in cueSheetInfosGroupedByDiscId) foreach (var cueSheetInfoGroup in cueSheetInfosGroupedByDiscId)
{ {
@ -120,6 +137,11 @@ namespace NzbDrone.Core.MediaFiles
return; return;
} }
if (decision.Item.Release == null)
{
return;
}
var tracksFromRelease = decision.Item.Release.Tracks.Value; var tracksFromRelease = decision.Item.Release.Tracks.Value;
if (tracksFromRelease.Count == 0) if (tracksFromRelease.Count == 0)
{ {
@ -172,16 +194,34 @@ namespace NzbDrone.Core.MediaFiles
return ""; return "";
} }
private List<string> 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) private CueSheet ParseLines(string[] lines)
{ {
var cueSheet = new CueSheet(); var cueSheet = new CueSheet();
var i = 0; var i = 0;
try string line = null;
while (GetNewLine(ref i, ref line, lines))
{ {
while (true)
{
var line = lines[i];
if (line.StartsWith(_FileKey)) if (line.StartsWith(_FileKey))
{ {
line = line.Trim(); line = line.Trim();
@ -189,8 +229,11 @@ namespace NzbDrone.Core.MediaFiles
var filename = line.Split('"')[1]; var filename = line.Split('"')[1];
var fileDetails = new CueSheet.FileEntry { Name = filename }; var fileDetails = new CueSheet.FileEntry { Name = filename };
i++; if (!GetNewLine(ref i, ref line, lines))
line = lines[i]; {
break;
}
while (line.StartsWith(" ")) while (line.StartsWith(" "))
{ {
line = line.Trim(); line = line.Trim();
@ -209,8 +252,11 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
i++; if (!GetNewLine(ref i, ref line, lines))
line = lines[i]; {
break;
}
while (line.StartsWith(" ")) while (line.StartsWith(" "))
{ {
line = line.Trim(); line = line.Trim();
@ -226,26 +272,19 @@ namespace NzbDrone.Core.MediaFiles
trackDetails.Indices.Add(new CueSheet.IndexEntry { Key = key, Time = value }); trackDetails.Indices.Add(new CueSheet.IndexEntry { Key = key, Time = value });
} }
} }
i++;
line = lines[i];
} }
else if (line.StartsWith(_TitleKey)) else if (line.StartsWith(_TitleKey))
{ {
trackDetails.Title = ExtractValue(line, _TitleKey); trackDetails.Title = ExtractValue(line, _TitleKey);
i++;
line = lines[i];
} }
else if (line.StartsWith(_PerformerKey)) else if (line.StartsWith(_PerformerKey))
{ {
trackDetails.Performer = ExtractValue(line, _PerformerKey); trackDetails.Performers = ExtractPerformers(line);
i++;
line = lines[i];
} }
else
if (!GetNewLine(ref i, ref line, lines))
{ {
i++; break;
line = lines[i];
} }
} }
@ -257,41 +296,42 @@ namespace NzbDrone.Core.MediaFiles
else if (line.StartsWith(_GenreKey)) else if (line.StartsWith(_GenreKey))
{ {
cueSheet.Genre = ExtractValue(line, _GenreKey); cueSheet.Genre = ExtractValue(line, _GenreKey);
i++;
} }
else if (line.StartsWith(_DateKey)) else if (line.StartsWith(_DateKey))
{ {
cueSheet.Date = ExtractValue(line, _DateKey); cueSheet.Date = ExtractValue(line, _DateKey);
i++;
} }
else if (line.StartsWith(_DiscIdKey)) else if (line.StartsWith(_DiscIdKey))
{ {
cueSheet.DiscID = ExtractValue(line, _DiscIdKey); cueSheet.DiscID = ExtractValue(line, _DiscIdKey);
i++;
} }
else if (line.StartsWith(_PerformerKey)) else if (line.StartsWith(_PerformerKey))
{ {
cueSheet.Performer = ExtractValue(line, _PerformerKey); cueSheet.Performers = ExtractPerformers(line);
i++;
} }
else if (line.StartsWith(_TitleKey)) else if (line.StartsWith(_TitleKey))
{ {
cueSheet.Title = ExtractValue(line, _TitleKey); cueSheet.Title = ExtractValue(line, _TitleKey);
i++;
} }
else
{
i++;
}
}
}
catch (IndexOutOfRangeException)
{
} }
return cueSheet; return cueSheet;
} }
private Artist GetArtist(List<string> 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<IFileInfo> musicFiles) private CueSheetInfo GetCueSheetInfo(IFileInfo cueFile, List<IFileInfo> musicFiles)
{ {
var cueSheetInfo = new CueSheetInfo(); var cueSheetInfo = new CueSheetInfo();
@ -302,22 +342,37 @@ namespace NzbDrone.Core.MediaFiles
} }
cueSheetInfo.CueSheet = cueSheet; cueSheetInfo.CueSheet = cueSheet;
cueSheetInfo.MusicFiles = musicFiles.Where(musicFile => cueSheet.Files.Any(musicFileFromCue => musicFileFromCue.Name == musicFile.Name)).ToList();
cueSheetInfo.IdOverrides = new IdentificationOverrides(); cueSheetInfo.IdOverrides = new IdentificationOverrides();
Artist artistFromCue = null; var artistFromCue = GetArtist(cueSheet.Performers);
if (!cueSheet.Performer.Empty())
if (artistFromCue == null && cueSheet.Files.Count > 0)
{ {
artistFromCue = _parsingService.GetArtist(cueSheet.Performer); foreach (var fileEntry in cueSheet.Files)
{
foreach (var track in fileEntry.Tracks)
{
artistFromCue = GetArtist(track.Performers);
if (artistFromCue != null) if (artistFromCue != null)
{ {
cueSheetInfo.IdOverrides.Artist = artistFromCue; break;
} }
} }
}
}
// The cue sheet file is too incomplete in this case
if (artistFromCue == null)
{
return cueSheetInfo;
}
var parsedAlbumInfo = new ParsedAlbumInfo var parsedAlbumInfo = new ParsedAlbumInfo
{ {
AlbumTitle = cueSheet.Title, AlbumTitle = cueSheet.Title,
ArtistName = artistFromCue?.Name, ArtistName = artistFromCue.Name,
ReleaseDate = cueSheet.Date, ReleaseDate = cueSheet.Date,
}; };
@ -327,8 +382,6 @@ namespace NzbDrone.Core.MediaFiles
cueSheetInfo.IdOverrides.Album = albumsFromCue[0]; cueSheetInfo.IdOverrides.Album = albumsFromCue[0];
} }
cueSheetInfo.MusicFiles = musicFiles.Where(musicFile => cueSheet.Files.Any(musicFileFromCue => musicFileFromCue.Name == musicFile.Name)).ToList();
return cueSheetInfo; return cueSheetInfo;
} }
} }

View file

@ -69,11 +69,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators
{ {
for (var i = 0; i < tracks.Count; ++i) for (var i = 0; i < tracks.Count; ++i)
{ {
tracks[i].FileTrackInfo.ArtistTitle = tracks[i].Artist.Name; tracks[i].FileTrackInfo.ArtistTitle = tracks[i].Artist?.Name;
tracks[i].FileTrackInfo.AlbumTitle = tracks[i].Album.Title; tracks[i].FileTrackInfo.AlbumTitle = tracks[i].Album?.Title;
// TODO this is too bold, the release year is not the one from the .cue file // 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 else