[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