File Coverage

blib/lib/Audio/Chromaprint.pm
Criterion Covered Total %
statement 69 90 76.6
branch 11 36 30.5
condition 0 6 0.0
subroutine 21 28 75.0
pod 16 18 88.8
total 117 178 65.7


line stmt bran cond sub pod time code
1             package Audio::Chromaprint;
2             # ABSTRACT: Interface to the Chromaprint library
3              
4 2     2   345437 use Moose;
  2         954173  
  2         15  
5 2     2   15761 use Carp qw< croak >;
  2         7  
  2         154  
6 2     2   1758 use FFI::Platypus 0.88;
  2         13982  
  2         62  
7 2     2   1009 use FFI::CheckLib;
  2         5624  
  2         151  
8 2     2   15 use Moose::Util::TypeConstraints;
  2         6  
  2         19  
9              
10             # This is in three statement so we could support 5.6.0,
11             # since hash version only came out in 5.8.0.
12 2     2   4755 use constant 'MIN_SILENCE_THRESHOLD' => 0;
  2         5  
  2         159  
13 2     2   13 use constant 'MAX_SILENCE_THRESHOLD' => 32_767;
  2         4  
  2         92  
14 2     2   13 use constant 'BYTES_PER_SAMPLE' => 2;
  2         5  
  2         3023  
