diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index d90ba63b9..f57dcbe3a 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -59,8 +59,8 @@ const columns = [ isVisible: true }, { - name: 'cuesheetPath', - label: () => 'Cuesheet Path', + name: 'cueSheetPath', + label: () => 'Cue Sheet Path', isVisible: true }, { @@ -446,7 +446,7 @@ class InteractiveImportModalContent extends Component { onSelectedChange={this.onSelectedChange} onValidRowChange={this.onValidRowChange} isSingleFileRelease={item.isSingleFileRelease} - cuesheetPath={item.cuesheetPath} + cueSheetPath={item.cueSheetPath} /> ); }) diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js index f7d094733..019d7c212 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js @@ -135,7 +135,7 @@ class InteractiveImportModalContentConnector extends Component { albumReleaseId, tracks, isSingleFileRelease, - cuesheetPath, + cueSheetPath, quality, disableReleaseSwitching } = item; @@ -150,7 +150,7 @@ class InteractiveImportModalContentConnector extends Component { return false; } - if (!(isSingleFileRelease && cuesheetPath) && (!tracks || !tracks.length)) { + if (!(isSingleFileRelease && cueSheetPath) && (!tracks || !tracks.length)) { this.setState({ interactiveImportErrorMessage: 'One or more tracks must be chosen for each selected file' }); return false; } @@ -167,7 +167,7 @@ class InteractiveImportModalContentConnector extends Component { albumReleaseId, trackIds: _.map(tracks, 'id'), isSingleFileRelease: item.isSingleFileRelease, - cuesheetPath: item.cuesheetPath, + cueSheetPath: item.cueSheetPath, quality, downloadId: this.props.downloadId, disableReleaseSwitching diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index 470553638..a2c05de62 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -169,7 +169,7 @@ class InteractiveImportRow extends Component { albumReleaseId, tracks, isSingleFileRelease, - cuesheetPath, + cueSheetPath, quality, releaseGroup, size, @@ -284,10 +284,10 @@ class InteractiveImportRow extends Component { { - cuesheetPath + cueSheetPath } @@ -432,7 +432,7 @@ InteractiveImportRow.propTypes = { albumReleaseId: PropTypes.number, tracks: PropTypes.arrayOf(PropTypes.object), isSingleFileRelease: PropTypes.bool.isRequired, - cuesheetPath: PropTypes.string.isRequired, + cueSheetPath: PropTypes.string.isRequired, releaseGroup: PropTypes.string, quality: PropTypes.object, size: PropTypes.number.isRequired, diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index 2dc63b6d2..037a56c3a 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -207,7 +207,7 @@ export const actionHandlers = handleThunks({ albumReleaseId: item.albumReleaseId ? item.albumReleaseId : undefined, trackIds: (item.tracks || []).map((e) => e.id), isSingleFileRelease: item.isSingleFileRelease, - cuesheetPath: item.cuesheetPath, + cueSheetPath: item.cueSheetPath, quality: item.quality, releaseGroup: item.releaseGroup, downloadId: item.downloadId, diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportController.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportController.cs index baafd6468..6b27dbd38 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportController.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportController.cs @@ -85,7 +85,7 @@ namespace Lidarr.Api.V1.ManualImport ReplaceExistingFiles = resource.ReplaceExistingFiles, DisableReleaseSwitching = resource.DisableReleaseSwitching, IsSingleFileRelease = resource.IsSingleFileRelease, - CuesheetPath = resource.CuesheetPath, + CueSheetPath = resource.CueSheetPath, }); } diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs index 4f132d3f9..a410d2537 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs @@ -30,7 +30,7 @@ namespace Lidarr.Api.V1.ManualImport public bool ReplaceExistingFiles { get; set; } public bool DisableReleaseSwitching { get; set; } public bool IsSingleFileRelease { get; set; } - public string CuesheetPath { get; set; } + public string CueSheetPath { get; set; } } public static class ManualImportResourceMapper @@ -55,7 +55,7 @@ namespace Lidarr.Api.V1.ManualImport Quality = model.Quality, ReleaseGroup = model.ReleaseGroup, IsSingleFileRelease = model.IsSingleFileRelease, - CuesheetPath = model.CuesheetPath, + CueSheetPath = model.CueSheetPath, // QualityWeight DownloadId = model.DownloadId, diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs index 0a443f715..4e9ec7c03 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs @@ -22,7 +22,7 @@ namespace Lidarr.Api.V1.ManualImport public bool ReplaceExistingFiles { get; set; } public bool DisableReleaseSwitching { get; set; } public bool IsSingleFileRelease { get; set; } - public string CuesheetPath { get; set; } + public string CueSheetPath { get; set; } public IEnumerable Rejections { get; set; } } } diff --git a/src/NzbDrone.Core/MediaFiles/CueSheet.cs b/src/NzbDrone.Core/MediaFiles/CueSheet.cs index bcf330f58..a22cb6c0a 100644 --- a/src/NzbDrone.Core/MediaFiles/CueSheet.cs +++ b/src/NzbDrone.Core/MediaFiles/CueSheet.cs @@ -12,6 +12,8 @@ namespace NzbDrone.Core.MediaFiles { public CueSheet(IFileInfo fileInfo) { + Path = fileInfo.FullName; + using (var fs = fileInfo.OpenRead()) { var bytes = new byte[fileInfo.Length]; @@ -23,58 +25,66 @@ namespace NzbDrone.Core.MediaFiles var lines = content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); // Single-file cue means it's an unsplit image - var fileNames = ReadFieldFromCuesheet(lines, "FILE"); - IsSingleFileRelease = fileNames.Count == 1; - FileName = fileNames[0]; + FileNames = ReadField(lines, "FILE"); + IsSingleFileRelease = FileNames.Count == 1; - var performers = ReadFieldFromCuesheet(lines, "PERFORMER"); + var performers = ReadField(lines, "PERFORMER"); if (performers.Count > 0) { Performer = performers[0]; } - var titles = ReadFieldFromCuesheet(lines, "TITLE"); + var titles = ReadField(lines, "TITLE"); if (titles.Count > 0) { Title = titles[0]; } - Date = ReadOptionalFieldFromCuesheet(lines, "REM DATE"); + var dates = ReadField(lines, "REM DATE"); + if (dates.Count > 0) + { + Date = dates[0]; + } } } } + public string Path { get; set; } public bool IsSingleFileRelease { get; set; } - public string FileName { get; set; } + public List FileNames { get; set; } public string Title { get; set; } public string Performer { get; set; } public string Date { get; set; } - private static List ReadFieldFromCuesheet(string[] lines, string fieldName) + private static List ReadField(string[] lines, string fieldName) { + var inQuotePattern = "\"(.*?)\""; + var flatPattern = fieldName + " (.+)"; + var results = new List(); var candidates = lines.Where(l => l.StartsWith(fieldName)).ToList(); foreach (var candidate in candidates) { - var matches = Regex.Matches(candidate, "\"(.*?)\""); - var result = matches.ToList()[0].Groups[1].Value; - results.Add(result); + var matches = Regex.Matches(candidate, inQuotePattern).ToList(); + if (matches.Count == 0) + { + matches = Regex.Matches(candidate, flatPattern).ToList(); + } + + if (matches.Count == 0) + { + continue; + } + + var groups = matches[0].Groups; + if (groups.Count > 0) + { + var result = groups[1].Value; + results.Add(result); + } } return results; } - - private static string ReadOptionalFieldFromCuesheet(string[] lines, string fieldName) - { - var results = lines.Where(l => l.StartsWith(fieldName)); - if (results.Any()) - { - var matches = Regex.Matches(results.ToList()[0], fieldName + " (.+)"); - var result = matches.ToList()[0].Groups[1].Value; - return result; - } - - return ""; - } } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs index 9abcf26de..5872047ba 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs @@ -91,18 +91,18 @@ namespace NzbDrone.Core.MediaFiles EnsureTrackFolder(trackFile, localTrack, filePath); - if (!localTrack.CuesheetPath.Empty()) + if (localTrack.IsSingleFileRelease && !localTrack.CueSheetPath.Empty()) { var directory = Path.GetDirectoryName(filePath); var fileName = Path.GetFileNameWithoutExtension(filePath); - var cuesheetPath = Path.Combine(directory, fileName + ".cue"); - _diskTransferService.TransferFile(localTrack.CuesheetPath, cuesheetPath, TransferMode.Copy); - var lines = new List(File.ReadAllLines(cuesheetPath)); + var cueSheetPath = Path.Combine(directory, fileName + ".cue"); + _diskTransferService.TransferFile(localTrack.CueSheetPath, cueSheetPath, TransferMode.Copy); + var lines = new List(File.ReadAllLines(cueSheetPath)); var fileLineIndex = lines.FindIndex(line => line.Contains("FILE")); if (fileLineIndex != -1) { lines[fileLineIndex] = "FILE \"" + Path.GetFileName(filePath) + "\" WAVE"; - File.WriteAllLines(cuesheetPath, lines); + File.WriteAllLines(cueSheetPath, lines); } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs index b047d3770..b1bbc4ec6 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs @@ -34,6 +34,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport public DownloadClientItem DownloadClientItem { get; set; } public ParsedAlbumInfo ParsedAlbumInfo { get; set; } public bool IsSingleFileRelease { get; set; } + public CueSheet CueSheet { get; set; } } public class ImportDecisionMakerConfig diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs index f3d6cd830..598ae785c 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual public string DownloadId { get; set; } public bool DisableReleaseSwitching { get; set; } public bool IsSingleFileRelease { get; set; } - public string CuesheetPath { get; set; } + public string CueSheetPath { get; set; } public bool Equals(ManualImportFile other) { diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs index 6102d8ad2..8456dfa5f 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs @@ -33,6 +33,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual public bool ReplaceExistingFiles { get; set; } public bool DisableReleaseSwitching { get; set; } public bool IsSingleFileRelease { get; set; } - public string CuesheetPath { get; set; } + public string CueSheetPath { get; set; } } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs index ae3ccc650..3adf9cd4f 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs @@ -165,7 +165,18 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual artistFromCue = _parsingService.GetArtist(cueSheet.Performer); } - var audioFile = audioFiles.Find(x => x.Name == cueSheet.FileName && x.DirectoryName == cueFile.DirectoryName); + if (artistFromCue == null) + { + continue; + } + + // TODO use the audio files from the cue sheet + var validAudioFiles = audioFiles.FindAll(x => cueSheet.FileNames.Contains(x.Name)); + if (validAudioFiles.Count == 0) + { + continue; + } + var parsedAlbumInfo = new ParsedAlbumInfo { AlbumTitle = cueSheet.Title, @@ -178,18 +189,16 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual continue; } - var tempAudioFiles = new List { audioFile }; - - results.AddRange(ProcessFolder(downloadId, artistFromCue, albumsFromCue[0], filter, replaceExistingFiles, downloadClientItem, cueSheet.Title, tempAudioFiles, cueFile.FullName)); - audioFiles.Remove(audioFile); + results.AddRange(ProcessFolder(downloadId, artistFromCue, albumsFromCue[0], filter, replaceExistingFiles, downloadClientItem, cueSheet.Title, validAudioFiles, cueSheet)); + audioFiles.RemoveAll(x => validAudioFiles.Contains(x)); } - results.AddRange(ProcessFolder(downloadId, artist, null, filter, replaceExistingFiles, downloadClientItem, directoryInfo.Name, audioFiles, string.Empty)); + results.AddRange(ProcessFolder(downloadId, artist, null, filter, replaceExistingFiles, downloadClientItem, directoryInfo.Name, audioFiles, null)); return results; } - private List ProcessFolder(string downloadId, Artist overrideArtist, Album overrideAlbum, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, string albumTitle, List audioFiles, string cuesheetPath) + private List ProcessFolder(string downloadId, Artist overrideArtist, Album overrideAlbum, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, string albumTitle, List audioFiles, CueSheet cueSheet) { var idOverrides = new IdentificationOverrides { @@ -200,7 +209,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual { DownloadClientItem = downloadClientItem, ParsedAlbumInfo = Parser.Parser.ParseAlbumTitle(albumTitle), - IsSingleFileRelease = !cuesheetPath.Empty() + CueSheet = cueSheet, + IsSingleFileRelease = cueSheet != null ? cueSheet.IsSingleFileRelease : false, }; var config = new ImportDecisionMakerConfig { @@ -225,7 +235,10 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual var existingItems = existingDecisions.Select(x => MapItem(x, null, replaceExistingFiles, false)); var itemsList = newItems.Concat(existingItems).ToList(); - itemsList.ForEach(item => { item.CuesheetPath = cuesheetPath; }); + if (cueSheet != null) + { + itemsList.ForEach(item => { item.CueSheetPath = cueSheet.Path; }); + } return itemsList; } @@ -265,7 +278,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual IsSingleFileRelease = group.All(x => x.IsSingleFileRelease == true) }; - // TODO support with the cuesheet + // TODO support with the cue sheet var decisions = _importDecisionMaker.GetImportDecisions(files, idOverride, itemInfo, config); var existingItems = group.Join(decisions, @@ -353,7 +366,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual item.ReplaceExistingFiles = replaceExistingFiles; item.DisableReleaseSwitching = disableReleaseSwitching; item.IsSingleFileRelease = decision.Item.IsSingleFileRelease; - item.CuesheetPath = decision.Item.CuesheetPath; + item.CueSheetPath = decision.Item.CueSheetPath; return item; } @@ -403,7 +416,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual Album = album, Release = release, IsSingleFileRelease = file.IsSingleFileRelease, - CuesheetPath = file.CuesheetPath, + CueSheetPath = file.CueSheetPath, }; if (file.IsSingleFileRelease) diff --git a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs index ee929c588..422ac2b87 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Parser.Model public string ReleaseGroup { get; set; } public string SceneName { get; set; } public bool IsSingleFileRelease { get; set; } - public string CuesheetPath { get; set; } + public string CueSheetPath { get; set; } public override string ToString() { return Path;