mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-22 06:23:31 -07:00
Add the support to copy .cue files to the media library folder.
(cherry picked from commit 57ae74b49afea29100882edbe6e49fa24210bbbf)
This commit is contained in:
parent
31016bca8a
commit
16a3fbe25b
13 changed files with 58 additions and 8 deletions
|
@ -58,6 +58,11 @@ const columns = [
|
||||||
label: () => 'Is Single File Release',
|
label: () => 'Is Single File Release',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'cuesheetPath',
|
||||||
|
label: () => 'Cuesheet Path',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'releaseGroup',
|
name: 'releaseGroup',
|
||||||
label: () => translate('ReleaseGroup'),
|
label: () => translate('ReleaseGroup'),
|
||||||
|
@ -441,6 +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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
|
@ -135,6 +135,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
albumReleaseId,
|
albumReleaseId,
|
||||||
tracks,
|
tracks,
|
||||||
isSingleFileRelease,
|
isSingleFileRelease,
|
||||||
|
cuesheetPath,
|
||||||
quality,
|
quality,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
} = item;
|
} = item;
|
||||||
|
@ -149,7 +150,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSingleFileRelease && (!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;
|
||||||
}
|
}
|
||||||
|
@ -166,6 +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,
|
||||||
quality,
|
quality,
|
||||||
downloadId: this.props.downloadId,
|
downloadId: this.props.downloadId,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
|
|
|
@ -65,6 +65,7 @@ class InteractiveImportRow extends Component {
|
||||||
album,
|
album,
|
||||||
tracks,
|
tracks,
|
||||||
isSingleFileRelease,
|
isSingleFileRelease,
|
||||||
|
cuesheetPath,
|
||||||
quality,
|
quality,
|
||||||
isSelected,
|
isSelected,
|
||||||
onValidRowChange
|
onValidRowChange
|
||||||
|
@ -83,7 +84,7 @@ class InteractiveImportRow extends Component {
|
||||||
const isValid = !!(
|
const isValid = !!(
|
||||||
artist &&
|
artist &&
|
||||||
album &&
|
album &&
|
||||||
(isSingleFileRelease || tracks.length) &&
|
((isSingleFileRelease && cuesheetPath) || tracks.length) &&
|
||||||
quality
|
quality
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -169,6 +170,7 @@ class InteractiveImportRow extends Component {
|
||||||
albumReleaseId,
|
albumReleaseId,
|
||||||
tracks,
|
tracks,
|
||||||
isSingleFileRelease,
|
isSingleFileRelease,
|
||||||
|
cuesheetPath,
|
||||||
quality,
|
quality,
|
||||||
releaseGroup,
|
releaseGroup,
|
||||||
size,
|
size,
|
||||||
|
@ -281,6 +283,15 @@ class InteractiveImportRow extends Component {
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell
|
||||||
|
id={id}
|
||||||
|
title={'Cuesheet Path'}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
cuesheetPath
|
||||||
|
}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCellButton
|
<TableRowCellButton
|
||||||
title={translate('ClickToChangeReleaseGroup')}
|
title={translate('ClickToChangeReleaseGroup')}
|
||||||
onPress={this.onSelectReleaseGroupPress}
|
onPress={this.onSelectReleaseGroupPress}
|
||||||
|
@ -422,6 +433,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,
|
||||||
releaseGroup: PropTypes.string,
|
releaseGroup: PropTypes.string,
|
||||||
quality: PropTypes.object,
|
quality: PropTypes.object,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
|
|
|
@ -207,6 +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,
|
||||||
quality: item.quality,
|
quality: item.quality,
|
||||||
releaseGroup: item.releaseGroup,
|
releaseGroup: item.releaseGroup,
|
||||||
downloadId: item.downloadId,
|
downloadId: item.downloadId,
|
||||||
|
|
|
@ -85,6 +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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +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 static class ManualImportResourceMapper
|
public static class ManualImportResourceMapper
|
||||||
|
@ -54,6 +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,
|
||||||
|
|
||||||
// QualityWeight
|
// QualityWeight
|
||||||
DownloadId = model.DownloadId,
|
DownloadId = model.DownloadId,
|
||||||
|
|
|
@ -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 IEnumerable<Rejection> Rejections { get; set; }
|
public IEnumerable<Rejection> Rejections { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
CleanMediaFiles(folder, files.Select(x => x.FullName).ToList());
|
CleanMediaFiles(folder, files.Select(x => x.FullName).ToList());
|
||||||
mediaFileList.AddRange(files);
|
mediaFileList.AddRange(files);
|
||||||
|
mediaFileList.RemoveAll(x => x.Extension == ".cue");
|
||||||
}
|
}
|
||||||
|
|
||||||
musicFilesStopwatch.Stop();
|
musicFilesStopwatch.Stop();
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||||
|
@ -90,6 +91,21 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
EnsureTrackFolder(trackFile, localTrack, filePath);
|
EnsureTrackFolder(trackFile, localTrack, filePath);
|
||||||
|
|
||||||
|
if (!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 fileLineIndex = lines.FindIndex(line => line.Contains("FILE"));
|
||||||
|
if (fileLineIndex != -1)
|
||||||
|
{
|
||||||
|
lines[fileLineIndex] = "FILE \"" + Path.GetFileName(filePath) + "\" WAVE";
|
||||||
|
File.WriteAllLines(cuesheetPath, lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_configService.CopyUsingHardlinks)
|
if (_configService.CopyUsingHardlinks)
|
||||||
{
|
{
|
||||||
_logger.Debug("Attempting to hardlink track file: {0} to {1}", trackFile.Path, filePath);
|
_logger.Debug("Attempting to hardlink track file: {0} to {1}", trackFile.Path, filePath);
|
||||||
|
|
|
@ -16,6 +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 bool Equals(ManualImportFile other)
|
public bool Equals(ManualImportFile other)
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,5 +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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,6 +186,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
audioFiles.RemoveAll(l => cueFiles.Contains(l));
|
audioFiles.RemoveAll(l => cueFiles.Contains(l));
|
||||||
foreach (var cueFile in cueFiles)
|
foreach (var cueFile in cueFiles)
|
||||||
{
|
{
|
||||||
|
// TODO move this to the disk service
|
||||||
using (var fs = cueFile.OpenRead())
|
using (var fs = cueFile.OpenRead())
|
||||||
{
|
{
|
||||||
var bytes = new byte[cueFile.Length];
|
var bytes = new byte[cueFile.Length];
|
||||||
|
@ -239,19 +240,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
audioFile
|
audioFile
|
||||||
};
|
};
|
||||||
|
|
||||||
results.AddRange(ProcessFolder(downloadId, artistFromCue, albumsFromCue[0], filter, replaceExistingFiles, downloadClientItem, albumTitle, tempAudioFiles, true));
|
results.AddRange(ProcessFolder(downloadId, artistFromCue, albumsFromCue[0], filter, replaceExistingFiles, downloadClientItem, albumTitle, tempAudioFiles, cueFile.FullName));
|
||||||
audioFiles.Remove(audioFile);
|
audioFiles.Remove(audioFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results.AddRange(ProcessFolder(downloadId, artist, null, filter, replaceExistingFiles, downloadClientItem, directoryInfo.Name, audioFiles, false));
|
results.AddRange(ProcessFolder(downloadId, artist, null, filter, replaceExistingFiles, downloadClientItem, directoryInfo.Name, audioFiles, string.Empty));
|
||||||
|
|
||||||
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, bool isSingleFileRelease)
|
private List<ManualImportItem> ProcessFolder(string downloadId, Artist overrideArtist, Album overrideAlbum, FilterFilesType filter, bool replaceExistingFiles, DownloadClientItem downloadClientItem, string albumTitle, List<IFileInfo> audioFiles, string cuesheetPath)
|
||||||
{
|
{
|
||||||
var idOverrides = new IdentificationOverrides
|
var idOverrides = new IdentificationOverrides
|
||||||
{
|
{
|
||||||
|
@ -262,7 +263,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
{
|
{
|
||||||
DownloadClientItem = downloadClientItem,
|
DownloadClientItem = downloadClientItem,
|
||||||
ParsedAlbumInfo = Parser.Parser.ParseAlbumTitle(albumTitle),
|
ParsedAlbumInfo = Parser.Parser.ParseAlbumTitle(albumTitle),
|
||||||
IsSingleFileRelease = isSingleFileRelease
|
IsSingleFileRelease = !cuesheetPath.Empty()
|
||||||
};
|
};
|
||||||
var config = new ImportDecisionMakerConfig
|
var config = new ImportDecisionMakerConfig
|
||||||
{
|
{
|
||||||
|
@ -286,7 +287,10 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
var existingDecisions = decisions.Except(newFiles.Select(x => x.Decision));
|
var existingDecisions = decisions.Except(newFiles.Select(x => x.Decision));
|
||||||
var existingItems = existingDecisions.Select(x => MapItem(x, null, replaceExistingFiles, false));
|
var existingItems = existingDecisions.Select(x => MapItem(x, null, replaceExistingFiles, false));
|
||||||
|
|
||||||
return newItems.Concat(existingItems).ToList();
|
var itemsList = newItems.Concat(existingItems).ToList();
|
||||||
|
itemsList.ForEach(item => { item.CuesheetPath = cuesheetPath; });
|
||||||
|
|
||||||
|
return itemsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ManualImportItem> UpdateItems(List<ManualImportItem> items)
|
public List<ManualImportItem> UpdateItems(List<ManualImportItem> items)
|
||||||
|
@ -405,6 +409,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;
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@ -454,6 +459,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
Album = album,
|
Album = album,
|
||||||
Release = release,
|
Release = release,
|
||||||
IsSingleFileRelease = file.IsSingleFileRelease,
|
IsSingleFileRelease = file.IsSingleFileRelease,
|
||||||
|
CuesheetPath = file.CuesheetPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (file.IsSingleFileRelease)
|
if (file.IsSingleFileRelease)
|
||||||
|
|
|
@ -32,6 +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 override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Path;
|
return Path;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue