mirror of https://github.com/lidarr/Lidarr
Centralize most of the cue sheet-related features to a service.
(cherry picked from commit 5636735ae645280132d94e64d2e45f1a4b5f6323)
This commit is contained in:
parent
14bf91360a
commit
2d5b6a1def
|
@ -1,35 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public class CueSheet : ModelBase
|
||||
{
|
||||
public CueSheet(IFileInfo fileInfo)
|
||||
{
|
||||
Path = fileInfo.FullName;
|
||||
|
||||
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);
|
||||
ParseCueSheet(lines);
|
||||
|
||||
// Single-file cue means it's an unsplit image, which should be specially treated in the pipeline
|
||||
IsSingleFileRelease = Files.Count == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IndexEntry
|
||||
{
|
||||
public int Key { get; set; }
|
||||
|
@ -59,143 +34,5 @@ namespace NzbDrone.Core.MediaFiles
|
|||
public string DiscID { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Performer { get; set; }
|
||||
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";
|
||||
|
||||
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 void ParseCueSheet(string[] lines)
|
||||
{
|
||||
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 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 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 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);
|
||||
}
|
||||
|
||||
Files.Add(fileDetails);
|
||||
}
|
||||
else if (line.StartsWith(_GenreKey))
|
||||
{
|
||||
Genre = ExtractValue(line, _GenreKey);
|
||||
i++;
|
||||
}
|
||||
else if (line.StartsWith(_DateKey))
|
||||
{
|
||||
Date = ExtractValue(line, _DateKey);
|
||||
i++;
|
||||
}
|
||||
else if (line.StartsWith(_DiscIdKey))
|
||||
{
|
||||
DiscID = ExtractValue(line, _DiscIdKey);
|
||||
i++;
|
||||
}
|
||||
else if (line.StartsWith(_PerformerKey))
|
||||
{
|
||||
Performer = ExtractValue(line, _PerformerKey);
|
||||
i++;
|
||||
}
|
||||
else if (line.StartsWith(_TitleKey))
|
||||
{
|
||||
Title = ExtractValue(line, _TitleKey);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly ICueSheetService _cueSheetService;
|
||||
private readonly IImportApprovedTracks _importApprovedTracks;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||
|
@ -54,6 +55,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
IDiskProvider diskProvider,
|
||||
IMediaFileService mediaFileService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
ICueSheetService cueSheetService,
|
||||
IImportApprovedTracks importApprovedTracks,
|
||||
IArtistService artistService,
|
||||
IRootFolderService rootFolderService,
|
||||
|
@ -65,6 +67,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
_diskProvider = diskProvider;
|
||||
_mediaFileService = mediaFileService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_cueSheetService = cueSheetService;
|
||||
_importApprovedTracks = importApprovedTracks;
|
||||
_artistService = artistService;
|
||||
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||
|
@ -98,46 +101,8 @@ namespace NzbDrone.Core.MediaFiles
|
|||
};
|
||||
|
||||
var decisions = new List<ImportDecision<LocalTrack>>();
|
||||
var cueFiles = mediaFileList.Where(x => x.Extension.Equals(".cue")).ToList();
|
||||
if (cueFiles.Count > 0)
|
||||
{
|
||||
mediaFileList.RemoveAll(l => cueFiles.Contains(l));
|
||||
var cueSheetInfos = new List<CueSheetInfo>();
|
||||
foreach (var cueFile in cueFiles)
|
||||
{
|
||||
var cueSheetInfo = _importDecisionMaker.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decisions.AddRange(_cueSheetService.GetImportDecisions(ref mediaFileList, itemInfo, config));
|
||||
decisions.AddRange(_importDecisionMaker.GetImportDecisions(mediaFileList, null, itemInfo, config));
|
||||
|
||||
decisionsStopwatch.Stop();
|
||||
|
|
|
@ -132,41 +132,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
|
|||
i++;
|
||||
_logger.ProgressInfo($"Identifying album {i}/{releases.Count}");
|
||||
IdentifyRelease(localRelease, idOverrides, config);
|
||||
|
||||
if (cueSheetInfos != null && localRelease.IsSingleFileRelease)
|
||||
{
|
||||
var addedMbTracks = new List<Track>();
|
||||
localRelease.LocalTracks.ForEach(localTrack =>
|
||||
{
|
||||
var cueSheetFindResult = cueSheetInfos.Find(x => x.IsForMediaFile(localTrack.Path));
|
||||
var cueSheet = cueSheetFindResult?.CueSheet;
|
||||
if (cueSheet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
localTrack.Tracks.Clear();
|
||||
localRelease.AlbumRelease.Tracks.Value.ForEach(mbTrack =>
|
||||
{
|
||||
cueSheet.Files[0].Tracks.ForEach(cueTrack =>
|
||||
{
|
||||
if (!string.Equals(cueTrack.Title, mbTrack.Title, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addedMbTracks.Contains(mbTrack))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mbTrack.IsSingleFileRelease = true;
|
||||
localTrack.Tracks.Add(mbTrack);
|
||||
addedMbTracks.Add(mbTrack);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using DryIoc.ImTools;
|
||||
|
@ -22,8 +21,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
public interface IMakeImportDecision
|
||||
{
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(List<IFileInfo> musicFiles, IdentificationOverrides idOverrides, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config, List<CueSheetInfo> cueSheetInfos = null);
|
||||
List<ImportDecision<LocalTrack>> GetImportDecisions(List<CueSheetInfo> cueSheetInfos, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config);
|
||||
CueSheetInfo GetCueSheetInfo(IFileInfo cueFile, List<IFileInfo> musicFiles);
|
||||
}
|
||||
|
||||
public class IdentificationOverrides
|
||||
|
@ -33,14 +30,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
public AlbumRelease AlbumRelease { get; set; }
|
||||
}
|
||||
|
||||
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 class ImportDecisionMakerInfo
|
||||
{
|
||||
public DownloadClientItem DownloadClientItem { get; set; }
|
||||
|
@ -92,46 +81,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public CueSheetInfo GetCueSheetInfo(IFileInfo cueFile, List<IFileInfo> musicFiles)
|
||||
{
|
||||
var cueSheetInfo = new CueSheetInfo();
|
||||
var cueSheet = new CueSheet(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;
|
||||
}
|
||||
|
||||
public Tuple<List<LocalTrack>, List<ImportDecision<LocalTrack>>> GetLocalTracks(List<IFileInfo> musicFiles, DownloadClientItem downloadClientItem, ParsedAlbumInfo folderInfo, FilterFilesType filter)
|
||||
{
|
||||
var watch = new System.Diagnostics.Stopwatch();
|
||||
|
@ -284,17 +233,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
return decisions;
|
||||
}
|
||||
|
||||
public List<ImportDecision<LocalTrack>> GetImportDecisions(List<CueSheetInfo> cueSheetInfos, ImportDecisionMakerInfo itemInfo, ImportDecisionMakerConfig config)
|
||||
{
|
||||
var decisions = new List<ImportDecision<LocalTrack>>();
|
||||
foreach (var cueSheetInfo in cueSheetInfos)
|
||||
{
|
||||
decisions.AddRange(GetImportDecisions(cueSheetInfo.MusicFiles, cueSheetInfo.IdOverrides, itemInfo, config, cueSheetInfos));
|
||||
}
|
||||
|
||||
return decisions;
|
||||
}
|
||||
|
||||
private void EnsureData(LocalAlbumRelease release)
|
||||
{
|
||||
if (release.AlbumRelease != null && release.AlbumRelease.Album.Value.Artist.Value.QualityProfileId == 0)
|
||||
|
|
|
@ -37,6 +37,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly ICueSheetService _cueSheetService;
|
||||
private readonly ICustomFormatCalculationService _formatCalculator;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
|
@ -55,6 +56,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
IRootFolderService rootFolderService,
|
||||
IDiskScanService diskScanService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
ICueSheetService cueSheetService,
|
||||
ICustomFormatCalculationService formatCalculator,
|
||||
IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
|
@ -73,6 +75,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
_rootFolderService = rootFolderService;
|
||||
_diskScanService = diskScanService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_cueSheetService = cueSheetService;
|
||||
_formatCalculator = formatCalculator;
|
||||
_artistService = artistService;
|
||||
_albumService = albumService;
|
||||
|
@ -153,28 +156,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
var audioFiles = _diskScanService.GetAudioFiles(folder).ToList();
|
||||
var results = new List<ManualImportItem>();
|
||||
|
||||
// Split cue and non-cue files
|
||||
var cueFiles = audioFiles.Where(x => x.Extension.Equals(".cue")).ToList();
|
||||
if (cueFiles.Count > 0)
|
||||
{
|
||||
audioFiles.RemoveAll(l => cueFiles.Contains(l));
|
||||
var cueSheetInfos = new List<CueSheetInfo>();
|
||||
foreach (var cueFile in cueFiles)
|
||||
{
|
||||
var cueSheetInfo = _importDecisionMaker.GetCueSheetInfo(cueFile, audioFiles);
|
||||
cueSheetInfos.Add(cueSheetInfo);
|
||||
}
|
||||
|
||||
var cueSheetInfosGroupedByDiscId = cueSheetInfos.GroupBy(x => x.CueSheet.DiscID).ToList();
|
||||
foreach (var cueSheetInfoGroup in cueSheetInfosGroupedByDiscId)
|
||||
{
|
||||
var manualImportItems = ProcessFolder(downloadId, filter, replaceExistingFiles, downloadClientItem, cueSheetInfoGroup.ToList());
|
||||
results.AddRange(manualImportItems);
|
||||
|
||||
RemoveProcessedAudioFiles(audioFiles, cueSheetInfos, manualImportItems);
|
||||
}
|
||||
}
|
||||
|
||||
var idOverrides = new IdentificationOverrides
|
||||
{
|
||||
Artist = artist,
|
||||
|
@ -186,36 +167,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
return results;
|
||||
}
|
||||
|
||||
private void RemoveProcessedAudioFiles(List<IFileInfo> audioFiles, List<CueSheetInfo> cueSheetInfos, List<ManualImportItem> manualImportItems)
|
||||
{
|
||||
foreach (var cueSheetInfo in cueSheetInfos)
|
||||
{
|
||||
if (cueSheetInfo.CueSheet != null)
|
||||
{
|
||||
manualImportItems.ForEach(item =>
|
||||
{
|
||||
if (cueSheetInfo.IsForMediaFile(item.Path))
|
||||
{
|
||||
item.CueSheetPath = cueSheetInfo.CueSheet.Path;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
audioFiles.RemoveAll(x => cueSheetInfo.MusicFiles.Contains(x));
|
||||
}
|
||||
}
|
||||
|
||||
private List<ManualImportItem> ProcessFolder(string downloadId, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, List<CueSheetInfo> cueSheetInfos)
|
||||
{
|
||||
var audioFilesForCues = new List<IFileInfo>();
|
||||
foreach (var cueSheetInfo in cueSheetInfos)
|
||||
{
|
||||
audioFilesForCues.AddRange(cueSheetInfo.MusicFiles);
|
||||
}
|
||||
|
||||
return ProcessFolder(downloadId, cueSheetInfos[0].IdOverrides, filter, replaceExistingFiles, downloadClientItem, cueSheetInfos[0].CueSheet.Title, audioFilesForCues, cueSheetInfos);
|
||||
}
|
||||
|
||||
private List<ManualImportItem> ProcessFolder(string downloadId, IdentificationOverrides idOverrides, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, string albumTitle, List<IFileInfo> audioFiles, List<CueSheetInfo> cueSheetInfos = null)
|
||||
{
|
||||
idOverrides ??= new IdentificationOverrides();
|
||||
|
@ -234,7 +185,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
AddNewArtists = false
|
||||
};
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(audioFiles, idOverrides, itemInfo, config, cueSheetInfos);
|
||||
var decisions = _cueSheetService.GetImportDecisions(ref audioFiles, itemInfo, config);
|
||||
if (audioFiles.Count > 0)
|
||||
{
|
||||
decisions.AddRange(_importDecisionMaker.GetImportDecisions(audioFiles, idOverrides, itemInfo, config, cueSheetInfos));
|
||||
}
|
||||
|
||||
// paths will be different for new and old files which is why we need to map separately
|
||||
var newFiles = audioFiles.Join(decisions,
|
||||
|
@ -280,40 +235,32 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
{
|
||||
var file = _diskProvider.GetFileInfo(item.Path);
|
||||
audioFiles.Add(file);
|
||||
}
|
||||
|
||||
var cueSheetInfos = new List<CueSheetInfo>();
|
||||
var audioFilesForCues = new List<IFileInfo>();
|
||||
var itemInfo = new ImportDecisionMakerInfo();
|
||||
foreach (var item in group)
|
||||
{
|
||||
if (item.IsSingleFileRelease)
|
||||
if (item.CueSheetPath != null)
|
||||
{
|
||||
var cueFile = _diskProvider.GetFileInfo(item.CueSheetPath);
|
||||
var cueSheetInfo = _importDecisionMaker.GetCueSheetInfo(cueFile, audioFiles);
|
||||
cueSheetInfos.Add(cueSheetInfo);
|
||||
audioFilesForCues.AddRange(cueSheetInfo.MusicFiles);
|
||||
audioFiles.Add(cueFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFilesForCues.Count > 0)
|
||||
{
|
||||
var idOverrides = cueSheetInfos.Count > 0 ? cueSheetInfos[0].IdOverrides : null;
|
||||
var singleFileReleaseDecisions = _importDecisionMaker.GetImportDecisions(audioFilesForCues, idOverrides, itemInfo, config, cueSheetInfos);
|
||||
var manualImportItems = UpdateItems(group, singleFileReleaseDecisions, replaceExistingFiles, disableReleaseSwitching);
|
||||
result.AddRange(manualImportItems);
|
||||
|
||||
RemoveProcessedAudioFiles(audioFiles, cueSheetInfos, manualImportItems);
|
||||
}
|
||||
|
||||
var itemInfo = new ImportDecisionMakerInfo();
|
||||
var idOverride = new IdentificationOverrides
|
||||
{
|
||||
Artist = group.First().Artist,
|
||||
Album = group.First().Album,
|
||||
AlbumRelease = group.First().Release
|
||||
};
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(audioFiles, idOverride, itemInfo, config);
|
||||
result.AddRange(UpdateItems(group, decisions, replaceExistingFiles, disableReleaseSwitching));
|
||||
|
||||
var decisions = _cueSheetService.GetImportDecisions(ref audioFiles, itemInfo, config);
|
||||
if (audioFiles.Count > 0)
|
||||
{
|
||||
decisions.AddRange(_importDecisionMaker.GetImportDecisions(audioFiles, idOverride, itemInfo, config));
|
||||
}
|
||||
|
||||
if (decisions.Count > 0)
|
||||
{
|
||||
result.AddRange(UpdateItems(group, decisions, replaceExistingFiles, disableReleaseSwitching));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
Loading…
Reference in New Issue