mirror of
https://github.com/Lidarr/Lidarr
synced 2026-01-25 17:02:41 +01:00
Add audio custom format specs and show scores in import UI
Added custom format specs for audio (sample rate, bit depth, bitrate, codec, channels) with validation and UI fields. MediaInfo is now passed to custom format evaluation. ManualImportResource and UI updated to expose and display custom format matches and scores. Improved API validation and upgrade logic to use custom format scores. SizeSpecification validator now allows Max = Min.
This commit is contained in:
parent
12d7d2df15
commit
4dee096c83
15 changed files with 324 additions and 47 deletions
|
|
@ -10,6 +10,12 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.customFormatScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'additionalFile': string;
|
||||
'customFormatScore': string;
|
||||
'customFormatTooltip': string;
|
||||
'label': string;
|
||||
'path': string;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
|||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
|
|
@ -185,6 +186,7 @@ class InteractiveImportRow extends Component {
|
|||
releaseGroup,
|
||||
size,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
indexerFlags,
|
||||
rejections,
|
||||
columns,
|
||||
|
|
@ -323,23 +325,17 @@ class InteractiveImportRow extends Component {
|
|||
{formatBytes(size)}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{
|
||||
customFormats?.length ?
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon name={icons.INTERACTIVE} />
|
||||
}
|
||||
title={translate('Formats')}
|
||||
body={
|
||||
<div className={styles.customFormatTooltip}>
|
||||
<AlbumFormats formats={customFormats} />
|
||||
</div>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
<TableRowCell
|
||||
className={styles.customFormatScore}
|
||||
>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats?.length
|
||||
)}
|
||||
tooltip={<AlbumFormats formats={customFormats} />}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
{isIndexerFlagsColumnVisible ? (
|
||||
|
|
@ -462,6 +458,7 @@ InteractiveImportRow.propTypes = {
|
|||
quality: PropTypes.object,
|
||||
size: PropTypes.number.isRequired,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||
customFormatScore: PropTypes.number.isRequired,
|
||||
indexerFlags: PropTypes.number.isRequired,
|
||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Lidarr.Http;
|
||||
using Lidarr.Http.REST;
|
||||
using Lidarr.Http.REST.Attributes;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace Lidarr.Api.V1.CustomFormats
|
||||
{
|
||||
|
|
@ -39,6 +37,22 @@ public CustomFormatController(ICustomFormatService formatService,
|
|||
{
|
||||
context.AddFailure("Condition name(s) cannot be empty or consist of only spaces");
|
||||
}
|
||||
|
||||
// Validate each specification's internal rules
|
||||
var model = customFormat.ToModel(_specifications);
|
||||
for (var i = 0; i < model.Specifications.Count; i++)
|
||||
{
|
||||
var spec = model.Specifications[i];
|
||||
var specValidationResult = spec.Validate();
|
||||
|
||||
if (!specValidationResult.IsValid)
|
||||
{
|
||||
foreach (var error in specValidationResult.Errors)
|
||||
{
|
||||
context.AddFailure($"Specifications[{i}].{error.PropertyName}", error.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -60,8 +74,6 @@ public ActionResult<CustomFormatResource> Create([FromBody] CustomFormatResource
|
|||
{
|
||||
var model = customFormatResource.ToModel(_specifications);
|
||||
|
||||
Validate(model);
|
||||
|
||||
return Created(_formatService.Insert(model).Id);
|
||||
}
|
||||
|
||||
|
|
@ -71,8 +83,6 @@ public ActionResult<CustomFormatResource> Update([FromBody] CustomFormatResource
|
|||
{
|
||||
var model = resource.ToModel(_specifications);
|
||||
|
||||
Validate(model);
|
||||
|
||||
_formatService.Update(model);
|
||||
|
||||
return Accepted(model.Id);
|
||||
|
|
@ -130,24 +140,6 @@ public object GetTemplates()
|
|||
return schema;
|
||||
}
|
||||
|
||||
private void Validate(CustomFormat definition)
|
||||
{
|
||||
foreach (var validationResult in definition.Specifications.Select(spec => spec.Validate()))
|
||||
{
|
||||
VerifyValidationResult(validationResult);
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyValidationResult(ValidationResult validationResult)
|
||||
{
|
||||
var result = new NzbDroneValidationResult(validationResult.Errors);
|
||||
|
||||
if (!result.IsValid)
|
||||
{
|
||||
throw new ValidationException(result.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ICustomFormatSpecification> GetPresets()
|
||||
{
|
||||
yield return new ReleaseTitleSpecification
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Linq;
|
||||
using Lidarr.Api.V1.Albums;
|
||||
using Lidarr.Api.V1.Artist;
|
||||
using Lidarr.Api.V1.CustomFormats;
|
||||
using Lidarr.Api.V1.Tracks;
|
||||
using Lidarr.Http.REST;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
|
|
@ -24,6 +25,8 @@ public class ManualImportResource : RestResource
|
|||
public string ReleaseGroup { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public List<CustomFormatResource> CustomFormats { get; set; }
|
||||
public int CustomFormatScore { get; set; }
|
||||
public int IndexerFlags { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
public ParsedTrackInfo AudioTags { get; set; }
|
||||
|
|
@ -41,6 +44,8 @@ public static ManualImportResource ToResource(this ManualImportItem model)
|
|||
return null;
|
||||
}
|
||||
|
||||
var customFormatScore = model.Artist?.QualityProfile?.Value?.CalculateCustomFormatScore(model.CustomFormats) ?? 0;
|
||||
|
||||
return new ManualImportResource
|
||||
{
|
||||
Id = model.Id,
|
||||
|
|
@ -56,6 +61,8 @@ public static ManualImportResource ToResource(this ManualImportItem model)
|
|||
|
||||
// QualityWeight
|
||||
DownloadId = model.DownloadId,
|
||||
CustomFormats = model.CustomFormats.ToResource(false),
|
||||
CustomFormatScore = customFormatScore,
|
||||
IndexerFlags = model.IndexerFlags,
|
||||
Rejections = model.Rejections,
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ public List<CustomFormat> ParseCustomFormat(LocalTrack localTrack)
|
|||
Size = localTrack.Size,
|
||||
Filename = Path.GetFileName(localTrack.Path),
|
||||
IndexerFlags = localTrack.IndexerFlags,
|
||||
MediaInfo = localTrack.FileTrackInfo?.MediaInfo
|
||||
};
|
||||
|
||||
return ParseCustomFormat(input);
|
||||
|
|
@ -189,7 +190,8 @@ private List<CustomFormat> ParseCustomFormat(TrackFile trackFile, Artist artist,
|
|||
Artist = artist,
|
||||
Size = trackFile.Size,
|
||||
IndexerFlags = trackFile.IndexerFlags,
|
||||
Filename = Path.GetFileName(trackFile.Path)
|
||||
Filename = Path.GetFileName(trackFile.Path),
|
||||
MediaInfo = trackFile.MediaInfo
|
||||
};
|
||||
|
||||
return ParseCustomFormat(input, allCustomFormats);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public class CustomFormatInput
|
|||
public long Size { get; set; }
|
||||
public IndexerFlags IndexerFlags { get; set; }
|
||||
public string Filename { get; set; }
|
||||
public MediaInfoModel MediaInfo { get; set; }
|
||||
|
||||
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series)
|
||||
// {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class AudioBitDepthSpecificationValidator : AbstractValidator<AudioBitDepthSpecification>
|
||||
{
|
||||
public AudioBitDepthSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThanOrEqualTo(c => c.Min);
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioBitDepthSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly AudioBitDepthSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 10;
|
||||
public override string ImplementationName => "Audio Bit Depth";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Bit Depth", HelpText = "Minimum bit depth (e.g., 16 for CD quality, 24 for Hi-Res)", Type = FieldType.Number)]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Bit Depth", HelpText = "Maximum bit depth", Type = FieldType.Number)]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
{
|
||||
if (input.MediaInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bitDepth = input.MediaInfo.AudioBits;
|
||||
return bitDepth >= Min && bitDepth <= Max;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class AudioBitrateSpecificationValidator : AbstractValidator<AudioBitrateSpecification>
|
||||
{
|
||||
public AudioBitrateSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThanOrEqualTo(c => c.Min);
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioBitrateSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly AudioBitrateSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 11;
|
||||
public override string ImplementationName => "Audio Bitrate";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Bitrate", HelpText = "Minimum bitrate in kbps (e.g., 320 for MP3)", Type = FieldType.Number)]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Bitrate", HelpText = "Maximum bitrate in kbps", Type = FieldType.Number)]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
{
|
||||
if (input.MediaInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bitrate = input.MediaInfo.AudioBitrate;
|
||||
return bitrate >= Min && bitrate <= Max;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class AudioChannelsSpecificationValidator : AbstractValidator<AudioChannelsSpecification>
|
||||
{
|
||||
public AudioChannelsSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThanOrEqualTo(c => c.Min);
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioChannelsSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly AudioChannelsSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 13;
|
||||
public override string ImplementationName => "Audio Channels";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Channels", HelpText = "Minimum number of audio channels (e.g., 2 for stereo, 6 for 5.1)", Type = FieldType.Number)]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Channels", HelpText = "Maximum number of audio channels", Type = FieldType.Number)]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
{
|
||||
if (input.MediaInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var channels = input.MediaInfo.AudioChannels;
|
||||
return channels >= Min && channels <= Max;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class AudioCodecSpecificationValidator : AbstractValidator<AudioCodecSpecification>
|
||||
{
|
||||
public AudioCodecSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty().WithMessage("Audio Codec must not be empty");
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioCodecSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly AudioCodecSpecificationValidator Validator = new ();
|
||||
|
||||
protected Regex _regex;
|
||||
protected string _raw;
|
||||
|
||||
public override int Order => 12;
|
||||
public override string ImplementationName => "Audio Codec";
|
||||
|
||||
[FieldDefinition(1, Label = "Audio Codec", HelpText = "Codec name or regex pattern (e.g., FLAC, MP3, AAC, ALAC, OPUS, WavPack, APE)", Type = FieldType.Textbox)]
|
||||
public string Value
|
||||
{
|
||||
get => _raw;
|
||||
set
|
||||
{
|
||||
_raw = value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
{
|
||||
if (input.MediaInfo == null || string.IsNullOrWhiteSpace(input.MediaInfo.AudioFormat))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_regex == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _regex.IsMatch(input.MediaInfo.AudioFormat);
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class AudioSampleRateSpecificationValidator : AbstractValidator<AudioSampleRateSpecification>
|
||||
{
|
||||
public AudioSampleRateSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThanOrEqualTo(c => c.Min);
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioSampleRateSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly AudioSampleRateSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 9;
|
||||
public override string ImplementationName => "Audio Sample Rate";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Sample Rate", HelpText = "Minimum sample rate in Hz (e.g., 44100 for CD quality, 192000 for Hi-Res)", Type = FieldType.Number)]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Sample Rate", HelpText = "Maximum sample rate in Hz", Type = FieldType.Number)]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
{
|
||||
if (input.MediaInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sampleRate = input.MediaInfo.AudioSampleRate;
|
||||
return sampleRate >= Min && sampleRate <= Max;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ public class SizeSpecificationValidator : AbstractValidator<SizeSpecification>
|
|||
public SizeSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThan(c => c.Min);
|
||||
RuleFor(c => c.Max).GreaterThanOrEqualTo(c => c.Min);
|
||||
RuleFor(c => c.Max).LessThanOrEqualTo(double.MaxValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,6 +304,11 @@ public List<ManualImportItem> UpdateItems(List<ManualImportItem> items)
|
|||
item.Rejections = decision.Rejections;
|
||||
item.Size = decision.Item.Size;
|
||||
|
||||
if (decision.Item.Artist != null)
|
||||
{
|
||||
item.CustomFormats = _formatCalculator.ParseCustomFormat(decision.Item);
|
||||
}
|
||||
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,11 @@ public Decision IsSatisfiedBy(LocalTrack localTrack, DownloadClientItem download
|
|||
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
var qualityComparer = new QualityModelComparer(localTrack.Artist.QualityProfile);
|
||||
var qualityProfile = localTrack.Artist.QualityProfile.Value;
|
||||
|
||||
// Calculate custom formats for the new track
|
||||
var newCustomFormats = _customFormatCalculationService.ParseCustomFormat(localTrack);
|
||||
var newFormatScore = qualityProfile.CalculateCustomFormatScore(newCustomFormats);
|
||||
|
||||
foreach (var track in localTrack.Tracks.Where(e => e.TrackFileId > 0))
|
||||
{
|
||||
|
|
@ -53,11 +58,31 @@ public Decision IsSatisfiedBy(LocalTrack localTrack, DownloadClientItem download
|
|||
return Decision.Reject("Not an upgrade for existing track file(s). New Quality is {0}", localTrack.Quality.Quality);
|
||||
}
|
||||
|
||||
if (qualityCompare == 0 && downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
localTrack.Quality.Revision.CompareTo(trackFile.Quality.Revision) < 0)
|
||||
// When quality is equal, compare custom format scores
|
||||
if (qualityCompare == 0)
|
||||
{
|
||||
_logger.Debug("This file isn't a quality upgrade for all tracks. Skipping {0}", localTrack.Path);
|
||||
return Decision.Reject("Not an upgrade for existing track file(s)");
|
||||
var existingCustomFormats = _customFormatCalculationService.ParseCustomFormat(trackFile, localTrack.Artist);
|
||||
var existingFormatScore = qualityProfile.CalculateCustomFormatScore(existingCustomFormats);
|
||||
|
||||
if (newFormatScore < existingFormatScore)
|
||||
{
|
||||
_logger.Debug("This file isn't a custom format upgrade. Existing: {0} [{1}], New: {2} [{3}]. Skipping {4}",
|
||||
existingFormatScore,
|
||||
string.Join(", ", existingCustomFormats.Select(x => x.Name)),
|
||||
newFormatScore,
|
||||
string.Join(", ", newCustomFormats.Select(x => x.Name)),
|
||||
localTrack.Path);
|
||||
return Decision.Reject("Not a custom format upgrade for existing track file(s). Existing score: {0}, New score: {1}",
|
||||
existingFormatScore,
|
||||
newFormatScore);
|
||||
}
|
||||
|
||||
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
localTrack.Quality.Revision.CompareTo(trackFile.Quality.Revision) < 0)
|
||||
{
|
||||
_logger.Debug("This file isn't a quality upgrade for all tracks. Skipping {0}", localTrack.Path);
|
||||
return Decision.Reject("Not an upgrade for existing track file(s)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue