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