Add multi-file/track support to the cue sheet loader.

Rename "cuesheet" to "cue sheet".

(cherry picked from commit c9686684ea82e7af8ad203d1bcbb7983adb9e293)
This commit is contained in:
zhangdoa 2023-10-08 13:29:59 +02:00
commit a7f07df588
14 changed files with 83 additions and 59 deletions

View file

@ -59,8 +59,8 @@ const columns = [
isVisible: true isVisible: true
}, },
{ {
name: 'cuesheetPath', name: 'cueSheetPath',
label: () => 'Cuesheet Path', label: () => 'Cue Sheet Path',
isVisible: true isVisible: true
}, },
{ {
@ -446,7 +446,7 @@ class InteractiveImportModalContent extends Component {
onSelectedChange={this.onSelectedChange} onSelectedChange={this.onSelectedChange}
onValidRowChange={this.onValidRowChange} onValidRowChange={this.onValidRowChange}
isSingleFileRelease={item.isSingleFileRelease} isSingleFileRelease={item.isSingleFileRelease}
cuesheetPath={item.cuesheetPath} cueSheetPath={item.cueSheetPath}
/> />
); );
}) })

View file

@ -135,7 +135,7 @@ class InteractiveImportModalContentConnector extends Component {
albumReleaseId, albumReleaseId,
tracks, tracks,
isSingleFileRelease, isSingleFileRelease,
cuesheetPath, cueSheetPath,
quality, quality,
disableReleaseSwitching disableReleaseSwitching
} = item; } = item;
@ -150,7 +150,7 @@ class InteractiveImportModalContentConnector extends Component {
return false; 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' }); this.setState({ interactiveImportErrorMessage: 'One or more tracks must be chosen for each selected file' });
return false; return false;
} }
@ -167,7 +167,7 @@ class InteractiveImportModalContentConnector extends Component {
albumReleaseId, albumReleaseId,
trackIds: _.map(tracks, 'id'), trackIds: _.map(tracks, 'id'),
isSingleFileRelease: item.isSingleFileRelease, isSingleFileRelease: item.isSingleFileRelease,
cuesheetPath: item.cuesheetPath, cueSheetPath: item.cueSheetPath,
quality, quality,
downloadId: this.props.downloadId, downloadId: this.props.downloadId,
disableReleaseSwitching disableReleaseSwitching

View file

@ -169,7 +169,7 @@ class InteractiveImportRow extends Component {
albumReleaseId, albumReleaseId,
tracks, tracks,
isSingleFileRelease, isSingleFileRelease,
cuesheetPath, cueSheetPath,
quality, quality,
releaseGroup, releaseGroup,
size, size,
@ -284,10 +284,10 @@ class InteractiveImportRow extends Component {
<TableRowCell <TableRowCell
id={id} id={id}
title={'Cuesheet Path'} title={'Cue Sheet Path'}
> >
{ {
cuesheetPath cueSheetPath
} }
</TableRowCell> </TableRowCell>
@ -432,7 +432,7 @@ InteractiveImportRow.propTypes = {
albumReleaseId: PropTypes.number, albumReleaseId: PropTypes.number,
tracks: PropTypes.arrayOf(PropTypes.object), tracks: PropTypes.arrayOf(PropTypes.object),
isSingleFileRelease: PropTypes.bool.isRequired, isSingleFileRelease: PropTypes.bool.isRequired,
cuesheetPath: PropTypes.string.isRequired, cueSheetPath: PropTypes.string.isRequired,
releaseGroup: PropTypes.string, releaseGroup: PropTypes.string,
quality: PropTypes.object, quality: PropTypes.object,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,

View file

@ -207,7 +207,7 @@ export const actionHandlers = handleThunks({
albumReleaseId: item.albumReleaseId ? item.albumReleaseId : undefined, albumReleaseId: item.albumReleaseId ? item.albumReleaseId : undefined,
trackIds: (item.tracks || []).map((e) => e.id), trackIds: (item.tracks || []).map((e) => e.id),
isSingleFileRelease: item.isSingleFileRelease, isSingleFileRelease: item.isSingleFileRelease,
cuesheetPath: item.cuesheetPath, cueSheetPath: item.cueSheetPath,
quality: item.quality, quality: item.quality,
releaseGroup: item.releaseGroup, releaseGroup: item.releaseGroup,
downloadId: item.downloadId, downloadId: item.downloadId,

View file

@ -85,7 +85,7 @@ namespace Lidarr.Api.V1.ManualImport
ReplaceExistingFiles = resource.ReplaceExistingFiles, ReplaceExistingFiles = resource.ReplaceExistingFiles,
DisableReleaseSwitching = resource.DisableReleaseSwitching, DisableReleaseSwitching = resource.DisableReleaseSwitching,
IsSingleFileRelease = resource.IsSingleFileRelease, IsSingleFileRelease = resource.IsSingleFileRelease,
CuesheetPath = resource.CuesheetPath, CueSheetPath = resource.CueSheetPath,
}); });
} }

