summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/protocol.rst11
-rw-r--r--meson.build8
-rw-r--r--src/command/AllCommands.cxx4
-rw-r--r--src/command/FingerprintCommands.cxx358
-rw-r--r--src/command/FingerprintCommands.hxx32
-rw-r--r--src/lib/chromaprint/DecoderClient.hxx7
6 files changed, 420 insertions, 0 deletions
diff --git a/doc/protocol.rst b/doc/protocol.rst
index 92b63dcfe..e1a882f9d 100644
--- a/doc/protocol.rst
+++ b/doc/protocol.rst
@@ -824,6 +824,17 @@ The music database
don't this group tag. It exists only if at least one such song is
found.
+:command:`getfingerprint {URI}`
+
+ Calculate the song's audio fingerprint. Example (abbreviated fingerprint)::
+
+ getfingerprint "foo/bar.ogg"
+ chromaprint: AQACcEmSREmWJJmkIT_6CCf64...
+ OK
+
+ This command is only available if MPD was built with
+ :file:`libchromaprint` (``-Dchromaprint=enabled``).
+
.. _command_find:
:command:`find {FILTER} [sort {TYPE}] [window {START:END}]`
diff --git a/meson.build b/meson.build
index 3aecf9310..19ac06ecf 100644
--- a/meson.build
+++ b/meson.build
@@ -356,6 +356,13 @@ if sqlite_dep.found()
]
endif
+if chromaprint_dep.found()
+ sources += [
+ 'src/command/FingerprintCommands.cxx',
+ 'src/lib/chromaprint/DecoderClient.cxx',
+ ]
+endif
+
basic = static_library(
'basic',
'src/ReplayGainInfo.cxx',
@@ -444,6 +451,7 @@ mpd = build_target(
sqlite_dep,
zeroconf_dep,
more_deps,
+ chromaprint_dep,
],
link_args: link_args,
install: not is_android and not is_haiku,
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index dc77538bc..b0b7f57e9 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -33,6 +33,7 @@
#include "NeighborCommands.hxx"
#include "ClientCommands.hxx"
#include "PartitionCommands.hxx"
+#include "FingerprintCommands.hxx"
#include "OtherCommands.hxx"
#include "Permission.hxx"
#include "tag/Type.h"
@@ -107,6 +108,9 @@ static constexpr struct command commands[] = {
{ "find", PERMISSION_READ, 1, -1, handle_find },
{ "findadd", PERMISSION_ADD, 1, -1, handle_findadd},
#endif
+#ifdef ENABLE_CHROMAPRINT
+ { "getfingerprint", PERMISSION_READ, 1, 1, handle_getfingerprint },
+#endif
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
#ifdef ENABLE_DATABASE
diff --git a/src/command/FingerprintCommands.cxx b/src/command/FingerprintCommands.cxx
new file mode 100644
index 000000000..3deacd8ec
--- /dev/null
+++ b/src/command/FingerprintCommands.cxx
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2003-2018 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FingerprintCommands.hxx"
+#include "Request.hxx"
+#include "LocateUri.hxx"
+#include "lib/chromaprint/DecoderClient.hxx"
+#include "decoder/DecoderAPI.hxx"
+#include "decoder/DecoderList.hxx"
+#include "storage/StorageInterface.hxx"
+#include "client/Client.hxx"
+#include "client/Response.hxx"
+#include "client/ThreadBackgroundCommand.hxx"
+#include "input/InputStream.hxx"
+#include "input/LocalOpen.hxx"
+#include "input/Handler.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "system/Error.hxx"
+#include "util/MimeType.hxx"
+#include "util/UriUtil.hxx"
+
+class GetChromaprintCommand final
+ : public ThreadBackgroundCommand, ChromaprintDecoderClient, InputStreamHandler
+{
+ Mutex mutex;
+ Cond cond;
+
+ const std::string uri;
+ const AllocatedPath path;
+
+ bool cancel = false;
+
+public:
+ GetChromaprintCommand(Client &_client, std::string &&_uri,
+ AllocatedPath &&_path) noexcept
+ :ThreadBackgroundCommand(_client),
+ uri(std::move(_uri)), path(std::move(_path))
+ {
+ }
+
+protected:
+ void Run() override;
+
+ void SendResponse(Response &r) noexcept override {
+ r.Format("chromaprint: %s\n",
+ GetFingerprint().c_str());
+ }
+
+ void CancelThread() noexcept override {
+ const std::lock_guard<Mutex> lock(mutex);
+ cancel = true;
+ cond.signal();
+ }
+
+private:
+ void DecodeStream(InputStream &is, const DecoderPlugin &plugin);
+ bool DecodeStream(InputStream &is, const char *suffix,
+ const DecoderPlugin &plugin);
+ void DecodeStream(InputStream &is);
+ bool DecodeContainer(const char *suffix, const DecoderPlugin &plugin);
+ bool DecodeContainer(const char *suffix);
+ bool DecodeFile(const char *suffix, InputStream &is,
+ const DecoderPlugin &plugin);
+ void DecodeFile();
+
+ /* virtual methods from class DecoderClient */
+ InputStreamPtr OpenUri(const char *uri) override;
+ size_t Read(InputStream &is, void *buffer, size_t length) override;
+
+ /* virtual methods from class InputStreamHandler */
+ void OnInputStreamReady() noexcept override {
+ cond.signal();
+ }
+
+ void OnInputStreamAvailable() noexcept override {
+ cond.signal();
+ }
+};
+
+inline void
+GetChromaprintCommand::DecodeStream(InputStream &input_stream,
+ const DecoderPlugin &plugin)
+{
+ assert(plugin.stream_decode != nullptr);
+ assert(input_stream.IsReady());
+
+ if (cancel)
+ throw StopDecoder();
+
+ /* rewind the stream, so each plugin gets a fresh start */
+ try {
+ input_stream.Rewind();
+ } catch (...) {
+ }
+
+ const ScopeUnlock unlock(mutex);
+ plugin.StreamDecode(*this, input_stream);
+}
+
+gcc_pure
+static bool
+decoder_check_plugin_mime(const DecoderPlugin &plugin,
+ const InputStream &is) noexcept
+{
+ assert(plugin.stream_decode != nullptr);
+
+ const char *mime_type = is.GetMimeType();
+ return mime_type != nullptr &&
+ plugin.SupportsMimeType(GetMimeTypeBase(mime_type).c_str());
+}
+
+gcc_pure
+static bool
+decoder_check_plugin_suffix(const DecoderPlugin &plugin,
+ const char *suffix) noexcept
+{
+ assert(plugin.stream_decode != nullptr);
+
+ return suffix != nullptr && plugin.SupportsSuffix(suffix);
+}
+
+gcc_pure
+static bool
+decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is,
+ const char *suffix) noexcept
+{
+ return plugin.stream_decode != nullptr &&
+ (decoder_check_plugin_mime(plugin, is) ||
+ decoder_check_plugin_suffix(plugin, suffix));
+}
+
+inline bool
+GetChromaprintCommand::DecodeStream(InputStream &is,
+ const char *suffix,
+ const DecoderPlugin &plugin)
+{
+ if (!decoder_check_plugin(plugin, is, suffix))
+ return false;
+
+ ChromaprintDecoderClient::Reset();
+
+ DecodeStream(is, plugin);
+ return true;
+}
+
+inline void
+GetChromaprintCommand::DecodeStream(InputStream &is)
+{
+ UriSuffixBuffer suffix_buffer;
+ const char *const suffix = uri_get_suffix(uri.c_str(), suffix_buffer);
+
+ decoder_plugins_try([this, &is, suffix](const DecoderPlugin &plugin){
+ return DecodeStream(is, suffix, plugin);
+ });
+}
+
+inline bool
+GetChromaprintCommand::DecodeContainer(const char *suffix,
+ const DecoderPlugin &plugin)
+{
+ if (plugin.container_scan == nullptr ||
+ plugin.file_decode == nullptr ||
+ !plugin.SupportsSuffix(suffix))
+ return false;
+
+ ChromaprintDecoderClient::Reset();
+
+ plugin.FileDecode(*this, path);
+ return IsReady();
+}
+
+inline bool
+GetChromaprintCommand::DecodeContainer(const char *suffix)
+{
+ return decoder_plugins_try([this, suffix](const DecoderPlugin &plugin){
+ return DecodeContainer(suffix, plugin);
+ });
+}
+
+inline bool
+GetChromaprintCommand::DecodeFile(const char *suffix, InputStream &is,
+ const DecoderPlugin &plugin)
+{
+ if (!plugin.SupportsSuffix(suffix))
+ return false;
+
+ {
+ const std::lock_guard<Mutex> protect(mutex);
+ if (cancel)
+ throw StopDecoder();
+ }
+
+ ChromaprintDecoderClient::Reset();
+
+ if (plugin.file_decode != nullptr) {
+ plugin.FileDecode(*this, path);
+ return IsReady();
+ } else if (plugin.stream_decode != nullptr) {
+ plugin.StreamDecode(*this, is);
+ return IsReady();
+ } else
+ return false;
+}
+
+inline void
+GetChromaprintCommand::DecodeFile()
+{
+ const char *suffix = uri_get_suffix(uri.c_str());
+ if (suffix == nullptr)
+ return;
+
+ InputStreamPtr input_stream;
+
+ try {
+ input_stream = OpenLocalInputStream(path, mutex);
+ } catch (const std::system_error &e) {
+ if (IsPathNotFound(e) &&
+ /* ENOTDIR means this may be a path inside a
+ "container" file */
+ DecodeContainer(suffix))
+ return;
+
+ throw;
+ }
+
+ assert(input_stream);
+
+ auto &is = *input_stream;
+ decoder_plugins_try([this, suffix, &is](const DecoderPlugin &plugin){
+ return DecodeFile(suffix, is, plugin);
+ });
+}
+
+void
+GetChromaprintCommand::Run()
+try {
+ if (!path.IsNull())
+ DecodeFile();
+ else
+ DecodeStream(*OpenUri(uri.c_str()));
+
+ ChromaprintDecoderClient::Finish();
+} catch (StopDecoder) {
+}
+
+InputStreamPtr
+GetChromaprintCommand::OpenUri(const char *uri2)
+{
+ if (cancel)
+ throw StopDecoder();
+
+ auto is = InputStream::Open(uri2, mutex);
+ is->SetHandler(this);
+
+ const std::lock_guard<Mutex> lock(mutex);
+ while (true) {
+ if (cancel)
+ throw StopDecoder();
+
+ is->Update();
+ if (is->IsReady()) {
+ is->Check();
+ return is;
+ }
+
+ cond.wait(mutex);
+ }
+}
+
+size_t
+GetChromaprintCommand::Read(InputStream &is, void *buffer, size_t length)
+{
+ /* overriding ChromaprintDecoderClient's implementation to
+ make it cancellable */
+
+ if (length == 0)
+ return 0;
+
+ std::lock_guard<Mutex> lock(mutex);
+
+ while (true) {
+ if (cancel)
+ return 0;
+
+ if (is.IsAvailable())
+ break;
+
+ cond.wait(mutex);
+ }
+
+ return is.Read(buffer, length);
+}
+
+CommandResult
+handle_getfingerprint(Client &client, Request args, Response &)
+{
+ const char *_uri = args.front();
+
+ auto lu = LocateUri(_uri, &client
+#ifdef ENABLE_DATABASE
+ , nullptr
+#endif
+ );
+
+ std::string uri = lu.canonical_uri;
+
+ switch (lu.type) {
+ case LocatedUri::Type::ABSOLUTE:
+ break;
+
+ case LocatedUri::Type::PATH:
+ break;
+
+ case LocatedUri::Type::RELATIVE:
+#ifdef ENABLE_DATABASE
+ {
+ const auto *storage = client.GetStorage();
+ if (storage == nullptr)
+ throw ProtocolError(ACK_ERROR_NO_EXIST, "No database");
+
+ lu.path = storage->MapFS(lu.canonical_uri);
+ if (lu.path.IsNull()) {
+ uri = storage->MapUTF8(lu.canonical_uri);
+ if (uri_has_scheme(uri.c_str()))
+ throw ProtocolError(ACK_ERROR_NO_EXIST, "No such song");
+ }
+ }
+#else
+ throw ProtocolError(ACK_ERROR_NO_EXIST, "No database");
+#endif
+ }
+
+
+ auto cmd = std::make_unique<GetChromaprintCommand>(client,
+ std::move(uri),
+ std::move(lu.path));
+ cmd->Start();
+ client.SetBackgroundCommand(std::move(cmd));
+ return CommandResult::BACKGROUND;
+}
diff --git a/src/command/FingerprintCommands.hxx b/src/command/FingerprintCommands.hxx
new file mode 100644
index 000000000..5428730a1
--- /dev/null
+++ b/src/command/FingerprintCommands.hxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2003-2019 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FINGERPRINT_COMMANDS_HXX
+#define MPD_FINGERPRINT_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+
+class Client;
+class Request;
+class Response;
+
+CommandResult
+handle_getfingerprint(Client &client, Request request, Response &response);
+
+#endif
diff --git a/src/lib/chromaprint/DecoderClient.hxx b/src/lib/chromaprint/DecoderClient.hxx
index c7dc021f3..b85b9a806 100644
--- a/src/lib/chromaprint/DecoderClient.hxx
+++ b/src/lib/chromaprint/DecoderClient.hxx
@@ -45,6 +45,13 @@ public:
ChromaprintDecoderClient();
~ChromaprintDecoderClient() noexcept;
+ bool IsReady() const noexcept {
+ return ready;
+ }
+
+ void Reset() noexcept {
+ }
+
void Finish();
std::string GetFingerprint() const {