(cherry picked from commit 5636735ae645280132d94e64d2e45f1a4b5f6323)pull/4200/head
parent
14bf91360a
commit
2d5b6a1def
@ -0,0 +1,332 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
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;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public class CueSheetInfo
|
||||
{
|
||||
public List<IFileInfo> MusicFiles { get; set; } = new List<IFileInfo>();
|
||||
public IdentificationOverrides IdOverrides { get; set; }
|
||||
public CueSheet CueSheet { get; set; }
|
||||
public bool IsForMediaFile(string path) => CueSheet != null && CueSheet.Files.Count > 0 && CueSheet.Files.Any(x => Path.GetFileName(path) == x.Name);
|
||||
}
|
||||
|
||||
public interface ICueSheetService
|
||||
{
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(ref List<IFileInfo> mediaFileList, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config);
|
||||
}
|
||||
|
||||
public class CueSheetService : ICueSheetService
|
||||
{
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
|
||||
private static string _FileKey = "FILE";
|
||||
private static string _TrackKey = "TRACK";
|
||||
private static string _IndexKey = "INDEX";
|
||||
private static string _GenreKey = "REM GENRE";
|
||||
private static string _DateKey = "REM DATE";
|
||||
private static string _DiscIdKey = "REM DISCID";
|
||||
private static string _PerformerKey = "PERFORMER";
|
||||
private static string _TitleKey = "TITLE";
|
||||
|
||||
public CueSheetService(IParsingService parsingService,
|
||||
IMakeImportDecision importDecisionMaker)
|
||||
{
|
||||
_parsingService = parsingService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
}
|
||||
|
||||
public List<ImportDecision<LocalTrack>> GetImportDecisions(ref List<IFileInfo> mediaFileList, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config)
|
||||
{
|
||||
var decisions = new List<ImportDecision<LocalTrack>>();
|
||||
var cueFiles = mediaFileList.Where(x => x.Extension.Equals(".cue")).ToList();
|
||||
if (cueFiles.Count == 0)
|
||||
{
|
||||
return decisions;
|
||||
}
|
||||
|
||||
mediaFileList.RemoveAll(l => cueFiles.Contains(l));
|
||||
var cueSheetInfos = new List<CueSheetInfo>();
|
||||
foreach (var cueFile in cueFiles)
|
||||
{
|
||||
var cueSheetInfo = GetCueSheetInfo(cueFile, mediaFileList);
|
||||
cueSheetInfos.Add(cueSheetInfo);
|
||||
}
|
||||
|
||||
var cueSheetInfosGroupedByDiscId = cueSheetInfos.GroupBy(x => x.CueSheet.DiscID).ToList();
|
||||
foreach (var cueSheetInfoGroup in cueSheetInfosGroupedByDiscId)
|
||||
{
|
||||
var audioFilesForCues = new List<IFileInfo>();
|
||||
foreach (var cueSheetInfo in cueSheetInfoGroup)
|
||||
{
|
||||
audioFilesForCues.AddRange(cueSheetInfo.MusicFiles);
|
||||
}
|
||||
|
||||
decisions.AddRange(_importDecisionMaker.GetImportDecisions(audioFilesForCues, cueSheetInfoGroup.First().IdOverrides, itemInfo, config, cueSheetInfos));
|
||||
|
||||
foreach (var cueSheetInfo in cueSheetInfos)
|
||||
{
|
||||
if (cueSheetInfo.CueSheet != null)
|
||||
{
|
||||
decisions.ForEach(item =>
|
||||
{
|
||||
if (cueSheetInfo.IsForMediaFile(item.Item.Path))
|
||||
{
|
||||
item.Item.CueSheetPath = cueSheetInfo.CueSheet.Path;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mediaFileList.RemoveAll(x => cueSheetInfo.MusicFiles.Contains(x));
|
||||
}
|
||||
}
|
||||
|
||||
var addedTracks = new List<Track>();
|
||||
decisions.ForEach(decision =>
|
||||
{
|
||||
if (!decision.Item.IsSingleFileRelease)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cueSheetFindResult = cueSheetInfos.Find(x => x.IsForMediaFile(decision.Item.Path));
|
||||
var cueSheet = cueSheetFindResult?.CueSheet;
|
||||
if (cueSheet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (cueSheet.Files.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tracksFromCueSheet = cueSheet.Files.SelectMany(x => x.Tracks).ToList();
|
||||
if (tracksFromCueSheet.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tracksFromRelease = decision.Item.Release.Tracks.Value;
|
||||
if (tracksFromRelease.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
decision.Item.Tracks = tracksFromRelease.Where(trackFromRelease => !addedTracks.Contains(trackFromRelease) && tracksFromCueSheet.Any(trackFromCueSheet => string.Equals(trackFromCueSheet.Title, trackFromRelease.Title, StringComparison.InvariantCultureIgnoreCase))).ToList();
|
||||
addedTracks.AddRange(decision.Item.Tracks);
|
||||
});
|
||||
|
||||
return decisions;
|
||||
}
|
||||
|
||||
private CueSheet LoadCueSheet(IFileInfo fileInfo)
|
||||
{
|
||||
using (var fs = fileInfo.OpenRead())
|
||||
{
|
||||
var bytes = new byte[fileInfo.Length];
|
||||
var encoding = new UTF8Encoding(true);
|
||||
string content;
|
||||
while (fs.Read(bytes, 0, bytes.Length) > 0)
|
||||
{
|
||||
content = encoding.GetString(bytes);
|
||||
var lines = content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
var cueSheet = ParseLines(lines);
|
||||
|
||||
// Single-file cue means it's an unsplit image, which should be specially treated in the pipeline
|
||||
cueSheet.IsSingleFileRelease = cueSheet.Files.Count == 1;
|
||||
cueSheet.Path = fileInfo.FullName;
|
||||
|
||||
return cueSheet;
|
||||
}
|
||||
}
|
||||
|
||||
return new CueSheet();
|
||||
}
|
||||
|
||||
private string ExtractValue(string line, string keyword)
|
||||
{
|
||||
var pattern = keyword + @"\s+(?:(?:\""(.*?)\"")|(.+))";
|
||||
var match = Regex.Match(line, pattern);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
var value = match.Groups[1].Success ? match.Groups[1].Value : match.Groups[2].Value;
|
||||
return value;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private CueSheet ParseLines(string[] lines)
|
||||
{
|
||||
var cueSheet = new CueSheet();
|
||||
|
||||
var i = 0;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
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 };
|
||||
|
||||
i++;
|
||||
line = lines[i];
|
||||
while (line.StartsWith(" "))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
}
|
||||
|
||||
return cueSheet;
|
||||
}
|
||||
|
||||
private CueSheetInfo GetCueSheetInfo(IFileInfo cueFile, List<IFileInfo> musicFiles)
|
||||
{
|
||||
var cueSheetInfo = new CueSheetInfo();
|
||||
var cueSheet = LoadCueSheet(cueFile);
|
||||
if (cueSheet == null)
|
||||
{
|
||||
return cueSheetInfo;
|
||||
}
|
||||
|
||||
cueSheetInfo.CueSheet = cueSheet;
|
||||
cueSheetInfo.IdOverrides = new IdentificationOverrides();
|
||||
|
||||
Artist artistFromCue = null;
|
||||
if (!cueSheet.Performer.Empty())
|
||||
{
|
||||
artistFromCue = _parsingService.GetArtist(cueSheet.Performer);
|
||||
if (artistFromCue != null)
|
||||
{
|
||||
cueSheetInfo.IdOverrides.Artist = artistFromCue;
|
||||
}
|
||||
}
|
||||
|
||||
var parsedAlbumInfo = new ParsedAlbumInfo
|
||||
{
|
||||
AlbumTitle = cueSheet.Title,
|
||||
ArtistName = artistFromCue.Name,
|
||||
ReleaseDate = cueSheet.Date,
|
||||
};
|
||||
|
||||
var albumsFromCue = _parsingService.GetAlbums(parsedAlbumInfo, artistFromCue);
|
||||
if (albumsFromCue != null && albumsFromCue.Count > 0)
|
||||
{
|
||||
cueSheetInfo.IdOverrides.Album = albumsFromCue[0];
|
||||
}
|
||||
|
||||
cueSheetInfo.MusicFiles = musicFiles.Where(musicFile => cueSheet.Files.Any(musicFileFromCue => musicFileFromCue.Name == musicFile.Name)).ToList();
|
||||
|
||||
return cueSheetInfo;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue