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

Rename "cuesheet" to "cue sheet".

(cherry picked from commit c9686684ea82e7af8ad203d1bcbb7983adb9e293)
pull/4200/head
zhangdoa 7 months ago
parent c87efacb6a
commit a7f07df588

@ -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}
/>
);
})

@ -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

@ -169,7 +169,7 @@ class InteractiveImportRow extends Component {
albumReleaseId,
tracks,
isSingleFileRelease,
cuesheetPath,
cueSheetPath,
quality,
releaseGroup,
size,
@ -284,10 +284,10 @@ class InteractiveImportRow extends Component {
<TableRowCell
id={id}
title={'Cuesheet Path'}
title={'Cue Sheet Path'}
>
{
cuesheetPath
cueSheetPath
}
</TableRowCell>
@ -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,

@ -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,

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

@ -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,

@ -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<Rejection> Rejections { get; set; }
}
}

@ -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<string> FileNames { get; set; }
public string Title { get; set; }
public string Performer { 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 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();
}
return results;
}
if (matches.Count == 0)
{
continue;
}
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;
var groups = matches[0].Groups;
if (groups.Count > 0)
{
var result = groups[1].Value;
results.Add(result);
}
}
return "";
return results;
}
}
}

@ -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<string>(File.ReadAllLines(cuesheetPath));
var cueSheetPath = Path.Combine(directory, fileName + ".cue");
_diskTransferService.TransferFile(localTrack.CueSheetPath, cueSheetPath, TransferMode.Copy);
var lines = new List<string>(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);
}
}

@ -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

@ -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)
{

@ -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; }
}
}

@ -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<IFileInfo> { 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<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
{
@ -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)

@ -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;

Loading…
Cancel
Save