1 /*****************************************************************************
2 * fingerprinter.c: Audio fingerprinter module
3 *****************************************************************************
4 * Copyright (C) 2012 VLC authors and VideoLAN
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
26 #include <vlc_common.h>
27 #include <vlc_plugin.h>
28 #include <vlc_stream.h>
29 #include <vlc_modules.h>
33 #include <vlc_player.h>
34 #include <vlc_fingerprinter.h>
35 #include "webservices/acoustid.h"
36 #include "../stream_out/chromaprint_data.h"
38 /*****************************************************************************
40 *****************************************************************************/
42 struct fingerprinter_sys_t
46 vlc_player_listener_id
*listener_id
;
63 static int Open (vlc_object_t
*);
64 static void Close (vlc_object_t
*);
65 static void CleanSys (fingerprinter_sys_t
*);
66 static void *Run(void *);
68 /*****************************************************************************
70 ****************************************************************************/
72 set_category(CAT_ADVANCED
)
73 set_subcategory(SUBCAT_ADVANCED_MISC
)
74 set_shortname(N_("acoustid"))
75 set_description(N_("Track fingerprinter (based on Acoustid)"))
76 set_capability("fingerprinter", 10)
77 set_callbacks(Open
, Close
)
80 /*****************************************************************************
82 *****************************************************************************/
84 static int EnqueueRequest( fingerprinter_thread_t
*f
, fingerprint_request_t
*r
)
86 fingerprinter_sys_t
*p_sys
= f
->p_sys
;
87 vlc_mutex_lock( &p_sys
->incoming
.lock
);
88 int i_ret
= vlc_array_append( &p_sys
->incoming
.queue
, r
);
89 vlc_mutex_unlock( &p_sys
->incoming
.lock
);
93 static void QueueIncomingRequests( fingerprinter_sys_t
*p_sys
)
95 vlc_mutex_lock( &p_sys
->incoming
.lock
);
97 for( size_t i
= vlc_array_count( &p_sys
->incoming
.queue
); i
> 0 ; i
-- )
99 fingerprint_request_t
*r
= vlc_array_item_at_index( &p_sys
->incoming
.queue
, i
- 1 );
100 if( vlc_array_append( &p_sys
->processing
.queue
, r
) )
101 fingerprint_request_Delete( r
);
103 vlc_array_clear( &p_sys
->incoming
.queue
);
104 vlc_mutex_unlock(&p_sys
->incoming
.lock
);
107 static fingerprint_request_t
* GetResult( fingerprinter_thread_t
*f
)
109 fingerprint_request_t
*r
= NULL
;
110 fingerprinter_sys_t
*p_sys
= f
->p_sys
;
111 vlc_mutex_lock( &p_sys
->results
.lock
);
112 if ( vlc_array_count( &p_sys
->results
.queue
) )
114 r
= vlc_array_item_at_index( &p_sys
->results
.queue
, 0 );
115 vlc_array_remove( &p_sys
->results
.queue
, 0 );
117 vlc_mutex_unlock( &p_sys
->results
.lock
);
121 static void ApplyResult( fingerprint_request_t
*p_r
, size_t i_resultid
)
123 if ( i_resultid
>= vlc_array_count( & p_r
->results
.metas_array
) ) return;
125 vlc_meta_t
*p_meta
= (vlc_meta_t
*)
126 vlc_array_item_at_index( & p_r
->results
.metas_array
, i_resultid
);
127 input_item_t
*p_item
= p_r
->p_item
;
128 vlc_mutex_lock( &p_item
->lock
);
129 vlc_meta_Merge( p_item
->p_meta
, p_meta
);
130 vlc_mutex_unlock( &p_item
->lock
);
133 static void player_on_state_changed(vlc_player_t
*player
,
134 enum vlc_player_state new_state
,
138 fingerprinter_sys_t
*p_sys
= p_user_data
;
139 if (new_state
== VLC_PLAYER_STATE_STOPPED
)
141 p_sys
->processing
.b_working
= false;
142 vlc_cond_signal( &p_sys
->processing
.cond
);
146 static void DoFingerprint( fingerprinter_thread_t
*p_fingerprinter
,
147 acoustid_fingerprint_t
*fp
,
148 const char *psz_uri
)
150 input_item_t
*p_item
= input_item_New( NULL
, NULL
);
151 if ( unlikely(p_item
== NULL
) )
154 char *psz_sout_option
;
155 /* Note: need at -max- 2 channels, but we can't guess it before playing */
156 /* the stereo upmix could make the mono tracks fingerprint to differ :/ */
157 if ( asprintf( &psz_sout_option
,
158 "sout=#transcode{acodec=%s,channels=2}:chromaprint",
159 ( VLC_CODEC_S16L
== VLC_CODEC_S16N
) ? "s16l" : "s16b" )
162 input_item_Release( p_item
);
166 input_item_AddOption( p_item
, psz_sout_option
, VLC_INPUT_OPTION_TRUSTED
);
167 free( psz_sout_option
);
168 if ( fp
->i_duration
)
170 if ( asprintf( &psz_sout_option
, "stop-time=%u", fp
->i_duration
) == -1 )
172 input_item_Release( p_item
);
175 input_item_AddOption( p_item
, psz_sout_option
, VLC_INPUT_OPTION_TRUSTED
);
176 free( psz_sout_option
);
178 input_item_SetURI( p_item
, psz_uri
) ;
180 chromaprint_fingerprint_t chroma_fingerprint
;
182 chroma_fingerprint
.psz_fingerprint
= NULL
;
183 chroma_fingerprint
.i_duration
= fp
->i_duration
;
185 var_Create( p_fingerprinter
, "fingerprint-data", VLC_VAR_ADDRESS
);
186 var_SetAddress( p_fingerprinter
, "fingerprint-data", &chroma_fingerprint
);
188 vlc_player_t
*player
= p_fingerprinter
->p_sys
->player
;
189 vlc_player_Lock(player
);
191 p_fingerprinter
->p_sys
->processing
.b_working
= true;
193 int ret
= vlc_player_SetCurrentMedia(player
, p_item
);
194 if (ret
== VLC_SUCCESS
)
195 ret
= vlc_player_Start(player
);
196 input_item_Release(p_item
);
198 if (ret
== VLC_SUCCESS
)
200 while( p_fingerprinter
->p_sys
->processing
.b_working
)
201 vlc_player_CondWait(player
,
202 &p_fingerprinter
->p_sys
->processing
.cond
);
204 fp
->psz_fingerprint
= chroma_fingerprint
.psz_fingerprint
;
205 if( !fp
->i_duration
) /* had not given hint */
206 fp
->i_duration
= chroma_fingerprint
.i_duration
;
209 vlc_player_Unlock(player
);
212 /*****************************************************************************
214 *****************************************************************************/
215 static int Open(vlc_object_t
*p_this
)
217 fingerprinter_thread_t
*p_fingerprinter
= (fingerprinter_thread_t
*) p_this
;
218 fingerprinter_sys_t
*p_sys
= calloc(1, sizeof(fingerprinter_sys_t
));
223 p_fingerprinter
->p_sys
= p_sys
;
225 var_Create(p_fingerprinter
, "vout", VLC_VAR_STRING
);
226 var_SetString(p_fingerprinter
, "vout", "dummy");
227 var_Create(p_fingerprinter
, "aout", VLC_VAR_STRING
);
228 var_SetString(p_fingerprinter
, "aout", "dummy");
229 p_sys
->player
= vlc_player_New(VLC_OBJECT(p_fingerprinter
),
230 VLC_PLAYER_LOCK_NORMAL
, NULL
, NULL
);
237 static const struct vlc_player_cbs cbs
= {
238 .on_state_changed
= player_on_state_changed
,
241 vlc_player_Lock(p_sys
->player
);
243 vlc_player_AddListener(p_sys
->player
, &cbs
, p_fingerprinter
->p_sys
);
244 vlc_player_Unlock(p_sys
->player
);
245 if (!p_sys
->listener_id
)
247 vlc_player_Delete(p_sys
->player
);
252 vlc_array_init( &p_sys
->incoming
.queue
);
253 vlc_mutex_init( &p_sys
->incoming
.lock
);
255 vlc_array_init( &p_sys
->processing
.queue
);
256 vlc_cond_init( &p_sys
->processing
.cond
);
258 vlc_array_init( &p_sys
->results
.queue
);
259 vlc_mutex_init( &p_sys
->results
.lock
);
261 p_fingerprinter
->pf_enqueue
= EnqueueRequest
;
262 p_fingerprinter
->pf_getresults
= GetResult
;
263 p_fingerprinter
->pf_apply
= ApplyResult
;
265 var_Create( p_fingerprinter
, "results-available", VLC_VAR_BOOL
);
266 if( vlc_clone( &p_sys
->thread
, Run
, p_fingerprinter
,
267 VLC_THREAD_PRIORITY_LOW
) )
269 msg_Err( p_fingerprinter
, "cannot spawn fingerprinter thread" );
281 /*****************************************************************************
283 *****************************************************************************/
284 static void Close(vlc_object_t
*p_this
)
286 fingerprinter_thread_t
*p_fingerprinter
= (fingerprinter_thread_t
*) p_this
;
287 fingerprinter_sys_t
*p_sys
= p_fingerprinter
->p_sys
;
289 vlc_cancel( p_sys
->thread
);
290 vlc_join( p_sys
->thread
, NULL
);
296 static void CleanSys( fingerprinter_sys_t
*p_sys
)
298 for ( size_t i
= 0; i
< vlc_array_count( &p_sys
->incoming
.queue
); i
++ )
299 fingerprint_request_Delete( vlc_array_item_at_index( &p_sys
->incoming
.queue
, i
) );
300 vlc_array_clear( &p_sys
->incoming
.queue
);
302 for ( size_t i
= 0; i
< vlc_array_count( &p_sys
->processing
.queue
); i
++ )
303 fingerprint_request_Delete( vlc_array_item_at_index( &p_sys
->processing
.queue
, i
) );
304 vlc_array_clear( &p_sys
->processing
.queue
);
306 for ( size_t i
= 0; i
< vlc_array_count( &p_sys
->results
.queue
); i
++ )
307 fingerprint_request_Delete( vlc_array_item_at_index( &p_sys
->results
.queue
, i
) );
308 vlc_array_clear( &p_sys
->results
.queue
);
310 vlc_player_Lock(p_sys
->player
);
311 vlc_player_RemoveListener(p_sys
->player
, p_sys
->listener_id
);
312 vlc_player_Unlock(p_sys
->player
);
313 vlc_player_Delete(p_sys
->player
);
316 static void fill_metas_with_results( fingerprint_request_t
*p_r
, acoustid_fingerprint_t
*p_f
)
318 for( unsigned int i
=0 ; i
< p_f
->results
.count
; i
++ )
320 acoustid_result_t
*p_result
= & p_f
->results
.p_results
[ i
];
321 for ( unsigned int j
=0 ; j
< p_result
->recordings
.count
; j
++ )
323 acoustid_mb_result_t
*p_record
= & p_result
->recordings
.p_recordings
[ j
];
324 vlc_meta_t
*p_meta
= vlc_meta_New();
327 vlc_meta_Set( p_meta
, vlc_meta_Title
, p_record
->psz_title
);
328 vlc_meta_Set( p_meta
, vlc_meta_Artist
, p_record
->psz_artist
);
329 vlc_meta_AddExtra( p_meta
, "musicbrainz-id", p_record
->s_musicbrainz_id
);
330 if( vlc_array_append( & p_r
->results
.metas_array
, p_meta
) )
331 vlc_meta_Delete( p_meta
);
337 /*****************************************************************************
339 *****************************************************************************/
340 static void *Run( void *opaque
)
342 fingerprinter_thread_t
*p_fingerprinter
= opaque
;
343 fingerprinter_sys_t
*p_sys
= p_fingerprinter
->p_sys
;
348 vlc_tick_sleep( VLC_TICK_FROM_SEC(1) );
350 QueueIncomingRequests( p_sys
);
354 bool results_available
= false;
355 while( vlc_array_count( &p_sys
->processing
.queue
) )
357 int canc
= vlc_savecancel();
358 fingerprint_request_t
*p_data
= vlc_array_item_at_index( &p_sys
->processing
.queue
, 0 );
360 char *psz_uri
= input_item_GetURI( p_data
->p_item
);
361 if ( psz_uri
!= NULL
)
363 acoustid_fingerprint_t acoustid_print
;
365 memset( &acoustid_print
, 0, sizeof (acoustid_print
) );
366 /* overwrite with hint, as in this case, fingerprint's session will be truncated */
367 if ( p_data
->i_duration
)
368 acoustid_print
.i_duration
= p_data
->i_duration
;
370 DoFingerprint( p_fingerprinter
, &acoustid_print
, psz_uri
);
373 acoustid_config_t cfg
= { .p_obj
= VLC_OBJECT(p_fingerprinter
),
374 .psz_server
= NULL
, .psz_apikey
= NULL
};
375 acoustid_lookup_fingerprint( &cfg
, &acoustid_print
);
376 fill_metas_with_results( p_data
, &acoustid_print
);
378 for( unsigned j
= 0; j
< acoustid_print
.results
.count
; j
++ )
379 acoustid_result_release( &acoustid_print
.results
.p_results
[j
] );
380 if( acoustid_print
.results
.count
)
381 free( acoustid_print
.results
.p_results
);
382 free( acoustid_print
.psz_fingerprint
);
384 vlc_restorecancel(canc
);
387 vlc_mutex_lock( &p_sys
->results
.lock
);
388 if( vlc_array_append( &p_sys
->results
.queue
, p_data
) )
389 fingerprint_request_Delete( p_data
);
391 results_available
= true;
392 vlc_mutex_unlock( &p_sys
->results
.lock
);
394 // the fingerprint request must not exist both in the
395 // processing and results queue, even in case of thread
396 // cancellation, so remove it immediately
397 vlc_array_remove( &p_sys
->processing
.queue
, 0 );
402 if ( results_available
)
404 var_TriggerCallback( p_fingerprinter
, "results-available" );
408 vlc_assert_unreachable();