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