parent
6db135877a
commit
62a3355546
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"files.associations": {
|
|
||||||
"*.yaml": "home-assistant"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
nzbdrone {version} {branch}; urgency=low
|
|
||||||
|
|
||||||
* Automatic Release.
|
|
||||||
|
|
||||||
-- NzbDrone <contact@nzbdrone.com> Mon, 26 Aug 2013 00:00:00 -0700
|
|
@ -1 +0,0 @@
|
|||||||
8
|
|
@ -1,12 +0,0 @@
|
|||||||
Section: web
|
|
||||||
Priority: optional
|
|
||||||
Maintainer: Sonarr <contact@nzbdrone.com>
|
|
||||||
Source: nzbdrone
|
|
||||||
Homepage: https://readarr.com
|
|
||||||
Vcs-Git: git@github.com:readarr/Readarr.git
|
|
||||||
Vcs-Browser: https://github.com/readarr/Readarr
|
|
||||||
|
|
||||||
Package: nzbdrone
|
|
||||||
Architecture: all
|
|
||||||
Depends: libmono-cil-dev (>= 3.2), sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
|
|
||||||
Description: Readarr is a music collection manager
|
|
@ -1,24 +0,0 @@
|
|||||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: nzbdrone
|
|
||||||
Source: https://github.com/readarr/Readarr
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: 2010-2016 Readarr <hello@readarr.com>
|
|
||||||
|
|
||||||
License: GPL-3.0+
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
.
|
|
||||||
This package is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
.
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
.
|
|
||||||
On Debian systems, the complete text of the GNU General
|
|
||||||
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
|
@ -1 +0,0 @@
|
|||||||
nzbdrone_bin/* opt/NzbDrone
|
|
@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/make -f
|
|
||||||
# -*- makefile -*-
|
|
||||||
# Sample debian/rules that uses debhelper.
|
|
||||||
# This file was originally written by Joey Hess and Craig Small.
|
|
||||||
# As a special exception, when this file is copied by dh-make into a
|
|
||||||
# dh-make output file, you may use that output file without restriction.
|
|
||||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
|
||||||
|
|
||||||
# Uncomment this to turn on verbose mode.
|
|
||||||
#export DH_VERBOSE=1
|
|
||||||
|
|
||||||
%:
|
|
||||||
dh $@
|
|
@ -1,4 +1,3 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<Import Project="Targets/PublishAllRids.targets" />
|
<Import Project="Targets/PublishAllRids.targets" />
|
||||||
<Import Project="Targets/CopyRuntimes.targets" />
|
|
||||||
</Project>
|
</Project>
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,46 +0,0 @@
|
|||||||
using System;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Configuration.Events;
|
|
||||||
using NzbDrone.Core.Localization;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.HealthCheck.Checks
|
|
||||||
{
|
|
||||||
[CheckOn(typeof(ConfigSavedEvent))]
|
|
||||||
public class FpcalcCheck : HealthCheckBase
|
|
||||||
{
|
|
||||||
private readonly IFingerprintingService _fingerprintingService;
|
|
||||||
private readonly IConfigService _configService;
|
|
||||||
|
|
||||||
public FpcalcCheck(IFingerprintingService fingerprintingService,
|
|
||||||
IConfigService configService,
|
|
||||||
ILocalizationService localizationService)
|
|
||||||
: base(localizationService)
|
|
||||||
{
|
|
||||||
_fingerprintingService = fingerprintingService;
|
|
||||||
_configService = configService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override HealthCheck Check()
|
|
||||||
{
|
|
||||||
// always pass if fingerprinting is disabled
|
|
||||||
if (_configService.AllowFingerprinting == AllowFingerprinting.Never)
|
|
||||||
{
|
|
||||||
return new HealthCheck(GetType());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_fingerprintingService.IsSetup())
|
|
||||||
{
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, $"fpcalc could not be found. Audio fingerprinting disabled.", "#fpcalc-missing");
|
|
||||||
}
|
|
||||||
|
|
||||||
var fpcalcVersion = _fingerprintingService.FpcalcVersion();
|
|
||||||
if (fpcalcVersion == null || fpcalcVersion < new Version("1.4.3"))
|
|
||||||
{
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, $"You have an old version of fpcalc. Please upgrade to 1.4.3.", "#fpcalc-upgrade");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HealthCheck(GetType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,508 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Cache;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Parser
|
|
||||||
{
|
|
||||||
public interface IFingerprintingService
|
|
||||||
{
|
|
||||||
bool IsSetup();
|
|
||||||
Version FpcalcVersion();
|
|
||||||
void Lookup(List<LocalBook> tracks, double threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AcoustId
|
|
||||||
{
|
|
||||||
public double Duration { get; set; }
|
|
||||||
public string Fingerprint { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FingerprintingService : IFingerprintingService
|
|
||||||
{
|
|
||||||
private const string _acoustIdUrl = "https://api.acoustid.org/v2/lookup";
|
|
||||||
private const string _acoustIdApiKey = "QANd68ji1L";
|
|
||||||
private const int _fingerprintingTimeout = 10000;
|
|
||||||
|
|
||||||
private readonly Logger _logger;
|
|
||||||
private readonly IHttpClient _httpClient;
|
|
||||||
private readonly IHttpRequestBuilderFactory _customerRequestBuilder;
|
|
||||||
private readonly ICached<AcoustId> _cache;
|
|
||||||
|
|
||||||
private readonly string _fpcalcPath;
|
|
||||||
private readonly Version _fpcalcVersion;
|
|
||||||
private readonly string _fpcalcArgs;
|
|
||||||
|
|
||||||
public FingerprintingService(Logger logger,
|
|
||||||
IHttpClient httpClient,
|
|
||||||
ICacheManager cacheManager)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_httpClient = httpClient;
|
|
||||||
_cache = cacheManager.GetCache<AcoustId>(GetType());
|
|
||||||
|
|
||||||
_customerRequestBuilder = new HttpRequestBuilder(_acoustIdUrl).CreateFactory();
|
|
||||||
|
|
||||||
// An exception here will cause Readarr to fail to start, so catch any errors
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_fpcalcPath = GetFpcalcPath();
|
|
||||||
|
|
||||||
if (_fpcalcPath.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
_fpcalcVersion = GetFpcalcVersion();
|
|
||||||
_fpcalcArgs = GetFpcalcArgs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Error(ex, "Somthing went wrong detecting fpcalc");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSetup() => _fpcalcPath.IsNotNullOrWhiteSpace();
|
|
||||||
public Version FpcalcVersion() => _fpcalcVersion;
|
|
||||||
|
|
||||||
private string GetFpcalcPath()
|
|
||||||
{
|
|
||||||
string path = null;
|
|
||||||
|
|
||||||
// Take the fpcalc from the install directory if it exists
|
|
||||||
path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fpcalc");
|
|
||||||
if (OsInfo.IsWindows)
|
|
||||||
{
|
|
||||||
path += ".exe";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise search path for a candidate and check it works
|
|
||||||
if (OsInfo.IsLinux)
|
|
||||||
{
|
|
||||||
// must be on users path on Linux
|
|
||||||
path = "fpcalc";
|
|
||||||
|
|
||||||
// check that the command exists
|
|
||||||
Process p = new Process();
|
|
||||||
p.StartInfo.FileName = path;
|
|
||||||
p.StartInfo.Arguments = "-version";
|
|
||||||
p.StartInfo.UseShellExecute = false;
|
|
||||||
p.StartInfo.CreateNoWindow = true;
|
|
||||||
p.StartInfo.RedirectStandardOutput = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
p.Start();
|
|
||||||
|
|
||||||
// To avoid deadlocks, always read the output stream first and then wait.
|
|
||||||
string output = p.StandardOutput.ReadToEnd();
|
|
||||||
p.WaitForExit(1000);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_logger.Debug("fpcalc not found");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// on OSX / Windows, we have put fpcalc in the application folder
|
|
||||||
_logger.Warn("fpcalc missing from application directory");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Version GetFpcalcVersion()
|
|
||||||
{
|
|
||||||
if (_fpcalcPath == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Process p = new Process();
|
|
||||||
p.StartInfo.FileName = _fpcalcPath;
|
|
||||||
p.StartInfo.Arguments = $"-version";
|
|
||||||
p.StartInfo.UseShellExecute = false;
|
|
||||||
p.StartInfo.CreateNoWindow = true;
|
|
||||||
p.StartInfo.RedirectStandardOutput = true;
|
|
||||||
|
|
||||||
p.Start();
|
|
||||||
|
|
||||||
// To avoid deadlocks, always read the output stream first and then wait.
|
|
||||||
string output = p.StandardOutput.ReadToEnd();
|
|
||||||
p.WaitForExit(1000);
|
|
||||||
|
|
||||||
if (p.ExitCode != 0)
|
|
||||||
{
|
|
||||||
_logger.Warn("Could not get fpcalc version (may be known issue with fpcalc v1.4)");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionstring = Regex.Match(output, @"\d\.\d\.\d").Value;
|
|
||||||
if (versionstring.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var version = new Version(versionstring);
|
|
||||||
_logger.Debug($"fpcalc version: {version}");
|
|
||||||
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetFpcalcArgs()
|
|
||||||
{
|
|
||||||
var args = "";
|
|
||||||
|
|
||||||
if (_fpcalcVersion == null)
|
|
||||||
{
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_fpcalcVersion >= new Version("1.4.0"))
|
|
||||||
{
|
|
||||||
args = "-json";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_fpcalcVersion >= new Version("1.4.3"))
|
|
||||||
{
|
|
||||||
args += " -ignore-errors";
|
|
||||||
}
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AcoustId ParseFpcalcJsonOutput(string output)
|
|
||||||
{
|
|
||||||
return Json.Deserialize<AcoustId>(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AcoustId ParseFpcalcTextOutput(string output)
|
|
||||||
{
|
|
||||||
var durationstring = Regex.Match(output, @"(?<=DURATION=)[\d\.]+(?=\s)").Value;
|
|
||||||
double duration;
|
|
||||||
if (durationstring.IsNullOrWhiteSpace() || !double.TryParse(durationstring, out duration))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fingerprint = Regex.Match(output, @"(?<=FINGERPRINT=)[^\s]+").Value;
|
|
||||||
if (fingerprint.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AcoustId
|
|
||||||
{
|
|
||||||
Duration = duration,
|
|
||||||
Fingerprint = fingerprint
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public AcoustId ParseFpcalcOutput(string output)
|
|
||||||
{
|
|
||||||
if (output.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_fpcalcArgs.Contains("-json"))
|
|
||||||
{
|
|
||||||
return ParseFpcalcJsonOutput(output);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ParseFpcalcTextOutput(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AcoustId GetFingerprint(string file)
|
|
||||||
{
|
|
||||||
return _cache.Get(file, () => GetFingerprintUncached(file), TimeSpan.FromMinutes(30));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AcoustId GetFingerprintUncached(string file)
|
|
||||||
{
|
|
||||||
if (IsSetup() && File.Exists(file))
|
|
||||||
{
|
|
||||||
Process p = new Process();
|
|
||||||
p.StartInfo.FileName = _fpcalcPath;
|
|
||||||
p.StartInfo.Arguments = $"{_fpcalcArgs} \"{file}\"";
|
|
||||||
p.StartInfo.UseShellExecute = false;
|
|
||||||
p.StartInfo.CreateNoWindow = true;
|
|
||||||
p.StartInfo.RedirectStandardOutput = true;
|
|
||||||
p.StartInfo.RedirectStandardError = true;
|
|
||||||
|
|
||||||
_logger.Trace("Executing {0} {1}", p.StartInfo.FileName, p.StartInfo.Arguments);
|
|
||||||
|
|
||||||
StringBuilder output = new StringBuilder();
|
|
||||||
StringBuilder error = new StringBuilder();
|
|
||||||
|
|
||||||
// see https://stackoverflow.com/questions/139593/processstartinfo-hanging-on-waitforexit-why?lq=1
|
|
||||||
// this is most likely overkill...
|
|
||||||
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
|
|
||||||
{
|
|
||||||
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
|
|
||||||
{
|
|
||||||
DataReceivedEventHandler outputHandler = (sender, e) =>
|
|
||||||
{
|
|
||||||
if (e.Data == null)
|
|
||||||
{
|
|
||||||
outputWaitHandle.Set();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
output.AppendLine(e.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
DataReceivedEventHandler errorHandler = (sender, e) =>
|
|
||||||
{
|
|
||||||
if (e.Data == null)
|
|
||||||
{
|
|
||||||
errorWaitHandle.Set();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
error.AppendLine(e.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
p.OutputDataReceived += outputHandler;
|
|
||||||
p.ErrorDataReceived += errorHandler;
|
|
||||||
|
|
||||||
p.Start();
|
|
||||||
|
|
||||||
p.BeginOutputReadLine();
|
|
||||||
p.BeginErrorReadLine();
|
|
||||||
|
|
||||||
if (p.WaitForExit(_fingerprintingTimeout) &&
|
|
||||||
outputWaitHandle.WaitOne(_fingerprintingTimeout) &&
|
|
||||||
errorWaitHandle.WaitOne(_fingerprintingTimeout))
|
|
||||||
{
|
|
||||||
// Process completed.
|
|
||||||
if (p.ExitCode != 0)
|
|
||||||
{
|
|
||||||
_logger.Warn($"fpcalc error: {error}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ParseFpcalcOutput(output.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Timed out. Remove handlers to avoid object disposed error
|
|
||||||
p.OutputDataReceived -= outputHandler;
|
|
||||||
p.ErrorDataReceived -= errorHandler;
|
|
||||||
|
|
||||||
_logger.Warn($"fpcalc timed out. {error}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Lookup(List<LocalBook> tracks, double threshold)
|
|
||||||
{
|
|
||||||
if (!IsSetup())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Lookup(tracks.Select(x => Tuple.Create(x, GetFingerprint(x.Path))).ToList(), threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Lookup(List<Tuple<LocalBook, AcoustId>> files, double threshold)
|
|
||||||
{
|
|
||||||
var toLookup = files.Where(x => x.Item2 != null).ToList();
|
|
||||||
if (!toLookup.Any())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = GenerateRequest(toLookup);
|
|
||||||
var response = GetResponse(request);
|
|
||||||
ParseResponse(response, toLookup, threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpRequest GenerateRequest(List<Tuple<LocalBook, AcoustId>> toLookup)
|
|
||||||
{
|
|
||||||
var httpRequest = _customerRequestBuilder.Create()
|
|
||||||
.WithRateLimit(0.334)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var sb = new StringBuilder($"client={_acoustIdApiKey}&format=json&meta=recordingids&batch=1", 2000);
|
|
||||||
for (int i = 0; i < toLookup.Count; i++)
|
|
||||||
{
|
|
||||||
sb.Append($"&duration.{i}={toLookup[i].Item2.Duration:F0}&fingerprint.{i}={toLookup[i].Item2.Fingerprint}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// they prefer a gzipped body
|
|
||||||
httpRequest.SetContent(Encoding.UTF8.GetBytes(sb.ToString()).Compress());
|
|
||||||
httpRequest.Headers.Add("Content-Encoding", "gzip");
|
|
||||||
httpRequest.Headers.ContentType = "application/x-www-form-urlencoded";
|
|
||||||
httpRequest.SuppressHttpError = true;
|
|
||||||
httpRequest.RequestTimeout = TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
return httpRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LookupResponse GetResponse(HttpRequest request, int retry = 3)
|
|
||||||
{
|
|
||||||
HttpResponse<LookupResponse> httpResponse;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
httpResponse = _httpClient.Post<LookupResponse>(request);
|
|
||||||
}
|
|
||||||
catch (UnexpectedHtmlContentException e)
|
|
||||||
{
|
|
||||||
_logger.Warn(e, "AcoustId API gave invalid response");
|
|
||||||
return retry > 0 ? GetResponse(request, retry - 1) : null;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Warn(e, "AcoustId API lookup failed");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = httpResponse.Resource;
|
|
||||||
|
|
||||||
if (httpResponse.HasHttpError || (response != null && response.Status != "ok"))
|
|
||||||
{
|
|
||||||
if (response?.Error != null)
|
|
||||||
{
|
|
||||||
if (response.Error.Code == AcoustIdErrorCode.TooManyRequests && retry > 0)
|
|
||||||
{
|
|
||||||
_logger.Trace($"Too many requests, retrying in 1s");
|
|
||||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
|
||||||
return GetResponse(request, retry - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Debug($"Webservice error {response.Error.Code}: {response.Error.Message}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Warn("HTTP Error - {0}", httpResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseResponse(LookupResponse response, List<Tuple<LocalBook, AcoustId>> toLookup, double threshold)
|
|
||||||
{
|
|
||||||
if (response == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The API will give errors if fingerprint isn't found or is invalid.
|
|
||||||
// We don't want to stop the entire import because the fingerprinting failed
|
|
||||||
// so just log and return.
|
|
||||||
foreach (var fileResponse in response.Fingerprints)
|
|
||||||
{
|
|
||||||
if (fileResponse.Results.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.Debug("No results for given fingerprint.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var result in fileResponse.Results.Where(x => x.Recordings != null))
|
|
||||||
{
|
|
||||||
_logger.Trace("Found: {0}, {1}, {2}", result.Id, result.Score, string.Join(", ", result.Recordings.Select(x => x.Id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var ids = fileResponse.Results.Where(x => x.Score > threshold && x.Recordings != null).SelectMany(y => y.Recordings.Select(z => z.Id)).Distinct().ToList();
|
|
||||||
_logger.Trace("All recordings: {0}", string.Join("\n", ids));
|
|
||||||
|
|
||||||
toLookup[fileResponse.index].Item1.AcoustIdResults = ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Debug("Fingerprinting complete.");
|
|
||||||
|
|
||||||
var serializerSettings = Json.GetSerializerSettings();
|
|
||||||
serializerSettings.Formatting = Formatting.None;
|
|
||||||
var output = new { Fingerprints = toLookup.Select(x => new { Path = x.Item1.Path, AcoustIdResults = x.Item1.AcoustIdResults }) };
|
|
||||||
_logger.Debug($"*** FingerprintingService TestCaseGenerator ***\n{JsonConvert.SerializeObject(output, serializerSettings)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LookupResponse
|
|
||||||
{
|
|
||||||
public string Status { get; set; }
|
|
||||||
public LookupError Error { get; set; }
|
|
||||||
public List<LookupResultListItem> Fingerprints { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AcoustIdErrorCode
|
|
||||||
{
|
|
||||||
// https://github.com/acoustid/acoustid-server/blob/f671339ad9ab049c4d4361d3eadb6660a8fe4dda/acoustid/api/errors.py#L10
|
|
||||||
UnknownFormat = 1,
|
|
||||||
MissingParameter = 2,
|
|
||||||
InvalidFingerprint = 3,
|
|
||||||
InvalidApikey = 4,
|
|
||||||
Internal = 5,
|
|
||||||
InvalidUserApikey = 6,
|
|
||||||
InvalidUuid = 7,
|
|
||||||
InvalidDuration = 8,
|
|
||||||
InvalidBitrate = 9,
|
|
||||||
InvalidForeignid = 10,
|
|
||||||
InvalidMaxDurationDiff = 11,
|
|
||||||
NotAllowed = 12,
|
|
||||||
ServiceUnavailable = 13,
|
|
||||||
TooManyRequests = 14,
|
|
||||||
InvalidMusicbrainzAccessToken = 15,
|
|
||||||
InsecureRequest = 16,
|
|
||||||
UnknownApplication = 17,
|
|
||||||
FingerprintNotFound = 18
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LookupError
|
|
||||||
{
|
|
||||||
public string Message { get; set; }
|
|
||||||
public AcoustIdErrorCode Code { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LookupResultListItem
|
|
||||||
{
|
|
||||||
public int index { get; set; }
|
|
||||||
public List<LookupResult> Results { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LookupResult
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public double Score { get; set; }
|
|
||||||
public List<RecordingResult> Recordings { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RecordingResult
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
<TemplatesExport />
|
|
@ -1,11 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<ItemGroup>
|
|
||||||
<RuntimeFiles Include="..\Runtimes\$(RuntimeIdentifier)\*" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Target Name="CopyRuntimeFilesOnBuild" AfterTargets="AfterBuild" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net6.0'">
|
|
||||||
<Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(OutDir)" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="CopyRuntimeFilesOnPublish" AfterTargets="Publish" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net6.0'">
|
|
||||||
<Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(PublishDir)" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
Loading…
Reference in new issue