File Coverage

Scan.xs
Criterion Covered Total %
statement 99 107 92.5
branch 61 82 74.3
condition n/a
subroutine n/a
pod n/a
total 160 189 84.6


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