[vlc-devel] [PATCH] Add Chromaprint+AcoustId based audio track fingerprinter

Francois Cartegnie fcvlcdev at free.fr
Fri Nov 16 13:19:41 CET 2012


This is a first working implementation of the Chromaprint+AcoustID
fingerprinting for audio tracks.
The fingerprint is generated in a 2nd vlc instance running a custom sout
configuration running at max speed, ideally only for the first 90 secs.
Comm with the chromaprint/sout instance is currently tricky and a switch
to shm would probably be better.

Fingerprinting can be started from Qt interface's media info dialog.
I believe libchromaprint is provided by Debian releases.

Francois

---
 configure.ac                                  |   18 +
 include/vlc_fingerprinter.h                   |   93 +++
 modules/gui/qt4/Modules.am                    |    8 +
 modules/gui/qt4/adapters/chromaprint.cpp      |   76 +++
 modules/gui/qt4/adapters/chromaprint.hpp      |   51 ++
 modules/gui/qt4/components/info_panels.cpp    |   31 +-
 modules/gui/qt4/components/info_panels.hpp    |    5 +
 modules/gui/qt4/dialogs/fingerprintdialog.cpp |  115 ++++
 modules/gui/qt4/dialogs/fingerprintdialog.hpp |   61 ++
 modules/gui/qt4/ui/fingerprintdialog.ui       |  138 +++++
 modules/misc/Modules.am                       |    7 +
 modules/misc/fingerprinter.c                  |  346 ++++++++++++
 modules/misc/webservices/acoustid.c           |  184 ++++++
 modules/misc/webservices/acoustid.h           |   61 ++
 modules/misc/webservices/json.c               |  741 +++++++++++++++++++++++++
 modules/misc/webservices/json.h               |   29 +
 modules/misc/webservices/use_json.h           |  191 +++++++
 modules/stream_out/Modules.am                 |    2 +
 modules/stream_out/chromaprint.c              |  223 ++++++++
 src/Makefile.am                               |    2 +
 src/libvlccore.sym                            |    2 +
 src/misc/fingerprinter.c                      |   59 ++
 22 files changed, 2440 insertions(+), 3 deletions(-)
 create mode 100644 include/vlc_fingerprinter.h
 create mode 100644 modules/gui/qt4/adapters/chromaprint.cpp
 create mode 100644 modules/gui/qt4/adapters/chromaprint.hpp
 create mode 100644 modules/gui/qt4/dialogs/fingerprintdialog.cpp
 create mode 100644 modules/gui/qt4/dialogs/fingerprintdialog.hpp
 create mode 100644 modules/gui/qt4/ui/fingerprintdialog.ui
 create mode 100644 modules/misc/fingerprinter.c
 create mode 100644 modules/misc/webservices/acoustid.c
 create mode 100644 modules/misc/webservices/acoustid.h
 create mode 100644 modules/misc/webservices/json.c
 create mode 100644 modules/misc/webservices/json.h
 create mode 100644 modules/misc/webservices/use_json.h
 create mode 100644 modules/stream_out/chromaprint.c
 create mode 100644 src/misc/fingerprinter.c

diff --git a/configure.ac b/configure.ac
index d2acd93..04d4211 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3690,6 +3690,24 @@ dnl
 PKG_ENABLE_MODULES_VLC([GOOM], [], [libgoom2], [goom visualization plugin], [auto])
 
 dnl
+dnl  chromaprint audio track fingerprinter
+dnl
+AC_ARG_ENABLE(chromaprint,
+  [  --enable-chromaprint       Chromaprint based audio fingerprinter plugin (default yes)])
+  AS_IF([test "${enable_chromaprint}" != "no"],
+    [
+      PKG_CHECK_MODULES(CHROMAPRINT, [libchromaprint >= 6.0],
+      [
+        VLC_ADD_PLUGIN([stream_out_chromaprint])
+        VLC_ADD_CFLAGS([stream_out_chromaprint],[$CHROMAPRINT_CFLAGS])
+        VLC_ADD_LIBS([stream_out_chromaprint],[$CHROMAPRINT_LIBS])
+        AC_DEFINE([HAVE_CHROMAPRINT], [1], [libchromaprint found])
+      ],[
+        AC_MSG_WARN([${CHROMAPRINT_PKG_ERRORS}.])
+      ])
+    ])
+
+dnl
 dnl libprojectM visualization plugin
 dnl
 AC_ARG_ENABLE(projectm,
diff --git a/include/vlc_fingerprinter.h b/include/vlc_fingerprinter.h
new file mode 100644
index 0000000..5954672
--- /dev/null
+++ b/include/vlc_fingerprinter.h
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * vlc_fingerprinter.h: Fingerprinter abstraction layer
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 VLC_FINGERPRINTER_H
+# define VLC_FINGERPRINTER_H
+
+#include <vlc_common.h>
+#include <vlc_meta.h>
+#include <vlc_input_item.h>
+#include <vlc_arrays.h>
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+typedef struct fingerprinter_sys_t fingerprinter_sys_t;
+
+struct fingerprint_request_t
+{
+    input_item_t *p_item;
+    unsigned int i_duration; /* track length hint in seconds, 0 if unkown */
+    struct
+    {
+        char *psz_fingerprint;
+        vlc_array_t metas_array;
+    } results ;
+};
+typedef struct fingerprint_request_t fingerprint_request_t;
+
+static inline fingerprint_request_t *new_fingerprint_request( input_item_t *p_item )
+{
+    fingerprint_request_t *p_r =
+            ( fingerprint_request_t * ) calloc( 1, sizeof( fingerprint_request_t ) );
+    if ( !p_r ) return NULL;
+    p_r->results.psz_fingerprint = NULL;
+    p_r->i_duration = 0;
+    vlc_gc_incref( p_item );
+    p_r->p_item = p_item;
+    vlc_array_init( & p_r->results.metas_array ); /* shouldn't be needed */
+    return p_r;
+}
+
+static inline void delete_fingerprint_request( fingerprint_request_t *p_f )
+{
+    vlc_gc_decref( p_f->p_item );
+    free( p_f->results.psz_fingerprint );
+    for ( int i=0; i< vlc_array_count( & p_f->results.metas_array ) ; i++ )
+        vlc_meta_Delete( (vlc_meta_t *) vlc_array_item_at_index( & p_f->results.metas_array, i ) );
+}
+
+struct fingerprinter_thread_t
+{
+    VLC_COMMON_MEMBERS
+
+    vlc_thread_t thread;
+
+    /* Specific interfaces */
+    fingerprinter_sys_t * p_sys;
+
+    module_t *   p_module;
+    void ( *pf_run ) ( struct fingerprinter_thread_t * );
+
+    void ( *pf_enqueue ) ( struct fingerprinter_thread_t *f, fingerprint_request_t *r );
+    fingerprint_request_t * ( *pf_getresults ) ( struct fingerprinter_thread_t *f );
+    void ( *pf_apply ) ( fingerprint_request_t *, int i_resultid );
+};
+typedef struct fingerprinter_thread_t fingerprinter_thread_t;
+
+VLC_API fingerprinter_thread_t *fingerprinter_Create( vlc_object_t *p_this );
+VLC_API void fingerprinter_Destroy( fingerprinter_thread_t *p_fingerprint );
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif
diff --git a/modules/gui/qt4/Modules.am b/modules/gui/qt4/Modules.am
index 575ba68..84e9c28 100644
--- a/modules/gui/qt4/Modules.am
+++ b/modules/gui/qt4/Modules.am
@@ -23,6 +23,7 @@ nodist_SOURCES_qt4 = \
 		extensions_manager.moc.cpp \
 		recents.moc.cpp \
 		adapters/seekpoints.moc.cpp \
+		adapters/chromaprint.moc.cpp \
 		variables.moc.cpp \
 		dialogs/playlist.moc.cpp \
 		dialogs/bookmarks.moc.cpp \
@@ -45,6 +46,7 @@ nodist_SOURCES_qt4 = \
 		dialogs/vlm.moc.cpp \
 		dialogs/firstrun.moc.cpp \
 		dialogs/extensions.moc.cpp \
+		dialogs/fingerprintdialog.moc.cpp \
 		components/extended_panels.moc.cpp \
 		components/info_panels.moc.cpp \
 		components/preferences_widgets.moc.cpp \
@@ -96,6 +98,7 @@ nodist_SOURCES_qt4 = \
 		ui/messages_panel.h \
 		ui/about.h \
 		ui/update.h \
+		ui/fingerprintdialog.h \
 		styles/seekstyle.moc.cpp \
 		dialogs/ml_configuration.moc.cpp \
 		components/playlist/ml_model.moc.cpp \
@@ -257,6 +260,7 @@ SOURCES_qt4 = 	qt4.cpp \
 		extensions_manager.cpp \
 		recents.cpp \
 		adapters/seekpoints.cpp \
+		adapters/chromaprint.cpp \
 		variables.cpp \
 		dialogs/playlist.cpp \
 		dialogs/bookmarks.cpp \
@@ -280,6 +284,7 @@ SOURCES_qt4 = 	qt4.cpp \
 		dialogs/firstrun.cpp \
 		dialogs/podcast_configuration.cpp \
 		dialogs/extensions.cpp \
+		dialogs/fingerprintdialog.cpp \
 		components/extended_panels.cpp \
 		components/info_panels.cpp \
 		components/preferences_widgets.cpp \
@@ -332,6 +337,7 @@ noinst_HEADERS = \
 	extensions_manager.hpp \
 	recents.hpp \
 	adapters/seekpoints.hpp \
+	adapters/chromaprint.hpp \
 	variables.hpp \
 	dialogs/playlist.hpp \
 	dialogs/bookmarks.hpp \
@@ -355,6 +361,7 @@ noinst_HEADERS = \
 	dialogs/firstrun.hpp \
 	dialogs/podcast_configuration.hpp \
 	dialogs/extensions.hpp \
+	dialogs/fingerprintdialog.hpp \
 	components/extended_panels.hpp \
 	components/info_panels.hpp \
 	components/preferences_widgets.hpp \
@@ -421,5 +428,6 @@ EXTRA_DIST += \
 	ui/update.ui \
 	ui/sout.ui \
 	ui/vlm.ui \
+	ui/fingerprintdialog.ui \
 	$(DEPS_res)
 
diff --git a/modules/gui/qt4/adapters/chromaprint.cpp b/modules/gui/qt4/adapters/chromaprint.cpp
new file mode 100644
index 0000000..b0b3f79
--- /dev/null
+++ b/modules/gui/qt4/adapters/chromaprint.cpp
@@ -0,0 +1,76 @@
+/*****************************************************************************
+ * chromaprint.cpp: Fingerprinter helper class
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "chromaprint.hpp"
+#include <vlc_fingerprinter.h>
+
+Chromaprint::Chromaprint( intf_thread_t *_p_intf ) : p_intf( _p_intf )
+{
+    Q_ASSERT( p_intf );
+    p_fingerprinter = fingerprinter_Create( VLC_OBJECT( p_intf ) );
+    if ( p_fingerprinter )
+        var_AddCallback( p_fingerprinter, "results-available", results_available, this );
+}
+
+int Chromaprint::results_available( vlc_object_t *, const char *, vlc_value_t, vlc_value_t , void *param )
+{
+    Chromaprint *me = (Chromaprint *) param;
+    me->finish();
+    return 0;
+}
+
+fingerprint_request_t * Chromaprint::fetchResults()
+{
+    return p_fingerprinter->pf_getresults( p_fingerprinter );
+}
+
+void Chromaprint::apply( fingerprint_request_t *p_r, int i_id )
+{
+    p_fingerprinter->pf_apply( p_r, i_id );
+}
+
+bool Chromaprint::enqueue( input_item_t *p_item )
+{
+    if ( ! p_fingerprinter ) return false;
+    fingerprint_request_t *p_r = new_fingerprint_request( p_item );
+    if ( ! p_r ) return false;
+    mtime_t t = input_item_GetDuration( p_item );
+    if ( t ) p_r->i_duration = (unsigned int) ( t / 1000000 );
+    p_fingerprinter->pf_enqueue( p_fingerprinter, p_r );
+    return true;
+}
+
+bool Chromaprint::isSupported( QString uri )
+{
+#ifndef HAVE_CHROMAPRINT
+    return false;
+#endif
+    return ( uri.startsWith( "file://" ) || uri.startsWith( "/" ) );
+}
+
+Chromaprint::~Chromaprint()
+{
+    if ( p_fingerprinter )
+        fingerprinter_Destroy( p_fingerprinter );
+}
diff --git a/modules/gui/qt4/adapters/chromaprint.hpp b/modules/gui/qt4/adapters/chromaprint.hpp
new file mode 100644
index 0000000..77e9ed4
--- /dev/null
+++ b/modules/gui/qt4/adapters/chromaprint.hpp
@@ -0,0 +1,51 @@
+/*****************************************************************************
+ * chromaprint.hpp: Fingerprinter helper class
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 CHROMAPRINT_HPP
+#define CHROMAPRINT_HPP
+
+#include <QObject>
+#include <QString>
+#include <vlc_fingerprinter.h>
+#include <vlc_interface.h>
+
+class Chromaprint : public QObject
+{
+    Q_OBJECT
+
+public:
+    Chromaprint( intf_thread_t *p_intf = NULL );
+    virtual ~Chromaprint();
+    bool enqueue( input_item_t *p_item );
+    static int results_available( vlc_object_t *p_this, const char *,
+                                  vlc_value_t, vlc_value_t newval, void *param );
+    fingerprint_request_t * fetchResults();
+    void apply( fingerprint_request_t *, int i_id );
+    static bool isSupported( QString uri );
+
+signals:
+    void finished();
+
+private:
+    void finish() { emit finished(); }
+    intf_thread_t *p_intf;
+    fingerprinter_thread_t *p_fingerprinter;
+};
+
+#endif // CHROMAPRINT_HPP
diff --git a/modules/gui/qt4/components/info_panels.cpp b/modules/gui/qt4/components/info_panels.cpp
index f139116..2b5a75f 100644
--- a/modules/gui/qt4/components/info_panels.cpp
+++ b/modules/gui/qt4/components/info_panels.cpp
@@ -33,6 +33,8 @@
 #include "qt4.hpp"
 #include "components/info_panels.hpp"
 #include "components/interface_widgets.hpp"
+#include "dialogs/fingerprintdialog.hpp"
+#include "adapters/chromaprint.hpp"
 
 #include <assert.h>
 #include <vlc_url.h>
@@ -132,7 +134,15 @@ MetaPanel::MetaPanel( QWidget *parent,
 
     /* Language on the same line */
     ADD_META( VLC_META_LANGUAGE, language_text, 7, -1 ); line++;
-    ADD_META( VLC_META_PUBLISHER, publisher_text, 0, 7 ); line++;
+    ADD_META( VLC_META_PUBLISHER, publisher_text, 0, 7 );
+
+    fingerprintButton = new QPushButton( qtr("&Fingerprint") );
+    fingerprintButton->setToolTip( qtr( "Find meta data using audio fingerprinting" ) );
+    fingerprintButton->setVisible( false );
+    metaLayout->addWidget( fingerprintButton, line, 7 , 3, -1 );
+    CONNECT( fingerprintButton, clicked(), this, fingerprint() );
+
+    line++;
 
     lblURL = new QLabel;
     lblURL->setOpenExternalLinks( true );
@@ -216,7 +226,8 @@ void MetaPanel::update( input_item_t *p_item )
     /* URL / URI */
     psz_meta = input_item_GetURI( p_item );
     if( !EMPTY_STR( psz_meta ) )
-         emit uriSet( qfu( psz_meta ) );
+        emit uriSet( qfu( psz_meta ) );
+    fingerprintButton->setVisible( Chromaprint::isSupported( QString( psz_meta ) ) );
     free( psz_meta );
 
     /* Other classic though */
@@ -332,11 +343,25 @@ void MetaPanel::clear()
     nowplaying_text->clear();
     publisher_text->clear();
     encodedby_text->clear();
-
+    fingerprintButton->setVisible( false );
     setEditMode( false );
     emit uriSet( "" );
 }
 
+void MetaPanel::fingerprint()
+{
+    FingerprintDialog *dialog = new FingerprintDialog( this, p_intf, p_input );
+    CONNECT( dialog, metaApplied( input_item_t * ), this, fingerprintUpdate( input_item_t * ) );
+    dialog->setAttribute( Qt::WA_DeleteOnClose, true );
+    dialog->show();
+}
+
+void MetaPanel::fingerprintUpdate( input_item_t *p_item )
+{
+    update( p_item );
+    setEditMode( true );
+}
+
 /**
  * Second Panel - Shows the extra metadata in a tree, non editable.
  **/
diff --git a/modules/gui/qt4/components/info_panels.hpp b/modules/gui/qt4/components/info_panels.hpp
index 390fd61..dc064ef 100644
--- a/modules/gui/qt4/components/info_panels.hpp
+++ b/modules/gui/qt4/components/info_panels.hpp
@@ -51,6 +51,7 @@ class QLineEdit;
 class CoverArtLabel;
 class QTextEdit;
 class QLabel;
+class QPushButton;
 
 class MetaPanel: public QWidget
 {
@@ -88,9 +89,13 @@ private:
     QLabel   *lblURL;
     QString  currentURL;
 
+    QPushButton *fingerprintButton;
+
 public slots:
     void update( input_item_t * );
     void clear();
+    void fingerprint();
+    void fingerprintUpdate( input_item_t * );
 
 private slots:
     void enterEditMode();
diff --git a/modules/gui/qt4/dialogs/fingerprintdialog.cpp b/modules/gui/qt4/dialogs/fingerprintdialog.cpp
new file mode 100644
index 0000000..bfbe3ae
--- /dev/null
+++ b/modules/gui/qt4/dialogs/fingerprintdialog.cpp
@@ -0,0 +1,115 @@
+/*****************************************************************************
+ * fingerprintdialog.cpp: Fingerprinter Dialog
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 "fingerprintdialog.hpp"
+#include "ui/fingerprintdialog.h"
+
+#include "adapters/chromaprint.hpp"
+#include <vlc_url.h>
+
+#include <QLabel>
+#include <QListWidgetItem>
+
+FingerprintDialog::FingerprintDialog(QWidget *parent, intf_thread_t *p_intf,
+                                     input_item_t *p_item ) :
+    QDialog(parent),
+    ui(new Ui::FingerprintDialog), p_r( NULL )
+{
+    ui->setupUi(this);
+
+    ui->stackedWidget->setCurrentWidget( ui->wait );
+
+    ui->buttonBox->addButton( "&Close",
+                              QDialogButtonBox::RejectRole );
+
+    ui->buttonsBox->addButton( "&Apply this identity to the file",
+                                QDialogButtonBox::AcceptRole );
+
+    ui->buttonsBox->addButton( "&Discard all identities",
+                                QDialogButtonBox::RejectRole );
+
+    CONNECT( ui->buttonsBox, accepted(), this, applyIdentity() );
+    CONNECT( ui->buttonBox, rejected(), this, close() );
+    CONNECT( ui->buttonsBox, rejected(), this, close() );
+
+    t = new Chromaprint( p_intf );
+    if ( t )
+    {
+        CONNECT( t, finished(), this, handleResults() );
+        t->enqueue( p_item );
+    }
+}
+
+void FingerprintDialog::applyIdentity()
+{
+    Q_ASSERT( p_r );
+    if ( ui->recordsList->currentIndex().isValid() )
+        t->apply( p_r, ui->recordsList->currentIndex().row() );
+    emit metaApplied( p_r->p_item );
+    close();
+}
+
+void FingerprintDialog::handleResults()
+{
+    p_r = t->fetchResults();
+
+    if ( ! p_r )
+    {
+        ui->stackedWidget->setCurrentWidget( ui->error );
+        return;
+    }
+
+    if ( vlc_array_count( & p_r->results.metas_array ) == 0 )
+    {
+        delete_fingerprint_request( p_r );
+        ui->stackedWidget->setCurrentWidget( ui->error );
+        return;
+    }
+
+    ui->stackedWidget->setCurrentWidget( ui->results );
+
+    for ( int i=0; i< vlc_array_count( & p_r->results.metas_array ) ; i++ )
+    {
+        vlc_meta_t *p_meta =
+                (vlc_meta_t *) vlc_array_item_at_index( & p_r->results.metas_array, i );
+        QListWidgetItem *item = new QListWidgetItem();
+        ui->recordsList->addItem( item );
+        QString mb_id( vlc_meta_GetExtra( p_meta, "musicbrainz-id" ) );
+        QLabel *label = new QLabel(
+                    QString( "<h3 style=\"margin: 0\"><a style=\"text-decoration:none\" href=\"%1\">%2</a></h3>"
+                             "<span style=\"padding-left:20px\">%3</span>" )
+                    .arg( QString( "http://mb.videolan.org/recording/%1" ).arg( mb_id ) )
+                    .arg( qfu( vlc_meta_Get( p_meta, vlc_meta_Title ) ) )
+                    .arg( qfu( vlc_meta_Get( p_meta, vlc_meta_Artist ) ) )
+        );
+        label->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
+        label->setOpenExternalLinks( true );
+        item->setSizeHint( label->sizeHint() );
+        ui->recordsList->setItemWidget( item, label );
+    }
+    ui->recordsList->setCurrentIndex( ui->recordsList->model()->index( 0, 0 ) );
+}
+
+FingerprintDialog::~FingerprintDialog()
+{
+    if ( t ) delete t;
+    if ( p_r ) delete_fingerprint_request( p_r );
+    delete ui;
+}
diff --git a/modules/gui/qt4/dialogs/fingerprintdialog.hpp b/modules/gui/qt4/dialogs/fingerprintdialog.hpp
new file mode 100644
index 0000000..99ca546
--- /dev/null
+++ b/modules/gui/qt4/dialogs/fingerprintdialog.hpp
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * fingerprintdialog.hpp: Fingerprinter Dialog
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 FINGERPRINTDIALOG_HPP
+#define FINGERPRINTDIALOG_HPP
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "qt4.hpp"
+
+#include <QDialog>
+#include <vlc_interface.h>
+#include <vlc_fingerprinter.h>
+
+namespace Ui {
+class FingerprintDialog;
+}
+
+class Chromaprint;
+
+class FingerprintDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    explicit FingerprintDialog( QWidget *parent, intf_thread_t *p_intf,
+                                input_item_t *p_item );
+    ~FingerprintDialog();
+
+private:
+    Ui::FingerprintDialog *ui;
+    Chromaprint *t;
+    fingerprint_request_t *p_r;
+
+private slots:
+    void handleResults();
+    void applyIdentity();
+
+signals:
+    void metaApplied( input_item_t * );
+};
+
+#endif // FINGERPRINTDIALOG_HPP
diff --git a/modules/gui/qt4/ui/fingerprintdialog.ui b/modules/gui/qt4/ui/fingerprintdialog.ui
new file mode 100644
index 0000000..acc8269
--- /dev/null
+++ b/modules/gui/qt4/ui/fingerprintdialog.ui
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FingerprintDialog</class>
+ <widget class="QDialog" name="FingerprintDialog">
+  <property name="windowModality">
+   <enum>Qt::WindowModal</enum>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>499</width>
+    <height>257</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Audio Fingerprinting</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <item>
+    <widget class="QStackedWidget" name="stackedWidget">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="results">
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <widget class="QLabel" name="label_3">
+         <property name="text">
+          <string>Select a matching identity</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QListWidget" name="recordsList"/>
+       </item>
+       <item>
+        <widget class="QDialogButtonBox" name="buttonsBox">
+         <property name="standardButtons">
+          <set>QDialogButtonBox::NoButton</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="error">
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <item>
+        <widget class="QLabel" name="label_2">
+         <property name="text">
+          <string>No fingerprint has been found</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QDialogButtonBox" name="buttonBox">
+         <property name="standardButtons">
+          <set>QDialogButtonBox::NoButton</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="wait">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QProgressBar" name="progressBar">
+         <property name="minimum">
+          <number>0</number>
+         </property>
+         <property name="maximum">
+          <number>0</number>
+         </property>
+         <property name="value">
+          <number>-1</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Fingerprinting track...</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/modules/misc/Modules.am b/modules/misc/Modules.am
index 4512e1d..469a7e7 100644
--- a/modules/misc/Modules.am
+++ b/modules/misc/Modules.am
@@ -1,5 +1,11 @@
 SOURCES_vod_rtsp = rtsp.c
 SOURCES_audioscrobbler = audioscrobbler.c
+SOURCES_fingerprinter = fingerprinter.c \
+	fingerprinter.h \
+	webservices/acoustid.c \
+	webservices/json_fixup.h \
+	webservices/json.c \
+	webservices/json.h
 SOURCES_sqlite = sqlite.c
 SOURCES_xml = xml/libxml.c
 
@@ -70,6 +76,7 @@ libstats_plugin_la_LIBADD = $(AM_LIBADD)
 
 libvlc_LTLIBRARIES += \
 	libaudioscrobbler_plugin.la \
+	libfingerprinter_plugin.la \
 	liblogger_plugin.la \
 	libstats_plugin.la
 
diff --git a/modules/misc/fingerprinter.c b/modules/misc/fingerprinter.c
new file mode 100644
index 0000000..e67636e
--- /dev/null
+++ b/modules/misc/fingerprinter.c
@@ -0,0 +1,346 @@
+/*****************************************************************************
+ * fingerprinter.c: Audio fingerprinter module
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_stream.h>
+#include <vlc_modules.h>
+#include <vlc_meta.h>
+#include <vlc_url.h>
+
+#include <vlc/vlc.h>
+#include <vlc_fingerprinter.h>
+#include "webservices/acoustid.h"
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+struct fingerprinter_sys_t
+{
+    vlc_array_t             *incoming_requests;
+    vlc_mutex_t             incoming_queue_lock;
+    vlc_cond_t              incoming_queue_filled;
+
+    vlc_array_t             *queue;
+    vlc_mutex_t             queue_lock;
+
+    vlc_array_t             *results;
+    vlc_mutex_t             results_lock;
+
+    int                     i_cancel_state;
+};
+
+static int  Open            (vlc_object_t *);
+static void Close           (vlc_object_t *);
+static void Run             (fingerprinter_thread_t *);
+
+/*****************************************************************************
+ * Module descriptor
+ ****************************************************************************/
+vlc_module_begin ()
+    set_category(CAT_ADVANCED)
+    set_shortname(N_("acoustid"))
+    set_description(N_("Track fingerprinter (based on Acoustid)"))
+    set_capability("fingerprinter", 1)
+    set_callbacks(Open, Close)
+vlc_module_end ()
+
+/*****************************************************************************
+ * Requests lifecycle
+ *****************************************************************************/
+
+static void EnqueueRequest( fingerprinter_thread_t *f, fingerprint_request_t *r )
+{
+    fingerprinter_sys_t *p_sys = f->p_sys;
+    vlc_mutex_lock( &p_sys->incoming_queue_lock );
+    vlc_array_append( p_sys->incoming_requests, r );
+    vlc_mutex_unlock( &p_sys->incoming_queue_lock );
+    vlc_cond_signal( &p_sys->incoming_queue_filled );
+}
+
+static void QueueIncomingRequests( fingerprinter_sys_t *p_sys )
+{
+    vlc_mutex_lock( &p_sys->incoming_queue_lock );
+    int i = vlc_array_count( p_sys->incoming_requests );
+    if ( i == 0 ) goto end;
+    vlc_mutex_lock( &p_sys->queue_lock );
+    while( i )
+        vlc_array_append( p_sys->queue,
+                          vlc_array_item_at_index( p_sys->incoming_requests, --i ) );
+    vlc_array_clear( p_sys->incoming_requests );
+    vlc_mutex_unlock( &p_sys->queue_lock );
+end:
+    vlc_mutex_unlock(&p_sys->incoming_queue_lock);
+}
+
+static fingerprint_request_t * GetResult( fingerprinter_thread_t *f )
+{
+    fingerprint_request_t *r = NULL;
+    fingerprinter_sys_t *p_sys = f->p_sys;
+    vlc_mutex_lock( &p_sys->results_lock );
+    if ( vlc_array_count( p_sys->results ) )
+    {
+        r = vlc_array_item_at_index( p_sys->results, 0 );
+        vlc_array_remove( p_sys->results, 0 );
+    }
+    vlc_mutex_unlock( &p_sys->results_lock );
+    return r;
+}
+
+static void ApplyResult( fingerprint_request_t *p_r, int i_resultid )
+{
+    if ( i_resultid >= vlc_array_count( & p_r->results.metas_array ) ) return;
+
+    vlc_meta_t *p_meta = (vlc_meta_t *)
+            vlc_array_item_at_index( & p_r->results.metas_array, i_resultid );
+    input_item_t *p_item = p_r->p_item;
+    vlc_mutex_lock( &p_item->lock );
+    vlc_meta_Merge( p_item->p_meta, p_meta );
+    vlc_mutex_unlock( &p_item->lock );
+}
+
+static void logger( void *data, int level, const char *fmt, va_list ap )
+{
+    VLC_UNUSED( level );
+    acoustid_fingerprint_t *fingerprint = (acoustid_fingerprint_t *) data;
+    char s[4096], s2[4096];
+    vsprintf( s, fmt, ap );
+    if ( strstr( s, "stream_out_chromaprint stream out" ) != NULL )
+    {
+        char *psz_tuple = strstr( s, "DURATION=" );
+        if ( psz_tuple != NULL )
+        {
+            int i_duration;
+            if ( sscanf( psz_tuple, "DURATION=%d;FINGERPRINT=%s", &i_duration, (char *)&s2 ) == 2 )
+            {
+                if ( ! fingerprint->i_duration ) /* had no hint */
+                    fingerprint->i_duration = i_duration;
+                fingerprint->psz_fingerprint = strdup( s2 );
+            }
+        }
+    }
+}
+
+static void DoFingerprint( char *psz_uri, acoustid_fingerprint_t *fp )
+{
+    libvlc_instance_t *p_instance;
+    libvlc_media_player_t *p_media_player;
+    libvlc_log_subscriber_t log;
+    const char * const durationarg = ( fp->i_duration )? "--stop-time=91" : "";
+    const char * const vlc_args[] = {
+        "--intf", "dummy",
+        "--vout", "dummy",
+        "--aout", "dummy",
+        "--ignore-config",
+        durationarg,
+        "-vvv",
+        "--sout", "#transcode{acodec=s16l}:chromaprint"
+    };
+
+    struct { vlc_mutex_t lock; vlc_cond_t cond; } wait;
+    vlc_mutex_init( &wait.lock );
+    vlc_cond_init( &wait.cond );
+
+    p_instance = libvlc_new( sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args );
+    if ( !p_instance ) goto end;
+    libvlc_log_subscribe( &log, logger, fp );
+    p_media_player = libvlc_media_player_new( p_instance );
+    if ( p_media_player )
+    {
+        libvlc_media_t *p_media = libvlc_media_new_path( p_instance, psz_uri );
+        if ( p_media )
+        {
+            libvlc_media_player_set_media ( p_media_player, p_media );
+            libvlc_media_player_play( p_media_player );
+            msleep( 1000000 ); /* have to find a better way */
+        }
+        while( libvlc_media_player_is_playing( p_media_player ) )
+        {
+            mtime_t timeout = mdate() + 250000;
+            vlc_mutex_lock( &wait.lock );
+            vlc_cond_timedwait( &wait.cond, &wait.lock, timeout );
+            vlc_mutex_unlock( &wait.lock );
+        }
+        libvlc_media_player_stop( p_media_player );
+        libvlc_media_player_release( p_media_player );
+    }
+    libvlc_log_unsubscribe( &log );
+    libvlc_release( p_instance );
+
+end:
+    vlc_mutex_destroy( &wait.lock );
+    vlc_cond_destroy( &wait.cond );
+}
+
+/*****************************************************************************
+ * Open: initialize and create stuff
+ *****************************************************************************/
+static int Open(vlc_object_t *p_this)
+{
+    fingerprinter_thread_t *p_fingerprinter = (fingerprinter_thread_t*) p_this;
+    fingerprinter_sys_t *p_sys = calloc(1, sizeof(fingerprinter_sys_t));
+
+    if ( !p_sys )
+        return VLC_ENOMEM;
+
+    p_fingerprinter->p_sys = p_sys;
+
+    p_sys->incoming_requests = vlc_array_new();
+    vlc_mutex_init( &p_sys->incoming_queue_lock );
+    vlc_cond_init( &p_sys->incoming_queue_filled );
+
+    p_sys->queue = vlc_array_new();
+    vlc_mutex_init( &p_sys->queue_lock );
+
+    p_sys->results = vlc_array_new();
+    vlc_mutex_init( &p_sys->results_lock );
+
+    p_fingerprinter->pf_run = Run;
+    p_fingerprinter->pf_enqueue = EnqueueRequest;
+    p_fingerprinter->pf_getresults = GetResult;
+    p_fingerprinter->pf_apply = ApplyResult;
+
+    var_Create( p_fingerprinter, "results-available", VLC_VAR_BOOL );
+    if( p_fingerprinter->pf_run
+     && vlc_clone( &p_fingerprinter->thread,
+                   (void *(*) (void *)) p_fingerprinter->pf_run,
+                   p_fingerprinter, VLC_THREAD_PRIORITY_LOW ) )
+    {
+        msg_Err( p_fingerprinter, "cannot spawn fingerprinter thread" );
+        goto error;
+    }
+
+    return VLC_SUCCESS;
+
+error:
+    free( p_sys );
+    return VLC_EGENERIC;
+}
+
+/*****************************************************************************
+ * Close: destroy interface stuff
+ *****************************************************************************/
+static void Close(vlc_object_t *p_this)
+{
+    fingerprinter_thread_t   *p_fingerprinter = (fingerprinter_thread_t*) p_this;
+    fingerprinter_sys_t *p_sys = p_fingerprinter->p_sys;
+
+    vlc_cancel( p_fingerprinter->thread );
+    vlc_join( p_fingerprinter->thread, NULL );
+
+    for ( int i = 0; i < vlc_array_count( p_sys->incoming_requests ); i++ )
+        delete_fingerprint_request( vlc_array_item_at_index( p_sys->incoming_requests, i ) );
+    vlc_array_destroy( p_sys->incoming_requests );
+    vlc_mutex_destroy( &p_sys->incoming_queue_lock );
+    vlc_cond_destroy( &p_sys->incoming_queue_filled );
+
+    for ( int i = 0; i < vlc_array_count( p_sys->queue ); i++ )
+        delete_fingerprint_request( vlc_array_item_at_index( p_sys->queue, i ) );
+    vlc_array_destroy( p_sys->queue );
+    vlc_mutex_destroy( &p_sys->queue_lock );
+
+    for ( int i = 0; i < vlc_array_count( p_sys->results ); i++ )
+        delete_fingerprint_request( vlc_array_item_at_index( p_sys->results, i ) );
+    vlc_array_destroy( p_sys->results );
+    vlc_mutex_destroy( &p_sys->results_lock );
+
+    free( p_sys );
+}
+
+static void fill_metas_with_results( fingerprint_request_t *p_r, acoustid_fingerprint_t *p_f )
+{
+    for( unsigned int i=0 ; i < p_f->results.count; i++ )
+    {
+        acoustid_result_t *p_result = & p_f->results.p_results[ i ];
+        for ( unsigned int j=0 ; j < p_result->recordings.count; j++ )
+        {
+            musicbrainz_recording_t *p_record = & p_result->recordings.p_recordings[ j ];
+            vlc_meta_t *p_meta = vlc_meta_New();
+            if ( p_meta )
+            {
+                vlc_meta_Set( p_meta, vlc_meta_Title, p_record->psz_title );
+                vlc_meta_Set( p_meta, vlc_meta_Artist, p_record->psz_artist );
+                vlc_meta_AddExtra( p_meta, "musicbrainz-id", p_record->sz_musicbrainz_id );
+                vlc_array_append( & p_r->results.metas_array, p_meta );
+            }
+        }
+    }
+}
+
+/*****************************************************************************
+ * Run : call Handshake() then submit songs
+ *****************************************************************************/
+static void Run( fingerprinter_thread_t *p_fingerprinter )
+{
+    fingerprinter_sys_t *p_sys = p_fingerprinter->p_sys;
+    p_sys->i_cancel_state = vlc_savecancel();
+
+    /* main loop */
+    for (;;)
+    {
+        vlc_mutex_lock( &p_sys->queue_lock );
+        mutex_cleanup_push( &p_sys->queue_lock );
+        vlc_restorecancel( p_sys->i_cancel_state );
+
+        vlc_cond_timedwait( &p_sys->incoming_queue_filled, &p_sys->queue_lock, mdate() + 1000000 );
+        vlc_cleanup_run();
+
+        p_sys->i_cancel_state = vlc_savecancel();
+
+        QueueIncomingRequests( p_sys );
+
+        vlc_mutex_lock( &p_sys->queue_lock );
+        for (int i = 0 ; i < vlc_array_count( p_sys->queue ); i++)
+        {
+            fingerprint_request_t *p_data = vlc_array_item_at_index( p_sys->queue, i );
+            acoustid_fingerprint_t acoustid_print;
+            char *psz_path = NULL;
+            char *psz_uri = input_item_GetURI( p_data->p_item );
+            if ( psz_uri )
+            {
+                psz_path = make_path( psz_uri );
+                free( psz_uri );
+            }
+            if ( psz_path )
+            {
+                /* overwrite with hint, as in this case, fingerprint's session will be truncated */
+                if ( p_data->i_duration ) acoustid_print.i_duration = p_data->i_duration;
+                DoFingerprint( psz_path, &acoustid_print );
+                free( psz_path );
+                DoAcoustIdWebRequest( VLC_OBJECT(p_fingerprinter), &acoustid_print );
+                fill_metas_with_results( p_data, &acoustid_print );
+            }
+            vlc_mutex_lock( &p_sys->results_lock );
+            vlc_array_append( p_sys->results, p_data );
+            vlc_mutex_unlock( &p_sys->results_lock );
+        }
+        if ( vlc_array_count( p_sys->queue ) )
+        {
+            var_TriggerCallback( p_fingerprinter, "results-available" );
+            vlc_array_clear( p_sys->queue );
+        }
+        vlc_mutex_unlock( &p_sys->queue_lock );
+    }
+    vlc_restorecancel( p_sys->i_cancel_state );
+}
diff --git a/modules/misc/webservices/acoustid.c b/modules/misc/webservices/acoustid.c
new file mode 100644
index 0000000..d02d362
--- /dev/null
+++ b/modules/misc/webservices/acoustid.c
@@ -0,0 +1,184 @@
+/*****************************************************************************
+ * acoustid.c: AcoustId webservice parser
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_stream.h>
+
+#include <vlc/vlc.h>
+#include "acoustid.h"
+#include "use_json.h"
+
+/*****************************************************************************
+ * Requests lifecycle
+ *****************************************************************************/
+void free_acoustid_result_t( acoustid_result_t * r )
+{
+    free( r->psz_id );
+    for ( unsigned int i=0; i<r->recordings.count; i++ )
+    {
+        free( r->recordings.p_recordings[ i ].psz_artist );
+        free( r->recordings.p_recordings[ i ].psz_title );
+    }
+    free( r->recordings.p_recordings );
+}
+
+static json_value * jsongetbyname( json_value *object, const char *psz_name )
+{
+    if ( object->type != json_object ) return NULL;
+    for ( unsigned int i=0; i < object->u.object.length; i++ )
+        if ( strcmp( object->u.object.values[i].name, psz_name ) == 0 )
+            return object->u.object.values[i].value;
+    return NULL;
+}
+
+static void parse_artists( json_value *node, musicbrainz_recording_t *record )
+{
+    /* take only main */
+    if ( !node || node->type != json_array || node->u.array.length < 1 ) return;
+    json_value *artistnode = node->u.array.values[ 0 ];
+    json_value *value = jsongetbyname( artistnode, "name" );
+    if ( value && value->type == json_string )
+        record->psz_artist = strdup( value->u.string.ptr );
+}
+
+static void parse_recordings( vlc_object_t *p_obj, json_value *node, acoustid_result_t *p_result )
+{
+    if ( !node || node->type != json_array ) return;
+    p_result->recordings.p_recordings = calloc( node->u.array.length, sizeof(musicbrainz_recording_t) );
+    if ( ! p_result->recordings.p_recordings ) return;
+    p_result->recordings.count = node->u.array.length;
+
+    for( unsigned int i=0; i<node->u.array.length; i++ )
+    {
+        msg_Err( p_obj, "recording %d", i );
+        musicbrainz_recording_t *record = & p_result->recordings.p_recordings[ i ];
+        json_value *recordnode = node->u.array.values[ i ];
+        if ( !recordnode || recordnode->type != json_object ) break;
+        json_value *value = jsongetbyname( recordnode, "title" );
+        if ( value && value->type == json_string )
+            record->psz_title = strdup( value->u.string.ptr );
+        value = jsongetbyname( recordnode, "id" );
+        if ( value && value->type == json_string )
+            strncpy( record->sz_musicbrainz_id, value->u.string.ptr, MB_ID_SIZE );
+        parse_artists( jsongetbyname( recordnode, "artists" ), record );
+        msg_Err( p_obj, "recording %d title %s %36s %s", i, record->psz_title, record->sz_musicbrainz_id, record->psz_artist );
+    }
+}
+
+static bool ParseJson( vlc_object_t *p_obj, char *psz_buffer, acoustid_results_t *p_results )
+{
+    json_settings settings;
+    char psz_error[128];
+    memset (&settings, 0, sizeof (json_settings));
+    json_value *root = json_parse_ex( &settings, psz_buffer, psz_error );
+    if ( root == NULL )
+    {
+        msg_Warn( p_obj, "Can't parse json data: %s", psz_error );
+        goto error;
+    }
+    if ( root->type != json_object )
+    {
+        msg_Warn( p_obj, "wrong json root node" );
+        goto error;
+    }
+    json_value *node = jsongetbyname( root, "status" );
+    if ( !node || node->type != json_string )
+    {
+        msg_Warn( p_obj, "status node not found or invalid" );
+        goto error;
+    }
+    if ( strcmp( node->u.string.ptr, "ok" ) != 0 )
+    {
+        msg_Warn( p_obj, "Bad request status" );
+        goto error;
+    }
+    node = jsongetbyname( root, "results" );
+    if ( !node || node->type != json_array )
+    {
+        msg_Warn( p_obj, "Bad results array or no results" );
+        goto error;
+    }
+    p_results->p_results = calloc( node->u.array.length, sizeof(acoustid_result_t) );
+    if ( ! p_results->p_results ) goto error;
+    p_results->count = node->u.array.length;
+    for( unsigned int i=0; i<node->u.array.length; i++ )
+    {
+        json_value *resultnode = node->u.array.values[i];
+        if ( resultnode && resultnode->type == json_object )
+        {
+            acoustid_result_t *p_result = & p_results->p_results[i];
+            json_value *value = jsongetbyname( resultnode, "score" );
+            if ( value && value->type == json_double )
+                p_result->d_score = value->u.dbl;
+            value = jsongetbyname( resultnode, "id" );
+            if ( value && value->type == json_string )
+                p_result->psz_id = strdup( value->u.string.ptr );
+            parse_recordings( p_obj, jsongetbyname( resultnode, "recordings" ), p_result );
+        }
+    }
+    json_value_free( root );
+    return true;
+
+error:
+    if ( root ) json_value_free( root );
+    return false;
+}
+
+int DoAcoustIdWebRequest( vlc_object_t *p_obj, acoustid_fingerprint_t *p_data )
+{
+    char p_buffer[4096];
+    stream_t *p_stream;
+    int i_ret;
+    char *psz_url;
+
+    if ( !p_data->psz_fingerprint ) return VLC_SUCCESS;
+
+    i_ret = asprintf( &psz_url,
+              "http://api-bollard.rhcloud.com/acoustid.php?meta=recordings+tracks+usermeta+releases&duration=%d&fingerprint=%s",
+              p_data->i_duration, p_data->psz_fingerprint );
+    if ( i_ret < 1 ) return VLC_EGENERIC;
+
+    msg_Dbg( p_obj, "Querying AcoustID from %s", psz_url );
+    p_stream = stream_UrlNew( p_obj, psz_url );
+    if ( !p_stream ) return VLC_EGENERIC;
+
+    /* read answer */
+    i_ret = stream_Read( p_stream, p_buffer, sizeof(p_buffer) - 1 );
+    if ( i_ret == 0 )
+    {
+        stream_Delete( p_stream );
+        return VLC_EGENERIC;
+    }
+    p_buffer[ i_ret ] = '\0';
+    stream_Delete( p_stream );
+
+    if ( ParseJson( p_obj, p_buffer, & p_data->results ) )
+    {
+        msg_Dbg( p_obj, "results count == %d", p_data->results.count );
+    } else {
+        msg_Dbg( p_obj, "No results" );
+    }
+
+    return VLC_SUCCESS;
+}
diff --git a/modules/misc/webservices/acoustid.h b/modules/misc/webservices/acoustid.h
new file mode 100644
index 0000000..cfaf957
--- /dev/null
+++ b/modules/misc/webservices/acoustid.h
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * acoustid.h: AcoustId webservice parser
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 <vlc_interface.h>
+
+#define MB_ID_SIZE 36
+
+struct musicbrainz_recording_t
+{
+    char *psz_artist;
+    char *psz_title;
+    char sz_musicbrainz_id[MB_ID_SIZE];
+};
+typedef struct musicbrainz_recording_t musicbrainz_recording_t;
+
+struct acoustid_result_t
+{
+    double d_score;
+    char *psz_id;
+    struct
+    {
+        unsigned int count;
+        musicbrainz_recording_t *p_recordings;
+    } recordings;
+};
+typedef struct acoustid_result_t acoustid_result_t;
+
+struct acoustid_results_t
+{
+    acoustid_result_t * p_results;
+    unsigned int count;
+};
+typedef struct acoustid_results_t acoustid_results_t;
+
+struct acoustid_fingerprint_t
+{
+    char *psz_fingerprint;
+    unsigned int i_duration;
+    acoustid_results_t results;
+};
+typedef struct acoustid_fingerprint_t acoustid_fingerprint_t;
+
+int DoAcoustIdWebRequest( vlc_object_t *p_obj, acoustid_fingerprint_t *p_data );
+void free_acoustid_result_t( acoustid_result_t * r );
diff --git a/modules/misc/webservices/json.c b/modules/misc/webservices/json.c
new file mode 100644
index 0000000..853c6c1
--- /dev/null
+++ b/modules/misc/webservices/json.c
@@ -0,0 +1,741 @@
+/* vim: set et ts=3 sw=3 ft=c:
+ *
+ * Copyright (C) 2012 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "json.h"
+
+#ifdef _MSC_VER
+   #ifndef _CRT_SECURE_NO_WARNINGS
+      #define _CRT_SECURE_NO_WARNINGS
+   #endif
+#endif
+
+#ifdef __cplusplus
+   const struct _json_value json_value_none; /* zero-d by ctor */
+#else
+   const struct _json_value json_value_none = { 0 };
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+typedef unsigned short json_uchar;
+
+static unsigned char hex_value (json_char c)
+{
+   if (c >= 'A' && c <= 'F')
+      return (c - 'A') + 10;
+
+   if (c >= 'a' && c <= 'f')
+      return (c - 'a') + 10;
+
+   if (c >= '0' && c <= '9')
+      return c - '0';
+
+   return 0xFF;
+}
+
+typedef struct
+{
+   json_settings settings;
+   int first_pass;
+
+   unsigned long used_memory;
+
+   unsigned int uint_max;
+   unsigned long ulong_max;
+
+} json_state;
+
+static void * json_alloc (json_state * state, unsigned long size, int zero)
+{
+   void * mem;
+
+   if ((state->ulong_max - state->used_memory) < size)
+      return 0;
+
+   if (state->settings.max_memory
+         && (state->used_memory += size) > state->settings.max_memory)
+   {
+      return 0;
+   }
+
+   if (! (mem = zero ? calloc (size, 1) : malloc (size)))
+      return 0;
+
+   return mem;
+}
+
+static int new_value
+   (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type)
+{
+   json_value * value;
+   int values_size;
+
+   if (!state->first_pass)
+   {
+      value = *top = *alloc;
+      *alloc = (*alloc)->_reserved.next_alloc;
+
+      if (!*root)
+         *root = value;
+
+      switch (value->type)
+      {
+         case json_array:
+
+            if (! (value->u.array.values = (json_value **) json_alloc
+               (state, value->u.array.length * sizeof (json_value *), 0)) )
+            {
+               return 0;
+            }
+
+            break;
+
+         case json_object:
+
+            values_size = sizeof (*value->u.object.values) * value->u.object.length;
+
+            if (! ((*(void **) &value->u.object.values) = json_alloc
+                  (state, values_size + ((unsigned long) value->u.object.values), 0)) )
+            {
+               return 0;
+            }
+
+            value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size;
+
+            break;
+
+         case json_string:
+
+            if (! (value->u.string.ptr = (json_char *) json_alloc
+               (state, (value->u.string.length + 1) * sizeof (json_char), 0)) )
+            {
+               return 0;
+            }
+
+            break;
+
+         default:
+            break;
+      };
+
+      value->u.array.length = 0;
+
+      return 1;
+   }
+
+   value = (json_value *) json_alloc (state, sizeof (json_value), 1);
+
+   if (!value)
+      return 0;
+
+   if (!*root)
+      *root = value;
+
+   value->type = type;
+   value->parent = *top;
+
+   if (*alloc)
+      (*alloc)->_reserved.next_alloc = value;
+
+   *alloc = *top = value;
+
+   return 1;
+}
+
+#define e_off \
+   ((int) (i - cur_line_begin))
+
+#define whitespace \
+   case '\n': ++ cur_line;  cur_line_begin = i; \
+   case ' ': case '\t': case '\r'
+
+#define string_add(b)  \
+   do { if (!state.first_pass) string [string_length] = b;  ++ string_length; } while (0);
+
+const static int
+   flag_next = 1, flag_reproc = 2, flag_need_comma = 4, flag_seek_value = 8, flag_exponent = 16,
+   flag_got_exponent_sign = 32, flag_escaped = 64, flag_string = 128, flag_need_colon = 256,
+   flag_done = 512;
+
+json_value * json_parse_ex (json_settings * settings, const json_char * json, char * error_buf)
+{
+   json_char error [128];
+   unsigned int cur_line;
+   const json_char * cur_line_begin, * i;
+   json_value * top, * root, * alloc = 0;
+   json_state state;
+   int flags;
+
+   error[0] = '\0';
+
+   memset (&state, 0, sizeof (json_state));
+   memcpy (&state.settings, settings, sizeof (json_settings));
+
+   memset (&state.uint_max, 0xFF, sizeof (state.uint_max));
+   memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max));
+
+   state.uint_max -= 8; /* limit of how much can be added before next check */
+   state.ulong_max -= 8;
+
+   for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass)
+   {
+      json_uchar uchar;
+      unsigned char uc_b1, uc_b2, uc_b3, uc_b4;
+      json_char * string;
+      unsigned int string_length;
+
+      top = root = 0;
+      flags = flag_seek_value;
+
+      cur_line = 1;
+      cur_line_begin = json;
+
+      for (i = json ;; ++ i)
+      {
+         json_char b = *i;
+
+         if (flags & flag_done)
+         {
+            if (!b)
+               break;
+
+            switch (b)
+            {
+               whitespace:
+                  continue;
+
+               default:
+                  sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b);
+                  goto e_failed;
+            };
+         }
+
+         if (flags & flag_string)
+         {
+            if (!b)
+            {  sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off);
+               goto e_failed;
+            }
+
+            if (string_length > state.uint_max)
+               goto e_overflow;
+
+            if (flags & flag_escaped)
+            {
+               flags &= ~ flag_escaped;
+
+               switch (b)
+               {
+                  case 'b':  string_add ('\b');  break;
+                  case 'f':  string_add ('\f');  break;
+                  case 'n':  string_add ('\n');  break;
+                  case 'r':  string_add ('\r');  break;
+                  case 't':  string_add ('\t');  break;
+                  case 'u':
+
+                    if ((uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF
+                          || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF)
+                    {
+                        sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off);
+                        goto e_failed;
+                    }
+
+                    uc_b1 = uc_b1 * 16 + uc_b2;
+                    uc_b2 = uc_b3 * 16 + uc_b4;
+
+                    uchar = ((json_char) uc_b1) * 256 + uc_b2;
+
+                    if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F))
+                    {
+                       string_add ((json_char) uchar);
+                       break;
+                    }
+
+                    if (uchar <= 0x7FF)
+                    {
+                        if (state.first_pass)
+                           string_length += 2;
+                        else
+                        {  string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x3) << 3);
+                           string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
+                        }
+
+                        break;
+                    }
+
+                    if (state.first_pass)
+                       string_length += 3;
+                    else
+                    {  string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4);
+                       string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6);
+                       string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
+                    }
+
+                    break;
+
+                  default:
+                     string_add (b);
+               };
+
+               continue;
+            }
+
+            if (b == '\\')
+            {
+               flags |= flag_escaped;
+               continue;
+            }
+
+            if (b == '"')
+            {
+               if (!state.first_pass)
+                  string [string_length] = 0;
+
+               flags &= ~ flag_string;
+               string = 0;
+
+               switch (top->type)
+               {
+                  case json_string:
+
+                     top->u.string.length = string_length;
+                     flags |= flag_next;
+
+                     break;
+
+                  case json_object:
+
+                     if (state.first_pass)
+                        (*(json_char **) &top->u.object.values) += string_length + 1;
+                     else
+                     {  
+                        top->u.object.values [top->u.object.length].name
+                           = (json_char *) top->_reserved.object_mem;
+
+                        (*(json_char **) &top->_reserved.object_mem) += string_length + 1;
+                     }
+
+                     flags |= flag_seek_value | flag_need_colon;
+                     continue;
+
+                  default:
+                     break;
+               };
+            }
+            else
+            {
+               string_add (b);
+               continue;
+            }
+         }
+
+         if (flags & flag_seek_value)
+         {
+            switch (b)
+            {
+               whitespace:
+                  continue;
+
+               case ']':
+
+                  if (top->type == json_array)
+                     flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
+                  else if (!state.settings.settings & json_relaxed_commas)
+                  {  sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off);
+                     goto e_failed;
+                  }
+
+                  break;
+
+               default:
+
+                  if (flags & flag_need_comma)
+                  {
+                     if (b == ',')
+                     {  flags &= ~ flag_need_comma;
+                        continue;
+                     }
+                     else
+                     {  sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b);
+                        goto e_failed;
+                     }
+                  }
+
+                  if (flags & flag_need_colon)
+                  {
+                     if (b == ':')
+                     {  flags &= ~ flag_need_colon;
+                        continue;
+                     }
+                     else
+                     {  sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b);
+                        goto e_failed;
+                     }
+                  }
+
+                  flags &= ~ flag_seek_value;
+
+                  switch (b)
+                  {
+                     case '{':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_object))
+                           goto e_alloc_failure;
+
+                        continue;
+
+                     case '[':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_array))
+                           goto e_alloc_failure;
+
+                        flags |= flag_seek_value;
+                        continue;
+
+                     case '"':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_string))
+                           goto e_alloc_failure;
+
+                        flags |= flag_string;
+
+                        string = top->u.string.ptr;
+                        string_length = 0;
+
+                        continue;
+
+                     case 't':
+
+                        if (*(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e')
+                           goto e_unknown_value;
+
+                        if (!new_value (&state, &top, &root, &alloc, json_boolean))
+                           goto e_alloc_failure;
+
+                        top->u.boolean = 1;
+
+                        flags |= flag_next;
+                        break;
+
+                     case 'f':
+
+                        if (*(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e')
+                           goto e_unknown_value;
+
+                        if (!new_value (&state, &top, &root, &alloc, json_boolean))
+                           goto e_alloc_failure;
+
+                        flags |= flag_next;
+                        break;
+
+                     case 'n':
+
+                        if (*(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l')
+                           goto e_unknown_value;
+
+                        if (!new_value (&state, &top, &root, &alloc, json_null))
+                           goto e_alloc_failure;
+
+                        flags |= flag_next;
+                        break;
+
+                     default:
+
+                        if (isdigit (b) || b == '-')
+                        {
+                           if (!new_value (&state, &top, &root, &alloc, json_integer))
+                              goto e_alloc_failure;
+
+                           flags &= ~ (flag_exponent | flag_got_exponent_sign);
+
+                           if (state.first_pass)
+                              continue;
+
+                           if (top->type == json_double)
+                              top->u.dbl = strtod (i, (json_char **) &i);
+                           else
+                              top->u.integer = strtol (i, (json_char **) &i, 10);
+
+                           flags |= flag_next | flag_reproc;
+                        }
+                        else
+                        {  sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b);
+                           goto e_failed;
+                        }
+                  };
+            };
+         }
+         else
+         {
+            switch (top->type)
+            {
+            case json_object:
+               
+               switch (b)
+               {
+                  whitespace:
+                     continue;
+
+                  case '"':
+
+                     if (flags & flag_need_comma && (!state.settings.settings & json_relaxed_commas))
+                     {
+                        sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off);
+                        goto e_failed;
+                     }
+
+                     flags |= flag_string;
+
+                     string = (json_char *) top->_reserved.object_mem;
+                     string_length = 0;
+
+                     break;
+                  
+                  case '}':
+
+                     flags = (flags & ~ flag_need_comma) | flag_next;
+                     break;
+
+                  case ',':
+
+                     if (flags & flag_need_comma)
+                     {
+                        flags &= ~ flag_need_comma;
+                        break;
+                     }
+
+                  default:
+
+                     sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b);
+                     goto e_failed;
+               };
+
+               break;
+
+            case json_integer:
+            case json_double:
+
+               if (isdigit (b))
+                  continue;
+
+               if (b == 'e' || b == 'E')
+               {
+                  if (!(flags & flag_exponent))
+                  {
+                     flags |= flag_exponent;
+                     top->type = json_double;
+
+                     continue;
+                  }
+               }
+               else if (b == '+' || b == '-')
+               {
+                  if (flags & flag_exponent && !(flags & flag_got_exponent_sign))
+                  {
+                     flags |= flag_got_exponent_sign;
+                     continue;
+                  }
+               }
+               else if (b == '.' && top->type == json_integer)
+               {
+                  top->type = json_double;
+                  continue;
+               }
+
+               flags |= flag_next | flag_reproc;
+               break;
+
+            default:
+               break;
+            };
+         }
+
+         if (flags & flag_reproc)
+         {
+            flags &= ~ flag_reproc;
+            -- i;
+         }
+
+         if (flags & flag_next)
+         {
+            flags = (flags & ~ flag_next) | flag_need_comma;
+
+            if (!top->parent)
+            {
+               /* root value done */
+
+               flags |= flag_done;
+               continue;
+            }
+
+            if (top->parent->type == json_array)
+               flags |= flag_seek_value;
+               
+            if (!state.first_pass)
+            {
+               json_value * parent = top->parent;
+
+               switch (parent->type)
+               {
+                  case json_object:
+
+                     parent->u.object.values
+                        [parent->u.object.length].value = top;
+
+                     break;
+
+                  case json_array:
+
+                     parent->u.array.values
+                           [parent->u.array.length] = top;
+
+                     break;
+
+                  default:
+                     break;
+               };
+            }
+
+            if ( (++ top->parent->u.array.length) > state.uint_max)
+               goto e_overflow;
+
+            top = top->parent;
+
+            continue;
+         }
+      }
+
+      alloc = root;
+   }
+
+   return root;
+
+e_unknown_value:
+
+   sprintf (error, "%d:%d: Unknown value", cur_line, e_off);
+   goto e_failed;
+
+e_alloc_failure:
+
+   strcpy (error, "Memory allocation failure");
+   goto e_failed;
+
+e_overflow:
+
+   sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off);
+   goto e_failed;
+
+e_failed:
+
+   if (error_buf)
+   {
+      if (*error)
+         strcpy (error_buf, error);
+      else
+         strcpy (error_buf, "Unknown error");
+   }
+
+   if (state.first_pass)
+      alloc = root;
+
+   while (alloc)
+   {
+      top = alloc->_reserved.next_alloc;
+      free (alloc);
+      alloc = top;
+   }
+
+   if (!state.first_pass)
+      json_value_free (root);
+
+   return 0;
+}
+
+json_value * json_parse (const json_char * json)
+{
+   json_settings settings;
+   memset (&settings, 0, sizeof (json_settings));
+
+   return json_parse_ex (&settings, json, 0);
+}
+
+void json_value_free (json_value * value)
+{
+   json_value * cur_value;
+
+   if (!value)
+      return;
+
+   value->parent = 0;
+
+   while (value)
+   {
+      switch (value->type)
+      {
+         case json_array:
+
+            if (!value->u.array.length)
+            {
+               free (value->u.array.values);
+               break;
+            }
+
+            value = value->u.array.values [-- value->u.array.length];
+            continue;
+
+         case json_object:
+
+            if (!value->u.object.length)
+            {
+               free (value->u.object.values);
+               break;
+            }
+
+            value = value->u.object.values [-- value->u.object.length].value;
+            continue;
+
+         case json_string:
+
+            free (value->u.string.ptr);
+            break;
+
+         default:
+            break;
+      };
+
+      cur_value = value;
+      value = value->parent;
+      free (cur_value);
+   }
+}
+
+
diff --git a/modules/misc/webservices/json.h b/modules/misc/webservices/json.h
new file mode 100644
index 0000000..c004a19
--- /dev/null
+++ b/modules/misc/webservices/json.h
@@ -0,0 +1,29 @@
+/*****************************************************************************
+ * json.h: json-parser fixups
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 _JSON_H
+#ifndef _JSONFIXUPS_H
+#define _JSONFIXUPS_H
+#include <vlc_common.h>
+#include <vlc_charset.h>
+/* json.c depends on the locale */
+#define strtod(foo,bar) us_strtod(foo,bar)
+#include "use_json.h"
+#endif
+#endif
diff --git a/modules/misc/webservices/use_json.h b/modules/misc/webservices/use_json.h
new file mode 100644
index 0000000..2bc1c44
--- /dev/null
+++ b/modules/misc/webservices/use_json.h
@@ -0,0 +1,191 @@
+/* vim: set et ts=3 sw=3 ft=c:
+ *
+ * Copyright (C) 2012 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _JSON_H
+#define _JSON_H
+
+#ifndef json_char
+   #define json_char char
+#endif
+
+#ifdef __cplusplus
+
+   #include <string.h>
+
+   extern "C"
+   {
+
+#endif
+
+typedef struct
+{
+   unsigned long max_memory;
+   int settings;
+
+} json_settings;
+
+#define json_relaxed_commas 1
+
+typedef enum
+{
+   json_none,
+   json_object,
+   json_array,
+   json_integer,
+   json_double,
+   json_string,
+   json_boolean,
+   json_null
+
+} json_type;
+
+extern const struct _json_value json_value_none;
+
+typedef struct _json_value
+{
+   struct _json_value * parent;
+
+   json_type type;
+
+   union
+   {
+      int boolean;
+      long integer;
+      double dbl;
+
+      struct
+      {
+         unsigned int length;
+         json_char * ptr; /* null terminated */
+
+      } string;
+
+      struct
+      {
+         unsigned int length;
+
+         struct
+         {
+            json_char * name;
+            struct _json_value * value;
+
+         } * values;
+
+      } object;
+
+      struct
+      {
+         unsigned int length;
+         struct _json_value ** values;
+
+      } array;
+
+   } u;
+
+   union
+   {
+      struct _json_value * next_alloc;
+      void * object_mem;
+
+   } _reserved;
+
+
+   /* Some C++ operator sugar */
+
+   #ifdef __cplusplus
+
+      public:
+
+         inline _json_value ()
+         {  memset (this, 0, sizeof (_json_value));
+         }
+
+         inline const struct _json_value &operator [] (int index) const
+         {
+            if (type != json_array || index < 0
+                     || ((unsigned int) index) >= u.array.length)
+            {
+               return json_value_none;
+            }
+
+            return *u.array.values [index];
+         }
+
+         inline const struct _json_value &operator [] (const char * index) const
+         { 
+            if (type != json_object)
+               return json_value_none;
+
+            for (unsigned int i = 0; i < u.object.length; ++ i)
+               if (!strcmp (u.object.values [i].name, index))
+                  return *u.object.values [i].value;
+
+            return json_value_none;
+         }
+
+         inline operator const char * () const
+         {  
+            switch (type)
+            {
+               case json_string:
+                  return u.string.ptr;
+
+               default:
+                  return "";
+            };
+         }
+
+         inline operator long () const
+         {  return u.integer;
+         }
+
+         inline operator bool () const
+         {  return u.boolean != 0;
+         }
+
+   #endif
+
+} json_value;
+
+json_value * json_parse
+   (const json_char * json);
+
+json_value * json_parse_ex
+   (json_settings * settings, const json_char * json, char * error);
+
+void json_value_free (json_value *);
+
+
+#ifdef __cplusplus
+   } /* extern "C" */
+#endif
+
+#endif
+
+
diff --git a/modules/stream_out/Modules.am b/modules/stream_out/Modules.am
index 26ad06b..eadc644 100644
--- a/modules/stream_out/Modules.am
+++ b/modules/stream_out/Modules.am
@@ -14,6 +14,7 @@ SOURCES_stream_out_record = record.c
 SOURCES_stream_out_smem = smem.c
 SOURCES_stream_out_setid = setid.c
 SOURCES_stream_out_langfromtelx = langfromtelx.c
+SOURCES_stream_out_chromaprint = chromaprint.c
 
 libstream_out_transcode_plugin_la_SOURCES = \
 	transcode/transcode.c transcode/transcode.h \
@@ -37,6 +38,7 @@ libvlc_LTLIBRARIES += \
 	libstream_out_smem_plugin.la \
 	libstream_out_setid_plugin.la \
 	libstream_out_langfromtelx_plugin.la \
+	libstream_out_chromaprint_plugin.la \
 	libstream_out_transcode_plugin.la
 
 # RTP plugin
diff --git a/modules/stream_out/chromaprint.c b/modules/stream_out/chromaprint.c
new file mode 100644
index 0000000..fe0ce48
--- /dev/null
+++ b/modules/stream_out/chromaprint.c
@@ -0,0 +1,223 @@
+/*****************************************************************************
+ * chromaprint.c: Chromaprint Fingerprinter Module
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_input.h>
+#include <vlc_block.h>
+#include <vlc_sout.h>
+
+#include <assert.h>
+
+#include "chromaprint.h"
+
+/*****************************************************************************
+ * Exported prototypes
+ *****************************************************************************/
+static int      Open    ( vlc_object_t * );
+static void     Close   ( vlc_object_t * );
+
+static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * );
+static int               Del ( sout_stream_t *, sout_stream_id_t * );
+static int               Send( sout_stream_t *, sout_stream_id_t *, block_t* );
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+#define DURATION_TEXT N_("Duration of the fingerprinting" )
+#define DURATION_LONGTEXT N_("Default: 90sec")
+
+vlc_module_begin ()
+    set_description( N_("Chromaprint stream output") )
+    set_capability( "sout stream", 0 )
+    add_shortcut( "chromaprint" )
+    add_integer( "duration", 90, DURATION_TEXT, DURATION_LONGTEXT, true )
+    set_callbacks( Open, Close )
+vlc_module_end ()
+
+struct sout_stream_sys_t
+{
+    unsigned int i_duration;
+    unsigned int i_total_samples;
+    int i_samples;
+    bool b_finished;
+    bool b_done;
+    ChromaprintContext *p_chromaprint_ctx;
+    sout_stream_id_t *id;
+};
+
+struct sout_stream_id_t
+{
+    int i_samples;
+    unsigned int i_channels;
+    unsigned int i_samplesize;
+    unsigned int i_samplerate;
+};
+
+/*****************************************************************************
+ * Open:
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+    sout_stream_t *p_stream = (sout_stream_t*)p_this;
+    sout_stream_sys_t *p_sys;
+
+    p_stream->pf_add  = Add;
+    p_stream->pf_del  = Del;
+    p_stream->pf_send = Send;
+    p_stream->p_sys = p_sys = malloc(sizeof(sout_stream_sys_t));
+    if ( ! p_sys ) return VLC_ENOMEM;
+    p_sys->id = NULL;
+    p_sys->b_finished = false;
+    p_sys->b_done = false;
+    p_sys->i_total_samples = 0;
+    p_sys->i_duration = var_CreateGetInteger( p_stream->p_parent, "duration" );
+    msg_Dbg( p_stream, "chromaprint version %s", chromaprint_get_version() );
+    p_sys->p_chromaprint_ctx = chromaprint_new( CHROMAPRINT_ALGORITHM_DEFAULT );
+    if ( ! p_sys->p_chromaprint_ctx )
+    {
+        msg_Err( p_stream, "Can't create chromaprint context" );
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
+
+static void Finish( sout_stream_t *p_stream )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    char *psz_fingerprint = NULL;
+    if ( p_sys->b_finished && chromaprint_finish( p_sys->p_chromaprint_ctx ) )
+    {
+        chromaprint_get_fingerprint( p_sys->p_chromaprint_ctx,
+                                     &psz_fingerprint );
+        if ( psz_fingerprint )
+        {
+            msg_Dbg( p_stream, "DURATION=%d;FINGERPRINT=%s",
+                    p_sys->i_total_samples / p_sys->id->i_samplerate,
+                    psz_fingerprint );
+            chromaprint_dealloc( psz_fingerprint );
+        }
+    } else {
+        msg_Dbg( p_stream, "Cannot create %ds fingerprint (not enough samples?)",
+                 p_sys->i_duration );
+    }
+    p_sys->b_done = true;
+    msg_Dbg( p_stream, "Fingerprinting finished" );
+}
+
+/*****************************************************************************
+ * Close:
+ *****************************************************************************/
+static void Close( vlc_object_t * p_this )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    if ( !p_sys->b_done ) Finish( p_stream );
+
+    if ( !p_sys->p_chromaprint_ctx ) goto end;
+    chromaprint_free( p_sys->p_chromaprint_ctx );
+end:
+    free( p_sys );
+}
+
+static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    sout_stream_id_t *id = malloc( sizeof( sout_stream_id_t ) );
+
+    if ( id && p_fmt->i_cat == AUDIO_ES && !p_sys->id )
+    {
+        if( p_fmt->i_codec != VLC_CODEC_S16L )
+        {
+            msg_Warn( p_stream, "bad input format: need s16l" );
+            goto end;
+        }
+
+        id->i_samplesize = p_fmt->audio.i_bitspersample >> 3;
+        id->i_channels = p_fmt->audio.i_channels;
+        id->i_samplerate = p_fmt->audio.i_rate;
+        id->i_samples = p_sys->i_duration * id->i_samplerate;
+
+        if ( id->i_channels > 2 || !p_sys->p_chromaprint_ctx )
+            goto end;
+
+        if ( !chromaprint_start( p_sys->p_chromaprint_ctx, p_fmt->audio.i_rate, id->i_channels ) )
+        {
+            msg_Err( p_stream, "Failed starting chromaprint on %dHz %dch samples",
+                     p_fmt->audio.i_rate, id->i_channels );
+            goto end;
+        }
+        else
+        {
+            p_sys->id = id;
+            msg_Dbg( p_stream, "Starting chromaprint on %dHz %dch samples",
+                     p_fmt->audio.i_rate, id->i_channels );
+        }
+    }
+end:
+    return id;
+}
+
+static int Del( sout_stream_t *p_stream, sout_stream_id_t *id )
+{
+    Finish( p_stream );
+    free( id );
+    return VLC_SUCCESS;
+}
+
+static int Send( sout_stream_t *p_stream, sout_stream_id_t *id,
+                 block_t *p_buf )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    if ( p_sys->id == id )
+    {
+        while( p_buf )
+        {
+            int i_samples = p_buf->i_buffer / (id->i_samplesize * id->i_channels);
+            p_sys->i_total_samples += i_samples;
+            if ( !p_sys->b_finished && id->i_samples > 0 && p_buf->i_buffer )
+            {
+                if(! chromaprint_feed( p_sys->p_chromaprint_ctx,
+                                       p_buf->p_buffer,
+                                       p_buf->i_buffer / id->i_samplesize ) )
+                    msg_Warn( p_stream, "feed error" );
+                id->i_samples -= i_samples;
+                if ( id->i_samples < 1 && !p_sys->b_finished )
+                {
+                    p_sys->b_finished = true;
+                    msg_Dbg( p_stream, "Fingerprint collection finished" );
+                }
+            }
+            p_buf = p_buf->p_next;
+        }
+    }
+    block_ChainRelease( p_buf );
+    return VLC_SUCCESS;
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index 9ac25d3..65bee8a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -78,6 +78,7 @@ pluginsinclude_HEADERS = \
 	../include/vlc_rand.h \
 	../include/vlc_services_discovery.h \
 	../include/vlc_sql.h \
+	../include/vlc_fingerprinter.h \
 	../include/vlc_sout.h \
 	../include/vlc_spu.h \
 	../include/vlc_stream.h \
@@ -460,6 +461,7 @@ SOURCES_libvlc_common = \
 	misc/filter_chain.c \
 	misc/http_auth.c \
 	misc/sql.c \
+	misc/fingerprinter.c \
 	misc/text_style.c \
 	misc/subpicture.c \
 	misc/subpicture.h \
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 0c36f01..0790ff7 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -642,3 +642,5 @@ xml_ReaderDelete
 xml_ReaderReset
 vlc_keycode2str
 vlc_str2keycode
+fingerprinter_Create
+fingerprinter_Destroy
diff --git a/src/misc/fingerprinter.c b/src/misc/fingerprinter.c
new file mode 100644
index 0000000..863c9ff
--- /dev/null
+++ b/src/misc/fingerprinter.c
@@ -0,0 +1,59 @@
+/*****************************************************************************
+ * fingerprinter.c: Fingerprinter creator and destructor
+ *****************************************************************************
+ * Copyright (C) 2012 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_fingerprinter.h>
+#include <vlc_modules.h>
+#include <assert.h>
+#include "libvlc.h"
+
+fingerprinter_thread_t *fingerprinter_Create( vlc_object_t *p_this )
+{
+    fingerprinter_thread_t *p_fingerprint;
+
+    p_fingerprint = ( fingerprinter_thread_t * )
+            vlc_custom_create( p_this, sizeof( *p_fingerprint ), "fingerprinter" );
+    if( !p_fingerprint )
+    {
+        msg_Err( p_this, "unable to create fingerprinter" );
+        return NULL;
+    }
+
+    p_fingerprint->p_module = module_need( p_fingerprint, "fingerprinter",
+                                           "acoustid", false );
+    if( !p_fingerprint->p_module )
+    {
+        vlc_object_release( p_fingerprint );
+        msg_Err( p_this, "AcoustID fingerprinter not found" );
+        return NULL;
+    }
+
+    return p_fingerprint;
+}
+
+void fingerprinter_Destroy( fingerprinter_thread_t *p_fingerprint )
+{
+    module_unneed( p_fingerprint, p_fingerprint->p_module );
+    vlc_object_release( p_fingerprint );
+}
-- 
1.7.6




More information about the vlc-devel mailing list