15              
16             our $HAS_SUBS;
17             our %SUBS = (
18             '_new' => [ ['int'] => 'opaque' ],
19             '_get_version' => [ [] => 'string' ],
20             '_free' => [ ['opaque'] => 'void' ],
21             '_set_option' => [ [ 'opaque', 'string', 'int' ] => 'int' ],
22             '_start' => [ [ 'opaque', 'int', 'int' ] => 'int' ],
23             '_finish' => [ ['opaque'] => 'int' ],
24             '_feed' => [ ['opaque', 'string', 'int' ] => 'int' ],
25              
26             '_get_fingerprint_hash' => [ [ 'opaque', 'uint32*' ], 'int' ],
27             '_get_fingerprint' => [ [ 'opaque', 'opaque*' ], 'int' ],
28             '_get_raw_fingerprint' => [ [ 'opaque', 'opaque*', 'int*' ], 'int' ],
29             '_get_num_channels' => [ [ 'opaque' ], 'int' ],
30             '_get_sample_rate' => [ [ 'opaque' ], 'int' ],
31             '_get_item_duration' => [ [ 'opaque' ], 'int' ],
32             '_get_item_duration_ms' => [ [ 'opaque' ], 'int' ],
33             '_get_delay' => [ [ 'opaque' ], 'int' ],
34             '_get_delay_ms' => [ [ 'opaque' ], 'int' ],
35             '_get_raw_fingerprint_size' => [ [ 'opaque', 'int*' ], 'int' ],
36             '_clear_fingerprint' => [ [ 'opaque' ], 'int' ],
37              
38             '_dealloc' => [ [ 'opaque' ] => 'void' ],
39             );
40              
41             sub BUILD {
42 5 100   5 0 95 $HAS_SUBS++
43             and return;
44              
45 2         21 my $ffi = FFI::Platypus->new;
46              
47             # Setting this mangler lets is omit the chromaprint_ prefix
48             # from the attach call below, and the function names used
49             # by perl
50             $ffi->mangler( sub {
51 38     38   61790 my $name = shift;
52 38         152 $name =~ s/^_/chromaprint_/xms;
53 38         230 return $name;
54 2         54 } );
55              
56 2         33 $ffi->lib( find_lib_or_exit( 'lib' => 'chromaprint', alien => 'Alien::chromaprint' ) );
57              
58 38         1972 $ffi->attach( $_, @{ $SUBS{$_} } )
59 2         150769 for keys %SUBS;
60              
61 2         105 $ffi->attach_cast( '_opaque_to_string' => opaque => 'string' );
62             }
63              
64             subtype 'ChromaprintAlgorithm',
65             as 'Int',
66             where { /^[1234]$/xms },
67             message { 'algorithm must be 1, 2, 3 or 4' };
68              
69             subtype 'ChromaprintSilenceThreshold',
70             as 'Int',
71             where { $_ >= MIN_SILENCE_THRESHOLD() && $_ <= MAX_SILENCE_THRESHOLD() },
72             message { 'silence_threshold option must be between 0 and 32767' };
73              
74             has 'algorithm' => (
75             'is' => 'ro',
76             'isa' => 'ChromaprintAlgorithm',
77             'default' => sub {2},
78             );
79              
80             has 'cp' => (
81             'is' => 'ro',
82             'lazy' => 1,
83             'init_arg' => undef,
84             'default' => sub {
85             my $self = shift;
86              
87             # subtract one from the algorithm so that
88             # 1 maps to 2 maps to CHROMAPRINT_ALGORITHM_TEST2
89             # (the latter has the value 1)
90             my $cp = _new( $self->algorithm - 1 );
91              
92             if ( $self->has_silence_threshold ) {
93             _set_option(
94             $cp, 'silence_threshold' => $self->silence_threshold,
95             ) or croak('Error setting option silence_threshold');
96             }
97              
98             return $cp;
99             }
100             );
101              
102             has 'silence_threshold' => (
103             'is' => 'ro',
104             'isa' => 'ChromaprintSilenceThreshold',
105             'predicate' => 'has_silence_threshold',
106             );
107              
108             sub get_version {
109             # generate chromaprint object
110 1 50   1 1 197 __PACKAGE__->can('_get_version')
111             or __PACKAGE__->new();
112              
113 1         17 return _get_version();
114             }
115              
116             sub start {
117 4     4 1 487 my ( $self, $sample_rate, $num_channels ) = @_;
118              
119 4 50       32 $sample_rate =~ /^[0-9]+$/xms
120             or croak 'sample_rate must be an integer';
121              
122 4 50       24 $num_channels =~ /^[12]$/xms
123             or croak 'num_channels must be 1 or 2';
124              
125 4 50       120 _start( $self->cp, $sample_rate, $num_channels )
126             or croak 'Unable to start (start)';
127             }
128              
129             sub set_option {
130 0     0 1 0 my ( $self, $name, $value ) = @_;
131              
132 0 0 0     0 $name && $value
133             or croak('set_option( name, value )');
134              
135 0 0       0 length $name
136             or croak('set_option requires a "name" string');
137              
138 0 0       0 $value =~ /^[0-9]+$/xms
139             or croak('set_option requires a "value" integer');
140              
141 0 0       0 if ( $name eq 'silence_threshold' ) {
142 0 0 0     0 $value >= MIN_SILENCE_THRESHOLD() && $value <= MAX_SILENCE_THRESHOLD()
143             or croak('silence_threshold option must be between 0 and 32767');
144             }
145              
146 0 0       0 _set_option( $self->cp, $name => $value )
147             or croak("Error setting option $name (set_option)");
148             }
149              
150             sub finish {
151 4     4 1 759 my $self = shift;
152 4 50       162 _finish( $self->cp )
153             or croak('Unable to finish (finish)');
154             }
155              
156             sub get_fingerprint_hash {
157 2     2 1 7 my $self = shift;
158 2         6 my $hash;
159 2 50       72 _get_fingerprint_hash( $self->cp, \$hash )
160             or croak('Unable to get fingerprint hash (get_fingerprint_hash)');
161 2         11 return $hash;
162             }
163              
164             sub get_fingerprint {
165 2     2 1 18 my $self = shift;
166 2         5 my $ptr;
167 2 50       70 _get_fingerprint($self->cp, \$ptr)
168             or croak('Unable to get fingerprint (get_fingerprint)');
169 2         14 my $str = _opaque_to_string($ptr);
170 2         11 _dealloc($ptr);
171 2         15 return $str;
172             }
173              
174             sub get_raw_fingerprint {
175 1     1 1 9 my $self = shift;
176 1         3 my ( $ptr, $size );
177              
178 1 50       66 _get_raw_fingerprint( $self->cp, \$ptr, \$size )
179             or croak('Unable to get raw fingerprint (get_raw_fingerprint)');
180              
181             # not espeically fast, but need a cast with a variable length array
182 1         17 my $fp = FFI::Platypus->new->cast( 'opaque' => "uint32[$size]", $ptr );
183 1         25574 _dealloc($ptr);
184 1         35 return $fp;
185             }
186              
187             sub get_num_channels {
188 1     1 1 8 my $self = shift;
189 1         32 return _get_num_channels($self->cp);
190             }
191              
192             sub get_sample_rate {
193 1     1 1 4 my $self = shift;
194 1         37 return _get_sample_rate($self->cp);
195             }
196              
197             sub get_item_duration {
198 0     0 1 0 my $self = shift;
199 0         0 return _get_item_duration($self->cp);
200             }
201              
202             sub get_item_duration_ms {
203 0     0 1 0 my $self = shift;
204 0         0 return _get_item_duration_ms($self->cp);
205             }
206              
207             sub get_delay {
208 0     0 1 0 my $self = shift;
209 0         0 return _get_delay($self->cp);
210             }
211              
212             sub get_delay_ms {
213 0     0 1 0 my $self = shift;
214 0         0 return _get_delay_ms($self->cp);
215             }
216              
217             sub get_raw_fingerprint_size {
218 0     0 1 0 my $self = shift;
219 0         0 my $size;
220 0 0       0 _get_raw_fingerprint_size($self->cp, \$size)
221             or croak('Unable to get raw fingerprint size (get_raw_fingerprint_size)');
222 0         0 return $size;
223             }
224              
225             sub clear_fingerprint {
226 0     0 1 0 my $self = shift;
227 0 0       0 _clear_fingerprint( $self->cp )
228             or croak('Unable to clear fingerprint (clear_fingerprint)');
229             }
230              
231             sub feed {
232 261     261 1 3088 my ( $self, $data ) = @_;
233 261 50       7008 _feed( $self->cp, $data, length($data) / BYTES_PER_SAMPLE() )
234             or corak("unable to feed");
235             }
236              
237             sub DEMOLISH {
238 5     5 0 15 my $self = shift;
239 5         133 _free( $self->cp );
240             }
241              
242             # TODO: chromaprint_encode_fingerprint
243             # TODO: chromaprint_decode_fingerprint
244             # TODO: chromaprint_hash_fingerprint
245              
246 2     2   31 no Moose;
  2         7  
  2         15  
