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 <windows.h>
17 #else
18 # include <sys/stat.h>
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 *
_get_taghandler(char * suffix)97 _get_taghandler(char *suffix)
98 {
99 int typeindex = -1;
100 int i, j;
101 taghandler *hdl = NULL;
102
103 for (i=0; typeindex==-1 && audio_types[i].type; i++) {
104 for (j=0; typeindex==-1 && audio_types[i].suffix[j]; j++) {
105 #ifdef _MSC_VER
106 if (!stricmp(audio_types[i].suffix[j], suffix)) {
107 #else
108 if (!strcasecmp(audio_types[i].suffix[j], suffix)) {
109 #endif
110 typeindex = i;
111 break;
112 }
113 }
114 }
115
116 if (typeindex > -1) {
117 for (hdl = taghandlers; hdl->type; ++hdl)
118 if (!strcmp(hdl->type, audio_types[typeindex].type))
119 break;
120 }
121
122 return hdl;
123 }
124
125 static void
126 _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 buffer_init(&buf, MD5_BUFFER_SIZE);
135 md5_init(&md5);
136
137 audio_offset = SvIV(*(my_hv_fetch(info, "audio_offset")));
138 audio_size = SvIV(*(my_hv_fetch(info, "audio_size")));
139
140 if (!start_offset) {
141 // Read bytes from middle of file to reduce chance of silence generating false matches
142 start_offset = audio_offset;
143 start_offset += (audio_size / 2) - (size / 2);
144 if (start_offset < audio_offset)
145 start_offset = audio_offset;
146 }
147
148 if (size >= audio_size) {
149 size = audio_size;
150 }
151
152 DEBUG_TRACE("Using %d bytes for audio MD5, starting at %d\n", size, start_offset);
153
154 if (PerlIO_seek(infile, start_offset, SEEK_SET) < 0) {
155 warn("Audio::Scan unable to determine MD5 for %s\n", file);
156 goto out;
157 }
158
159 while (size > 0) {
160 if ( !_check_buf(infile, &buf, 1, MIN(size, MD5_BUFFER_SIZE)) ) {
161 warn("Audio::Scan unable to determine MD5 for %s\n", file);
162 goto out;
163 }
164
165 md5_append(&md5, buffer_ptr(&buf), buffer_len(&buf));
166
167 size -= buffer_len(&buf);
168 buffer_consume(&buf, buffer_len(&buf));
169 DEBUG_TRACE("%d bytes left\n", size);
170 }
171
172 md5_finish(&md5, digest);
173
174 for (di = 0; di < 16; ++di)
175 sprintf(hexdigest + di * 2, "%02x", digest[di]);
176
177 my_hv_store(info, "audio_md5", newSVpvn(hexdigest, 32));
178
179 out:
180 buffer_free(&buf);
181 }
182
183 static uint32_t
184 _generate_hash(const char *file)
185 {
186 char hashstr[MAX_PATH_STR_LEN];
187 int mtime = 0;
188 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 if (stat(file, &buf) != -1) {
202 mtime = (int)buf.st_mtime;
203 size = (uint64_t)buf.st_size;
204 }
205 #endif
206
207 memset(hashstr, 0, sizeof(hashstr));
208 snprintf(hashstr, sizeof(hashstr) - 1, "%s%d%llu", file, mtime, size);
209 hash = hashlittle(hashstr, strlen(hashstr), 0);
210
211 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 RETVAL = newHV();
222
223 // don't leak
224 sv_2mortal( (SV*)RETVAL );
225
226 hdl = _get_taghandler(suffix);
227
228 if (hdl) {
229 HV *info = newHV();
230
231 // Ignore filter if a file type has only one function (FLAC/Ogg)
232 if ( !hdl->get_fileinfo ) {
233 filter = FILTER_TYPE_INFO | FILTER_TYPE_TAGS;
234 }
235
236 if ( hdl->get_fileinfo && (filter & FILTER_TYPE_INFO) ) {
237 hdl->get_fileinfo(infile, SvPVX(path), info);
238 }
239
240 if ( hdl->get_tags && (filter & FILTER_TYPE_TAGS) ) {
241 HV *tags = newHV();
242 hdl->get_tags(infile, SvPVX(path), info, tags);
243 hv_store( RETVAL, "tags", 4, newRV_noinc( (SV *)tags ), 0 );
244 }
245
246 // Generate audio MD5 value
247 if ( md5_size > 0
248 && my_hv_exists(info, "audio_offset")
249 && my_hv_exists(info, "audio_size")
250 && !my_hv_exists(info, "audio_md5")
251 ) {
252 _generate_md5(infile, SvPVX(path), md5_size, md5_offset, info);
253 }
254
255 // Generate hash value
256 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 hv_store( RETVAL, "info", 4, newRV_noinc( (SV *)info ), 0 );
260 }
261 else {
262 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 RETVAL = -1;
275 hdl = _get_taghandler(suffix);
276
277 if (hdl && hdl->find_frame) {
278 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 taghandler *hdl = _get_taghandler(suffix);
289 RETVAL = newHV();
290 sv_2mortal((SV*)RETVAL);
291
292 if (hdl && hdl->find_frame_return_info) {
293 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 RETVAL = 1;
304 }
305 OUTPUT:
306 RETVAL
307
308 int
309 is_supported(char *, SV *path)
310 CODE:
311 {
312 char *suffix = strrchr( SvPVX(path), '.' );
313
314 if (suffix != NULL && *suffix == '.' && _get_taghandler(suffix + 1)) {
315 RETVAL = 1;
316 }
317 else {
318 RETVAL = 0;
319 }
320 }
321 OUTPUT:
322 RETVAL
323
324 SV *
325 type_for(char *, SV *suffix)
326 CODE:
327 {
328 taghandler *hdl = NULL;
329 char *suff = SvPVX(suffix);
330
331 if (suff == NULL || *suff == '\0') {
332 RETVAL = newSV(0);
333 }
334 else {
335 hdl = _get_taghandler(suff);
336 if (hdl == NULL) {
337 RETVAL = newSV(0);
338 }
339 else {
340 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 RETVAL = newAV();
354 sv_2mortal((SV*)RETVAL);
355 for (i = 0; audio_types[i].type; i++) {
356 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 char *t = SvPVX(type);
368
369 RETVAL = newAV();
370 sv_2mortal((SV*)RETVAL);
371 for (i = 0; audio_types[i].type; i++) {
372 #ifdef _MSC_VER
373 if (!stricmp(audio_types[i].type, t)) {
374 #else
375 if (!strcasecmp(audio_types[i].type, t)) {
376 #endif
377
378 for (j = 0; audio_types[i].suffix[j]; j++) {
379 av_push(RETVAL, newSVpv(audio_types[i].suffix[j], 0));
380 }
381 break;
382
383 }
384 }
385 }
386 OUTPUT:
387 RETVAL
388