View file

@ -30,7 +30,7 @@ namespace Lidarr.Api.V1.ManualImport
public bool ReplaceExistingFiles { get; set; } public bool ReplaceExistingFiles { get; set; }
public bool DisableReleaseSwitching { get; set; } public bool DisableReleaseSwitching { get; set; }
public bool IsSingleFileRelease { get; set; } public bool IsSingleFileRelease { get; set; }
public string CuesheetPath { get; set; } public string CueSheetPath { get; set; }
} }
public static class ManualImportResourceMapper public static class ManualImportResourceMapper
@ -55,7 +55,7 @@ namespace Lidarr.Api.V1.ManualImport
Quality = model.Quality, Quality = model.Quality,
ReleaseGroup = model.ReleaseGroup, ReleaseGroup = model.ReleaseGroup,
IsSingleFileRelease = model.IsSingleFileRelease, IsSingleFileRelease = model.IsSingleFileRelease,
CuesheetPath = model.CuesheetPath, CueSheetPath = model.CueSheetPath,
// QualityWeight // QualityWeight
DownloadId = model.DownloadId, DownloadId = model.DownloadId,

View file

@ -22,7 +22,7 @@ namespace Lidarr.Api.V1.ManualImport
public bool ReplaceExistingFiles { get; set; } public bool ReplaceExistingFiles { get; set; }
public bool DisableReleaseSwitching { get; set; } public bool DisableReleaseSwitching { get; set; }
public bool IsSingleFileRelease { get; set; } public bool IsSingleFileRelease { get; set; }
public string CuesheetPath { get; set; } public string CueSheetPath { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
} }
} }

View file