247             __PACKAGE__->meta->make_immutable;
248              
249             1;
250              
251             __END__
252              
253             =pod
254              
255             =encoding UTF-8
256              
257             =head1 NAME
258              
259             Audio::Chromaprint - Interface to the Chromaprint library
260              
261             =head1 VERSION
262              
263             version 0.002
264              
265             =head1 SYNOPSIS
266              
267             use Audio::Chromaprint;
268             use Path::Tiny qw< path >;
269              
270             my $cp = Audio::Chromaprint->new();
271              
272             $cp->start( 44_100, 1 ); # sample rate (Hz), 1 audio stream
273             $cp->feed( path('file.wav')->slurp_raw );
274             $cp->finish;
275              
276             say "Fingerprint hash: ", $cp->get_fingerprint_hash;
277              
278             =head1 DESCRIPTION
279              
280             Chromaprint is the core component of the AcoustID project. It's a
281             client-side library that implements a custom algorithm for extracting
282             fingerprints from any audio source.
283              
284             You can read more about Chromaprint on its
285             L<website|https://acoustid.org/chromaprint>.
286              
287             This binding was done against 1.4.3. While it should work for newer versions,
288             please let us know if you are experiencing issues with newer versions.
289              
290             =head1 ATTRIBUTES
291              
292             =head2 algorithm
293              
294             Integer representing the Chromaprint algorithm.
295              
296             Acceptable values:
297              
298             =over 4
299              
300             =item * B<1>
301              
302             =item * B<2>
303              
304             =item * B<3>
305              
306             =item * B<4>
307              
308             =back
309              
310             The default is B<2>. (This is the default in Chromaprint.)
311              
312             =head2 silence_threshold
313              
314             An integer representing the silence threshold.
315              
316             Accepting a number between B<0> and B<32,767> (without a comma).
317              
318             =head1 METHODS
319              
320             =head2 new
321              
322             my $chromaprint = Audio::Chromaprint->new(
323             'algorithm' => 1, # optional, default is 2
324             'silence_threshold' => 1_000, # optional,
325             );
326              
327             =head2 start
328              
329             $chromaprint->start( $sample_rate, $num_streams );
330              
331             Start the computation of a fingerprint with a new audio stream.
332              
333             First argument is the sample rate (in integer) of the audio stream (in Hz).
334              
335             Second argument is number of channels in the audio stream (1 or 2).
336              
337             =head2 set_option
338              
339             $chromaprint->set_option( $key => $value );
340              
341             Setting an option to Chromaprint.
342              
343             In version 1.4.3 only the C<silence_threshold> is available, which we
344             also expose during instantiation under C<new>.
345              
346             =head2 get_version
347              
348             my $version = $chromaprint->get_version();
349              
350             Returns a string representing the version.
351              
352             =head2 feed
353              
354             $chromaprint->feed($data);
355              
356             Feed data to Chromaprint to analyze. The size definitions are handled
357             in the module, so you only send the data, no need for more.
358              
359             You can use L<Path::Tiny> to do this easily using the C<slurp_raw>:
360              
361             use Path::Tiny qw< path >;
362             my $file = path('some_file.wav');
363             my $data = $file->slurp_raw();
364              
365             $chromaprint->feed($data);
366              
367             =head2 finish
368              
369             $chromaprint->finish();
370              
371             Process any remaining buffered audio data.
372              
373             This has to be run before you can get the fingerprints.
374              
375             =head2 get_fingerprint
376              
377             my $fingerprint = $chromaprint->get_fingerprint();
378              
379             Provides a compressed string representing the fingerprint of the file.
380             You might prefer using C<get_fingerprint_hash>.
381              
382             =head2 get_fingerprint_hash
383              
384             my $fingerprint_hash = $chromaprint->get_fingerprint_hash();
385              
386             Provides a hash string, representing the fingerprint for the file.
387              
388             =head2 get_raw_fingerprint
389              
390             my $raw_fingerprint = $chromaprint->get_raw_fingerprint();
391              
392             Return the calculated fingerprint as an array of 32-bit integers.
393              
394             =head2 get_raw_fingerprint_size
395              
396             my $fingerprint_size = $chromaprint->get_fingerprint_size();
397              
398             Return the length of the current raw fingerprint.
399              
400             =head2 clear_fingerprint
401              
402             $chromaprint->clear_fingerprint();
403              
404             Clear the current fingerprint, but allow more data to be processed.
405              
406             =head2 get_num_channels
407              
408             my $num_of_channels = $chromaprint->get_num_channels();
409              
410             Get the number of channels that is internally used for fingerprinting.
411              
412             =head2 get_sample_rate
413              
414             my $sample_rate = $chromaprint->get_sample_rate();
415              
416             Get the sampling rate that is internally used for fingerprinting.
417              
418             =head2 get_item_duration
419              
420             my $item_duration = $chromaprint->get_item_duration();
421              
422             Get the duration of one item in the raw fingerprint in samples.
423              
424             =head2 get_item_duration_ms
425              
426             my $item_duration_ms = $chromaprint->get_item_duration_ms();
427              
428             Get the duration of one item in the raw fingerprint in milliseconds.
429              
430             =head2 get_delay
431              
432             my $delay = $chromaprint->get_delay();
433              
434             Get the duration of internal buffers that the fingerprinting algorithm uses.
435              
436             =head2 get_delay_ms
437              
438             my $delay_ms = $chromaprint->get_delay_ms();
439              
440             Get the duration of internal buffers that the fingerprinting algorithm uses.
441              
442             =head1 UNSUPPORTED METHODS
443              
444             We do not yet support the following methods.
445              
446             =over 4
447              
448             =item * C<encode_fingerprint>
449              
450             =item * C<decode_fingerprint>
451              
452             =item * C<hash_fingerprint>
453              
454             =back
455              
456             =head1 AUTHORS
457              
458             =over 4
459              
460             =item *
461              
462             Sawyer X <xsawyerx@cpan.org>
463              
464             =item *
465              
466             Graham Ollis <plicease@cpan.org>
467              
468             =back
469              
470             =head1 COPYRIGHT AND LICENSE
471              
472             This software is Copyright (c) 2019 by Sawyer X.
473              
474             This is free software, licensed under:
475              
476             The MIT (X11) License
477              
478             =cut