File Coverage

Scan.xs
Criterion Covered Total %
statement 98 106 92.4
branch 63 84 75.0
condition n/a
subroutine n/a
pod n/a
total 161 190 84.7


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4              
5             #include "ppport.h"
6              
7             // If we are on MSVC, disable some stupid MSVC warnings
8             #ifdef _MSC_VER
9             # pragma warning( disable: 4996 )
10             # pragma warning( disable: 4127 )
11             # pragma warning( disable: 4711 )
12             #endif
13              
14             // Headers for stat support
15             #ifdef _MSC_VER
16             # include
17             #else
18             # include
19             #endif
20              
21             #include "common.c"
22             #include "ape.c"
23             #include "id3.c"
24              
25             #include "aac.c"
26             #include "asf.c"
27             #include "mac.c"
28             #include "mp3.c"
29             #include "mp4.c"
30             #include "mpc.c"
31             #include "ogg.c"
32             #include "opus.c"
33             #include "wav.c"
34             #include "flac.c"
35             #include "wavpack.c"
36             #include "dsf.c"
37             #include "dsdiff.c"
38              
39             #include "md5.c"
40             #include "jenkins_hash.c"
41              
42             #define FILTER_TYPE_INFO 0x01
43             #define FILTER_TYPE_TAGS 0x02
44              
45             #define MD5_BUFFER_SIZE 4096
46              
47             #define MAX_PATH_STR_LEN 1024
48              
49             struct _types {
50             char *type;
51             char *suffix[15];
52             };
53              
54             typedef struct {
55             char* type;
56             int (*get_tags)(PerlIO *infile, char *file, HV *info, HV *tags);
57             int (*get_fileinfo)(PerlIO *infile, char *file, HV *tags);
58             int (*find_frame)(PerlIO *infile, char *file, int offset);
59             int (*find_frame_return_info)(PerlIO *infile, char *file, int offset, HV *info);
60             } taghandler;
61              
62             struct _types audio_types[] = {
63             {"mp4", {"mp4", "m4a", "m4b", "m4p", "m4v", "m4r", "k3g", "skm", "3gp", "3g2", "mov", 0}},
64             {"aac", {"aac", "adts", 0}},
65             {"mp3", {"mp3", "mp2", 0}},
66             {"ogg", {"ogg", "oga", 0}},
67             {"opus", {"opus", 0}},
68             {"mpc", {"mpc", "mp+", "mpp", 0}},
69             {"ape", {"ape", "apl", 0}},
70             {"flc", {"flc", "flac", "fla", 0}},
71             {"asf", {"wma", "asf", "wmv", 0}},
72             {"wav", {"wav", "aif", "aiff", 0}},
73             {"wvp", {"wv", 0}},
74             {"dsf", {"dsf", 0}},
75             {"dff", {"dff", 0}},
76             {0, {0, 0}}
77             };
78              
79             static taghandler taghandlers[] = {
80             { "mp4", get_mp4tags, 0, mp4_find_frame, mp4_find_frame_return_info },
81             { "aac", get_aacinfo, 0, 0, 0 },
82             { "mp3", get_mp3tags, get_mp3fileinfo, mp3_find_frame, 0 },
83             { "ogg", get_ogg_metadata, 0, ogg_find_frame, 0 },
84             { "opus", get_opus_metadata, 0, opus_find_frame, 0 },
85             { "mpc", get_ape_metadata, get_mpcfileinfo, 0, 0 },
86             { "ape", get_ape_metadata, get_macfileinfo, 0, 0 },
87             { "flc", get_flac_metadata, 0, flac_find_frame, 0 },
88             { "asf", get_asf_metadata, 0, asf_find_frame, 0 },
89             { "wav", get_wav_metadata, 0, 0, 0 },
90             { "wvp", get_ape_metadata, get_wavpack_info, 0 },
91             { "dsf", get_dsf_metadata, 0, 0, 0 },
92             { "dff", get_dsdiff_metadata, 0, 0, 0 },
93             { NULL, 0, 0, 0 }
94             };
95              
96             static taghandler *
97 226           _get_taghandler(char *suffix)
98             {
99 226           int typeindex = -1;
100             int i, j;
101 226           taghandler *hdl = NULL;
102            
103 1353 100         for (i=0; typeindex==-1 && audio_types[i].type; i++) {
    100          
104 4933 50         for (j=0; typeindex==-1 && audio_types[i].suffix[j]; j++) {
    100          
105             #ifdef _MSC_VER
106             if (!stricmp(audio_types[i].suffix[j], suffix)) {
107             #else
108 4031 100         if (!strcasecmp(audio_types[i].suffix[j], suffix)) {
109             #endif
110 225           typeindex = i;
111 225           break;
112             }
113             }
114             }
115            
116 226 100         if (typeindex > -1) {
117 1114 50         for (hdl = taghandlers; hdl->type; ++hdl)
118 1114 100         if (!strcmp(hdl->type, audio_types[typeindex].type))
119 225           break;
120             }
121            
122 226           return hdl;
123             }
124              
125             static void
126 18           _generate_md5(PerlIO *infile, const char *file, int size, int start_offset, HV *info)
127             {
128             md5_state_t md5;
129             md5_byte_t digest[16];
130             char hexdigest[33];
131             Buffer buf;
132             int audio_offset, audio_size, di;
133            
134 18           buffer_init(&buf, MD5_BUFFER_SIZE);
135 18           md5_init(&md5);
136            
137 18 50         audio_offset = SvIV(*(my_hv_fetch(info, "audio_offset")));
138 18 50         audio_size = SvIV(*(my_hv_fetch(info, "audio_size")));
139            
140 18 100         if (!start_offset) {
141             // Read bytes from middle of file to reduce chance of silence generating false matches
142 17           start_offset = audio_offset;
143 17           start_offset += (audio_size / 2) - (size / 2);
144 17 100         if (start_offset < audio_offset)
145 6           start_offset = audio_offset;
146             }
147            
148 18 100         if (size >= audio_size) {
149 7           size = audio_size;
150             }
151            
152             DEBUG_TRACE("Using %d bytes for audio MD5, starting at %d\n", size, start_offset);
153            
154 18 50         if (PerlIO_seek(infile, start_offset, SEEK_SET) < 0) {
155 0           warn("Audio::Scan unable to determine MD5 for %s\n", file);
156 0           goto out;
157             }
158            
159 50 100         while (size > 0) {
160 32 50         if ( !_check_buf(infile, &buf, 1, MIN(size, MD5_BUFFER_SIZE)) ) {
161 0           warn("Audio::Scan unable to determine MD5 for %s\n", file);
162 0           goto out;
163             }
164            
165 32           md5_append(&md5, buffer_ptr(&buf), buffer_len(&buf));
166            
167 32           size -= buffer_len(&buf);
168 32           buffer_consume(&buf, buffer_len(&buf));
169             DEBUG_TRACE("%d bytes left\n", size);
170             }
171            
172 18           md5_finish(&md5, digest);
173            
174 306 100         for (di = 0; di < 16; ++di)
175 288           sprintf(hexdigest + di * 2, "%02x", digest[di]);
176            
177 18           my_hv_store(info, "audio_md5", newSVpvn(hexdigest, 32));
178            
179             out:
180 18           buffer_free(&buf);
181 18           }
182              
183             static uint32_t
184 196           _generate_hash(const char *file)
185             {
186             char hashstr[MAX_PATH_STR_LEN];
187 196           int mtime = 0;
188 196           uint64_t size = 0;
189             uint32_t hash;
190              
191             #ifdef _MSC_VER
192             BOOL fOk;
193             WIN32_FILE_ATTRIBUTE_DATA fileInfo;
194              
195             fOk = GetFileAttributesEx(file, GetFileExInfoStandard, (void *)&fileInfo);
196             mtime = fileInfo.ftLastWriteTime.dwLowDateTime;
197             size = (uint64_t)fileInfo.nFileSizeLow;
198             #else
199             struct stat buf;
200              
201 196 100         if (stat(file, &buf) != -1) {
202 193           mtime = (int)buf.st_mtime;
203 193           size = (uint64_t)buf.st_size;
204             }
205             #endif
206              
207 196           memset(hashstr, 0, sizeof(hashstr));
208 196           snprintf(hashstr, sizeof(hashstr) - 1, "%s%d%llu", file, mtime, size);
209 196           hash = hashlittle(hashstr, strlen(hashstr), 0);
210            
211 196           return hash;
212             }
213              
214             MODULE = Audio::Scan PACKAGE = Audio::Scan
215              
216             HV *
217             _scan( char *, char *suffix, PerlIO *infile, SV *path, int filter, int md5_size, int md5_offset )
218             CODE:
219             {
220             taghandler *hdl;
221 196           RETVAL = newHV();
222            
223             // don't leak
224 196           sv_2mortal( (SV*)RETVAL );
225            
226 196           hdl = _get_taghandler(suffix);
227            
228 196 50         if (hdl) {
229 196           HV *info = newHV();
230              
231             // Ignore filter if a file type has only one function (FLAC/Ogg)
232 196 100         if ( !hdl->get_fileinfo ) {
233 83           filter = FILTER_TYPE_INFO | FILTER_TYPE_TAGS;
234             }
235              
236 196 100         if ( hdl->get_fileinfo && (filter & FILTER_TYPE_INFO) ) {
    100          
237 98           hdl->get_fileinfo(infile, SvPVX(path), info);
238             }
239              
240 196 50         if ( hdl->get_tags && (filter & FILTER_TYPE_TAGS) ) {
    100          
241 179           HV *tags = newHV();
242 179           hdl->get_tags(infile, SvPVX(path), info, tags);
243 179           hv_store( RETVAL, "tags", 4, newRV_noinc( (SV *)tags ), 0 );
244             }
245            
246             // Generate audio MD5 value
247 196 100         if ( md5_size > 0
248 19 50         && my_hv_exists(info, "audio_offset")
249 19 50         && my_hv_exists(info, "audio_size")
250 19 100         && !my_hv_exists(info, "audio_md5")
251             ) {
252 18           _generate_md5(infile, SvPVX(path), md5_size, md5_offset, info);
253             }
254            
255             // Generate hash value
256 196           my_hv_store(info, "jenkins_hash", newSVuv( _generate_hash(SvPVX(path)) ));
257              
258             // Info may be used in tag function, i.e. to find tag version
259 196           hv_store( RETVAL, "info", 4, newRV_noinc( (SV *)info ), 0 );
260             }
261             else {
262 0           croak("Audio::Scan unsupported file type: %s (%s)", suffix, SvPVX(path));
263             }
264             }
265             OUTPUT:
266             RETVAL
267            
268             int
269             _find_frame( char *, char *suffix, PerlIO *infile, SV *path, int offset )
270             CODE:
271             {
272             taghandler *hdl;
273            
274 23           RETVAL = -1;
275 23           hdl = _get_taghandler(suffix);
276            
277 23 50         if (hdl && hdl->find_frame) {
    50          
278 23           RETVAL = hdl->find_frame(infile, SvPVX(path), offset);
279             }
280             }
281             OUTPUT:
282             RETVAL
283              
284             HV *
285             _find_frame_return_info( char *, char *suffix, PerlIO *infile, SV *path, int offset )
286             CODE:
287             {
288 4           taghandler *hdl = _get_taghandler(suffix);
289 4           RETVAL = newHV();
290 4           sv_2mortal((SV*)RETVAL);
291            
292 4 50         if (hdl && hdl->find_frame_return_info) {
    50          
293 4           hdl->find_frame_return_info(infile, SvPVX(path), offset, RETVAL);
294             }
295             }
296             OUTPUT:
297             RETVAL
298              
299             int
300             has_flac(void)
301             CODE:
302             {
303 0           RETVAL = 1;
304             }
305             OUTPUT:
306             RETVAL
307              
308             int
309             is_supported(char *, SV *path)
310             CODE:
311             {
312 2           char *suffix = strrchr( SvPVX(path), '.' );
313              
314 2 50         if (suffix != NULL && *suffix == '.' && _get_taghandler(suffix + 1)) {
    50          
    100          
315 1           RETVAL = 1;
316             }
317             else {
318 1           RETVAL = 0;
319             }
320             }
321             OUTPUT:
322             RETVAL
323              
324             SV *
325             type_for(char *, SV *suffix)
326             CODE:
327             {
328 1           taghandler *hdl = NULL;
329 1           char *suff = SvPVX(suffix);
330              
331 1 50         if (suff == NULL || *suff == '\0') {
    50          
332 0           RETVAL = newSV(0);
333             }
334             else {
335 1           hdl = _get_taghandler(suff);
336 1 50         if (hdl == NULL) {
337 0           RETVAL = newSV(0);
338             }
339             else {
340 1           RETVAL = newSVpv(hdl->type, 0);
341             }
342             }
343             }
344             OUTPUT:
345             RETVAL
346              
347             AV *
348             get_types(void)
349             CODE:
350             {
351             int i;
352              
353 1           RETVAL = newAV();
354 1           sv_2mortal((SV*)RETVAL);
355 14 100         for (i = 0; audio_types[i].type; i++) {
356 13           av_push(RETVAL, newSVpv(audio_types[i].type, 0));
357             }
358             }
359             OUTPUT:
360             RETVAL
361              
362             AV *
363             extensions_for(char *, SV *type)
364             CODE:
365             {
366             int i, j;
367 1           char *t = SvPVX(type);
368              
369 1           RETVAL = newAV();
370 1           sv_2mortal((SV*)RETVAL);
371 1 50         for (i = 0; audio_types[i].type; i++) {
372             #ifdef _MSC_VER
373             if (!stricmp(audio_types[i].type, t)) {
374             #else
375 1 50         if (!strcasecmp(audio_types[i].type, t)) {
376             #endif
377              
378 12 100         for (j = 0; audio_types[i].suffix[j]; j++) {
379 11           av_push(RETVAL, newSVpv(audio_types[i].suffix[j], 0));
380             }
381 1           break;
382              
383             }
384             }
385             }
386             OUTPUT:
387             RETVAL