diff --git a/frontend/src/Album/Details/TrackRow.js b/frontend/src/Album/Details/TrackRow.js
index 5f60df882..e79672860 100644
--- a/frontend/src/Album/Details/TrackRow.js
+++ b/frontend/src/Album/Details/TrackRow.js
@@ -28,6 +28,7 @@ class TrackRow extends Component {
absoluteTrackNumber,
title,
duration,
+ isSingleFileRelease,
trackFilePath,
trackFileSize,
customFormats,
@@ -86,7 +87,7 @@ class TrackRow extends Component {
return (
{
- trackFilePath
+ isSingleFileRelease ? `${trackFilePath} (Single File)` : trackFilePath
}
);
@@ -203,6 +204,7 @@ TrackRow.propTypes = {
absoluteTrackNumber: PropTypes.number,
title: PropTypes.string.isRequired,
duration: PropTypes.number.isRequired,
+ isSingleFileRelease: PropTypes.bool.isRequired,
isSaving: PropTypes.bool,
trackFilePath: PropTypes.string,
trackFileSize: PropTypes.number,
diff --git a/frontend/src/Album/Details/TrackRowConnector.js b/frontend/src/Album/Details/TrackRowConnector.js
index aee72e39a..37f4fc00a 100644
--- a/frontend/src/Album/Details/TrackRowConnector.js
+++ b/frontend/src/Album/Details/TrackRowConnector.js
@@ -13,7 +13,8 @@ function createMapStateToProps() {
trackFilePath: trackFile ? trackFile.path : null,
trackFileSize: trackFile ? trackFile.size : null,
customFormats: trackFile ? trackFile.customFormats : [],
- customFormatScore: trackFile ? trackFile.customFormatScore : 0
+ customFormatScore: trackFile ? trackFile.customFormatScore : 0,
+ isSingleFileRelease: trackFile ? trackFile.isSingleFileRelease : false
};
}
);
diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
index d1361a785..220b13a4b 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
@@ -53,6 +53,11 @@ const columns = [
label: () => translate('Tracks'),
isVisible: true
},
+ {
+ name: 'isSingleFileRelease',
+ label: () => 'Is Single File Release',
+ isVisible: true
+ },
{
name: 'releaseGroup',
label: () => translate('ReleaseGroup'),
@@ -435,6 +440,7 @@ class InteractiveImportModalContent extends Component {
allowArtistChange={allowArtistChange}
onSelectedChange={this.onSelectedChange}
onValidRowChange={this.onValidRowChange}
+ isSingleFileRelease={item.isSingleFileRelease}
/>
);
})
diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js
index f40da69ee..d05b38e06 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js
@@ -134,6 +134,7 @@ class InteractiveImportModalContentConnector extends Component {
album,
albumReleaseId,
tracks,
+ isSingleFileRelease,
quality,
disableReleaseSwitching
} = item;
@@ -148,7 +149,7 @@ class InteractiveImportModalContentConnector extends Component {
return false;
}
- if (!tracks || !tracks.length) {
+ if (!isSingleFileRelease && (!tracks || !tracks.length)) {
this.setState({ interactiveImportErrorMessage: 'One or more tracks must be chosen for each selected file' });
return false;
}
@@ -164,6 +165,7 @@ class InteractiveImportModalContentConnector extends Component {
albumId: album.id,
albumReleaseId,
trackIds: _.map(tracks, 'id'),
+ isSingleFileRelease: item.isSingleFileRelease,
quality,
downloadId: this.props.downloadId,
disableReleaseSwitching
diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js
index b914c0996..797ad389e 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js
@@ -64,6 +64,7 @@ class InteractiveImportRow extends Component {
artist,
album,
tracks,
+ isSingleFileRelease,
quality,
isSelected,
onValidRowChange
@@ -82,7 +83,7 @@ class InteractiveImportRow extends Component {
const isValid = !!(
artist &&
album &&
- tracks.length &&
+ (isSingleFileRelease || tracks.length) &&
quality
);
@@ -167,6 +168,7 @@ class InteractiveImportRow extends Component {
album,
albumReleaseId,
tracks,
+ isSingleFileRelease,
quality,
releaseGroup,
size,
@@ -257,7 +259,7 @@ class InteractiveImportRow extends Component {
@@ -265,10 +267,20 @@ class InteractiveImportRow extends Component {
showTrackNumbersLoading &&
}
{
- showTrackNumbersPlaceholder ? : trackNumbers
+ !isSingleFileRelease && showTrackNumbersPlaceholder ? : trackNumbers
}
+
+
+ {
+ isSingleFileRelease ? 'Yes' : 'No'
+ }
+
+
e.id),
+ isSingleFileRelease: item.isSingleFileRelease,
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 b11c36a91..071fbe05f 100644
--- a/src/Lidarr.Api.V1/ManualImport/ManualImportController.cs
+++ b/src/Lidarr.Api.V1/ManualImport/ManualImportController.cs
@@ -83,7 +83,8 @@ namespace Lidarr.Api.V1.ManualImport
DownloadId = resource.DownloadId,
AdditionalFile = resource.AdditionalFile,
ReplaceExistingFiles = resource.ReplaceExistingFiles,
- DisableReleaseSwitching = resource.DisableReleaseSwitching
+ DisableReleaseSwitching = resource.DisableReleaseSwitching,
+ IsSingleFileRelease = resource.IsSingleFileRelease,
});
}
diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs
index 4b38b4f7c..5d4f29815 100644
--- a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs
+++ b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs
@@ -29,6 +29,7 @@ namespace Lidarr.Api.V1.ManualImport
public bool AdditionalFile { get; set; }
public bool ReplaceExistingFiles { get; set; }
public bool DisableReleaseSwitching { get; set; }
+ public bool IsSingleFileRelease { get; set; }
}
public static class ManualImportResourceMapper
@@ -52,6 +53,7 @@ namespace Lidarr.Api.V1.ManualImport
Tracks = model.Tracks.ToResource(),
Quality = model.Quality,
ReleaseGroup = model.ReleaseGroup,
+ IsSingleFileRelease = model.IsSingleFileRelease,
// QualityWeight
DownloadId = model.DownloadId,
diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs
index 84a513807..bf19a2b1b 100644
--- a/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs
+++ b/src/Lidarr.Api.V1/ManualImport/ManualImportUpdateResource.cs
@@ -21,6 +21,7 @@ namespace Lidarr.Api.V1.ManualImport
public bool AdditionalFile { get; set; }
public bool ReplaceExistingFiles { get; set; }
public bool DisableReleaseSwitching { get; set; }
+ public bool IsSingleFileRelease { get; set; }
public IEnumerable Rejections { get; set; }
}
diff --git a/src/Lidarr.Api.V1/Tracks/TrackResource.cs b/src/Lidarr.Api.V1/Tracks/TrackResource.cs
index 47f58811d..59834b273 100644
--- a/src/Lidarr.Api.V1/Tracks/TrackResource.cs
+++ b/src/Lidarr.Api.V1/Tracks/TrackResource.cs
@@ -26,6 +26,7 @@ namespace Lidarr.Api.V1.Tracks
public ArtistResource Artist { get; set; }
public Ratings Ratings { get; set; }
+ public bool IsSingleFileRelease { get; set; }
// Hiding this so people don't think its usable (only used to set the initial state)
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
@@ -58,6 +59,7 @@ namespace Lidarr.Api.V1.Tracks
MediumNumber = model.MediumNumber,
HasFile = model.HasFile,
Ratings = model.Ratings,
+ IsSingleFileRelease = model.IsSingleFileRelease
};
}
diff --git a/src/NzbDrone.Core/Datastore/Migration/073_add_flac_cue.cs b/src/NzbDrone.Core/Datastore/Migration/073_add_flac_cue.cs
new file mode 100644
index 000000000..d4ad6b928
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/073_add_flac_cue.cs
@@ -0,0 +1,14 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+ [Migration(073)]
+ public class add_flac_cue : NzbDroneMigrationBase
+ {
+ protected override void MainDbUpgrade()
+ {
+ Alter.Table("TrackFiles").AddColumn("IsSingleFileRelease").AsBoolean().WithDefaultValue(false);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs b/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
index b87fcc619..8833ba94d 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
@@ -27,7 +27,8 @@ namespace NzbDrone.Core.MediaFiles
{ ".ape", Quality.APE },
{ ".aif", Quality.Unknown },
{ ".aiff", Quality.Unknown },
- { ".aifc", Quality.Unknown }
+ { ".aifc", Quality.Unknown },
+ { ".cue", Quality.Unknown }
};
}
diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs
index de5d8a805..07eb8a694 100644
--- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs
+++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs
@@ -21,6 +21,7 @@ namespace NzbDrone.Core.MediaFiles
public QualityModel Quality { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public int AlbumId { get; set; }
+ public bool IsSingleFileRelease { get; set; }
// These are queried from the database
public LazyLoaded> Tracks { get; set; }
diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs
index 28c2c3633..9afb5e927 100644
--- a/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs
+++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateFilenameInfo.cs
@@ -65,14 +65,25 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators
|| tracks.Any(x => x.FileTrackInfo.DiscNumber == 0))
{
_logger.Debug("Missing data in tags, trying filename augmentation");
- foreach (var charSep in CharsAndSeps)
+ if (tracks.Count == 1 && tracks[0].IsSingleFileRelease)
{
- foreach (var pattern in Patterns(charSep.Item1, charSep.Item2))
+ tracks[0].FileTrackInfo.ArtistTitle = tracks[0].Artist.Name;
+ tracks[0].FileTrackInfo.AlbumTitle = tracks[0].Album.Title;
+
+ // TODO this is too bold, the release year is not the one from the .cue file
+ tracks[0].FileTrackInfo.Year = (uint)tracks[0].Album.ReleaseDate.Value.Year;
+ }
+ else
+ {
+ foreach (var charSep in CharsAndSeps)
{
- var matches = AllMatches(tracks, pattern);
- if (matches != null)
+ foreach (var pattern in Patterns(charSep.Item1, charSep.Item2))
{
- ApplyMatches(matches, pattern);
+ var matches = AllMatches(tracks, pattern);
+ if (matches != null)
+ {
+ ApplyMatches(matches, pattern);
+ }
}
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/CandidateService.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/CandidateService.cs
index 6fb62bff0..1d3e0e140 100644
--- a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/CandidateService.cs
+++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/CandidateService.cs
@@ -131,6 +131,13 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
private List GetDbCandidatesByAlbum(LocalAlbumRelease localAlbumRelease, Album album, bool includeExisting)
{
+ if (localAlbumRelease.LocalTracks.Count == 1 && localAlbumRelease.LocalTracks[0].IsSingleFileRelease)
+ {
+ return GetDbCandidatesByRelease(_releaseService.GetReleasesByAlbum(album.Id)
+ .OrderBy(x => x.ReleaseDate)
+ .ToList(), includeExisting);
+ }
+
// sort candidate releases by closest track count so that we stand a chance of
// getting a perfect match early on
return GetDbCandidatesByRelease(_releaseService.GetReleasesByAlbum(album.Id)
diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/DistanceCalculator.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/DistanceCalculator.cs
index 833ccb12f..a2f68fd15 100644
--- a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/DistanceCalculator.cs
+++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/DistanceCalculator.cs
@@ -118,13 +118,16 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
{
var albumYear = release.Album.Value.ReleaseDate?.Year ?? 0;
var releaseYear = release.ReleaseDate?.Year ?? 0;
- if (localYear == albumYear || localYear == releaseYear)
+
+ // The single file version's year is from the album year already, to avoid false positives here we consider it's always different
+ var isSameWithAlbumYear = (localTracks.Count == 1 && localTracks[0].IsSingleFileRelease) ? false : localYear == albumYear;
+ if (isSameWithAlbumYear || localYear == releaseYear)
{
dist.Add("year", 0.0);
}
else
{
- var remoteYear = albumYear > 0 ? albumYear : releaseYear;
+ var remoteYear = (albumYear > 0 && isSameWithAlbumYear) ? albumYear : releaseYear;
var diff = Math.Abs(localYear - remoteYear);
var diff_max = Math.Abs(DateTime.Now.Year - remoteYear);
dist.AddRatio("year", diff, diff_max);
@@ -176,29 +179,36 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
}
// tracks
- foreach (var pair in mapping.Mapping)
+ if (localTracks.Count == 1 && localTracks[0].IsSingleFileRelease)
{
- dist.Add("tracks", pair.Value.Item2.NormalizedDistance());
+ dist.Add("tracks", 0);
}
-
- Logger.Trace("after trackMapping: {0}", dist.NormalizedDistance());
-
- // missing tracks
- foreach (var track in mapping.MBExtra.Take(localTracks.Count))
+ else
{
- dist.Add("missing_tracks", 1.0);
+ foreach (var pair in mapping.Mapping)
+ {
+ dist.Add("tracks", pair.Value.Item2.NormalizedDistance());
+ }
+
+ Logger.Trace("after trackMapping: {0}", dist.NormalizedDistance());
+
+ // missing tracks
+ foreach (var track in mapping.MBExtra.Take(localTracks.Count))
+ {
+ dist.Add("missing_tracks", 1.0);
+ }
+
+ Logger.Trace("after missing tracks: {0}", dist.NormalizedDistance());
+
+ // unmatched tracks
+ foreach (var track in mapping.LocalExtra.Take(localTracks.Count))
+ {
+ dist.Add("unmatched_tracks", 1.0);
+ }
+
+ Logger.Trace("after unmatched tracks: {0}", dist.NormalizedDistance());
}
- Logger.Trace("after missing tracks: {0}", dist.NormalizedDistance());
-
- // unmatched tracks
- foreach (var track in mapping.LocalExtra.Take(localTracks.Count))
- {
- dist.Add("unmatched_tracks", 1.0);
- }
-
- Logger.Trace("after unmatched tracks: {0}", dist.NormalizedDistance());
-
return dist;
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/IdentificationService.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/IdentificationService.cs
index ccd0b3939..32cd6f43a 100644
--- a/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/IdentificationService.cs
+++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Identification/IdentificationService.cs
@@ -154,6 +154,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
private bool ShouldFingerprint(LocalAlbumRelease localAlbumRelease)
{
+ if (localAlbumRelease.LocalTracks.Count == 1 && localAlbumRelease.LocalTracks[0].IsSingleFileRelease)
+ {
+ return false;
+ }
+
var worstTrackMatchDist = localAlbumRelease.TrackMapping?.Mapping
.DefaultIfEmpty()
.MaxBy(x => x.Value.Item2.NormalizedDistance())
@@ -335,6 +340,12 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
localAlbumRelease.AlbumRelease = release;
localAlbumRelease.ExistingTracks = extraTracks;
localAlbumRelease.TrackMapping = mapping;
+ if (localAlbumRelease.LocalTracks.Count == 1 && localAlbumRelease.LocalTracks[0].IsSingleFileRelease)
+ {
+ localAlbumRelease.LocalTracks[0].Tracks = release.Tracks;
+ localAlbumRelease.LocalTracks[0].Tracks.ForEach(x => x.IsSingleFileRelease = true);
+ }
+
if (currDistance == 0.0)
{
break;
@@ -348,6 +359,14 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Identification
public TrackMapping MapReleaseTracks(List localTracks, List