@ -12,6 +12,8 @@ namespace NzbDrone.Core.MediaFiles
{ {
public CueSheet(IFileInfo fileInfo) public CueSheet(IFileInfo fileInfo)
{ {
Path = fileInfo.FullName;
using (var fs = fileInfo.OpenRead()) using (var fs = fileInfo.OpenRead())
{ {
var bytes = new byte[fileInfo.Length]; var bytes = new byte[fileInfo.Length];
@ -23,58 +25,66 @@ namespace NzbDrone.Core.MediaFiles
var lines = content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); var lines = content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
// Single-file cue means it's an unsplit image // Single-file cue means it's an unsplit image
var fileNames = ReadFieldFromCuesheet(lines, "FILE"); FileNames = ReadField(lines, "FILE");
IsSingleFileRelease = fileNames.Count == 1; IsSingleFileRelease = FileNames.Count == 1;
FileName = fileNames[0];
var performers = ReadFieldFromCuesheet(lines, "PERFORMER"); var performers = ReadField(lines, "PERFORMER");
if (performers.Count > 0) if (performers.Count > 0)
{ {
Performer = performers[0]; Performer = performers[0];
} }
var titles = ReadFieldFromCuesheet(lines, "TITLE"); var titles = ReadField(lines, "TITLE");
if (titles.Count > 0) if (titles.Count > 0)
{ {
Title = titles[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 bool IsSingleFileRelease { get; set; }
public string FileName { get; set; } public List<string> FileNames { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Performer { get; set; } public string Performer { get; set; }
public string Date { get; set; } public string Date { get; set; }
private static List<string> ReadFieldFromCuesheet(string[] lines, string fieldName) private static List<string> ReadField(string[] lines, string fieldName)
{ {
var inQuotePattern = "\"(.*?)\"";
var flatPattern = fieldName + " (.+)";
var results = new List<string>(); var results = new List<string>();
var candidates = lines.Where(l => l.StartsWith(fieldName)).ToList(); var candidates = lines.Where(l => l.StartsWith(fieldName)).ToList();
foreach (var candidate in candidates) foreach (var candidate in candidates)
{ {
var matches = Regex.Matches(candidate, "\"(.*?)\""); var matches = Regex.Matches(candidate, inQuotePattern).ToList();
var result = matches.ToList()[0].Groups[1].Value; if (matches.Count == 0)
results.Add(result); {
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; 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 "";
}
} }
} }

View file

@ -91,18 +91,18 @@ namespace NzbDrone.Core.MediaFiles
EnsureTrackFolder(trackFile, localTrack, filePath); EnsureTrackFolder(trackFile, localTrack, filePath);
if (!localTrack.CuesheetPath.Empty()) if (localTrack.IsSingleFileRelease && !localTrack.CueSheetPath.Empty())
{ {
var directory = Path.GetDirectoryName(filePath); var directory = Path.GetDirectoryName(filePath);
var fileName = Path.GetFileNameWithoutExtension(filePath); var fileName = Path.GetFileNameWithoutExtension(filePath);
var cuesheetPath = Path.Combine(directory, fileName + ".cue"); var cueSheetPath = Path.Combine(directory, fileName + ".cue");
_diskTransferService.TransferFile(localTrack.CuesheetPath, cuesheetPath, TransferMode.Copy); _diskTransferService.TransferFile(localTrack.CueSheetPath, cueSheetPath, TransferMode.Copy);
var lines = new List<string>(File.ReadAllLines(cuesheetPath)); var lines = new List<string>(File.ReadAllLines(cueSheetPath));
var fileLineIndex = lines.FindIndex(line => line.Contains("FILE")); var fileLineIndex = lines.FindIndex(line => line.Contains("FILE"));
if (fileLineIndex != -1) if (fileLineIndex != -1)
{ {
lines[fileLineIndex] = "FILE \"" + Path.GetFileName(filePath) + "\" WAVE"; lines[fileLineIndex] = "FILE \"" + Path.GetFileName(filePath) + "\" WAVE";
File.WriteAllLines(cuesheetPath, lines); File.WriteAllLines(cueSheetPath, lines);
} }
} }

View file

@ -34,6 +34,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
public DownloadClientItem DownloadClientItem { get; set; } public DownloadClientItem DownloadClientItem { get; set; }
public ParsedAlbumInfo ParsedAlbumInfo { get; set; } public ParsedAlbumInfo ParsedAlbumInfo { get; set; }
public bool IsSingleFileRelease { get; set; } public bool IsSingleFileRelease { get; set; }
public CueSheet CueSheet { get; set; }
} }
public class ImportDecisionMakerConfig public class ImportDecisionMakerConfig

View file

@ -16,7 +16,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
public string DownloadId { get; set; } public string DownloadId { get; set; }
public bool DisableReleaseSwitching { get; set; } public bool DisableReleaseSwitching { get; set; }
public bool IsSingleFileRelease { get; set; } public bool IsSingleFileRelease { get; set; }
public string CuesheetPath { get; set; } public string CueSheetPath { get; set; }
public bool Equals(ManualImportFile other) public bool Equals(ManualImportFile other)
{ {

View file

@ -33,6 +33,6 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
public bool ReplaceExistingFiles { get; set; } public bool ReplaceExistingFiles { get; set; }
public bool DisableReleaseSwitching { get; set; } public bool DisableReleaseSwitching { get; set; }
public bool IsSingleFileRelease { get; set; } public bool IsSingleFileRelease { get; set; }
public string CuesheetPath { get; set; } public string CueSheetPath { get; set; }
} }
} }

View file

@ -165,7 +165,18 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
artistFromCue = _parsingService.GetArtist(cueSheet.Performer); 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 var parsedAlbumInfo = new ParsedAlbumInfo
{ {
AlbumTitle = cueSheet.Title, AlbumTitle = cueSheet.Title,
@ -178,18 +189,16 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
continue; continue;
} }
var tempAudioFiles = new List<IFileInfo> { 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, artistFromCue, albumsFromCue[0], filter, replaceExistingFiles, downloadClientItem, cueSheet.Title, tempAudioFiles, cueFile.FullName));
audioFiles.Remove(audioFile);
} }
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; return results;
} }
private List<ManualImportItem> ProcessFolder(string downloadId, Artist overrideArtist, Album overrideAlbum, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, string albumTitle, List<IFileInfo> audioFiles, string cuesheetPath) private List<ManualImportItem> ProcessFolder(string downloadId, Artist overrideArtist, Album overrideAlbum, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, string albumTitle, List<IFileInfo> audioFiles, CueSheet cueSheet)
{ {
var idOverrides = new IdentificationOverrides var idOverrides = new IdentificationOverrides
{ {
@ -200,7 +209,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{ {
DownloadClientItem = downloadClientItem, DownloadClientItem = downloadClientItem,
ParsedAlbumInfo = Parser.Parser.ParseAlbumTitle(albumTitle), ParsedAlbumInfo = Parser.Parser.ParseAlbumTitle(albumTitle),
IsSingleFileRelease = !cuesheetPath.Empty() CueSheet = cueSheet,
IsSingleFileRelease = cueSheet != null ? cueSheet.IsSingleFileRelease : false,
}; };
var config = new ImportDecisionMakerConfig 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 existingItems = existingDecisions.Select(x => MapItem(x, null, replaceExistingFiles, false));
var itemsList = newItems.Concat(existingItems).ToList(); var itemsList = newItems.Concat(existingItems).ToList();
itemsList.ForEach(item => { item.CuesheetPath = cuesheetPath; }); if (cueSheet != null)
{
itemsList.ForEach(item => { item.CueSheetPath = cueSheet.Path; });
}
return itemsList; return itemsList;
} }
@ -265,7 +278,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
IsSingleFileRelease = group.All(x => x.IsSingleFileRelease == true) 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 decisions = _importDecisionMaker.GetImportDecisions(files, idOverride, itemInfo, config);
var existingItems = group.Join(decisions, var existingItems = group.Join(decisions,
@ -353,7 +366,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
item.ReplaceExistingFiles = replaceExistingFiles; item.ReplaceExistingFiles = replaceExistingFiles;
item.DisableReleaseSwitching = disableReleaseSwitching; item.DisableReleaseSwitching = disableReleaseSwitching;
item.IsSingleFileRelease = decision.Item.IsSingleFileRelease; item.IsSingleFileRelease = decision.Item.IsSingleFileRelease;
item.CuesheetPath = decision.Item.CuesheetPath; item.CueSheetPath = decision.Item.CueSheetPath;
return item; return item;
} }
@ -403,7 +416,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
Album = album, Album = album,
Release = release, Release = release,
IsSingleFileRelease = file.IsSingleFileRelease, IsSingleFileRelease = file.IsSingleFileRelease,
CuesheetPath = file.CuesheetPath, CueSheetPath = file.CueSheetPath,
}; };
if (file.IsSingleFileRelease) if (file.IsSingleFileRelease)

View file

@ -32,7 +32,7 @@ namespace NzbDrone.Core.Parser.Model
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public string SceneName { get; set; } public string SceneName { get; set; }
public bool IsSingleFileRelease { get; set; } public bool IsSingleFileRelease { get; set; }
public string CuesheetPath { get; set; } public string CueSheetPath { get; set; }
public override string ToString() public override string ToString()
{ {
return Path; return Path;