#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" // If we are on MSVC, disable some stupid MSVC warnings #ifdef _MSC_VER # pragma warning( disable: 4996 ) # pragma warning( disable: 4127 ) # pragma warning( disable: 4711 ) #endif // Headers for stat support #ifdef _MSC_VER # include #else # include #endif #include "common.c" #include "ape.c" #include "id3.c" #include "aac.c" #include "asf.c" #include "mac.c" #include "mp3.c" #include "mp4.c" #include "mpc.c" #include "ogg.c" #include "opus.c" #include "wav.c" #include "flac.c" #include "wavpack.c" #include "dsf.c" #include "dsdiff.c" #include "md5.c" #include "jenkins_hash.c" #define FILTER_TYPE_INFO 0x01 #define FILTER_TYPE_TAGS 0x02 #define MD5_BUFFER_SIZE 4096 #define MAX_PATH_STR_LEN 1024 struct _types { char *type; char *suffix[15]; }; typedef struct { char* type; int (*get_tags)(PerlIO *infile, char *file, HV *info, HV *tags); int (*get_fileinfo)(PerlIO *infile, char *file, HV *tags); int (*find_frame)(PerlIO *infile, char *file, int offset); int (*find_frame_return_info)(PerlIO *infile, char *file, int offset, HV *info); } taghandler; struct _types audio_types[] = { {"mp4", {"mp4", "m4a", "m4b", "m4p", "m4v", "m4r", "k3g", "skm", "3gp", "3g2", "mov", 0}}, {"aac", {"aac", "adts", 0}}, {"mp3", {"mp3", "mp2", 0}}, {"ogg", {"ogg", "oga", 0}}, {"opus", {"opus", 0}}, {"mpc", {"mpc", "mp+", "mpp", 0}}, {"ape", {"ape", "apl", 0}}, {"flc", {"flc", "flac", "fla", 0}}, {"asf", {"wma", "asf", "wmv", 0}}, {"wav", {"wav", "aif", "aiff", 0}}, {"wvp", {"wv", 0}}, {"dsf", {"dsf", 0}}, {"dff", {"dff", 0}}, {0, {0, 0}} }; static taghandler taghandlers[] = { { "mp4", get_mp4tags, 0, mp4_find_frame, mp4_find_frame_return_info }, { "aac", get_aacinfo, 0, 0, 0 }, { "mp3", get_mp3tags, get_mp3fileinfo, mp3_find_frame, 0 }, { "ogg", get_ogg_metadata, 0, ogg_find_frame, 0 }, { "opus", get_opus_metadata, 0, opus_find_frame, 0 }, { "mpc", get_ape_metadata, get_mpcfileinfo, 0, 0 }, { "ape", get_ape_metadata, get_macfileinfo, 0, 0 }, { "flc", get_flac_metadata, 0, flac_find_frame, 0 }, { "asf", get_asf_metadata, 0, asf_find_frame, 0 }, { "wav", get_wav_metadata, 0, 0, 0 }, { "wvp", get_ape_metadata, get_wavpack_info, 0 }, { "dsf", get_dsf_metadata, 0, 0, 0 }, { "dff", get_dsdiff_metadata, 0, 0, 0 }, { NULL, 0, 0, 0 } }; static taghandler * _get_taghandler(char *suffix) { int typeindex = -1; int i, j; taghandler *hdl = NULL; for (i=0; typeindex==-1 && audio_types[i].type; i++) { for (j=0; typeindex==-1 && audio_types[i].suffix[j]; j++) { #ifdef _MSC_VER if (!stricmp(audio_types[i].suffix[j], suffix)) { #else if (!strcasecmp(audio_types[i].suffix[j], suffix)) { #endif typeindex = i; break; } } } if (typeindex > -1) { for (hdl = taghandlers; hdl->type; ++hdl) if (!strcmp(hdl->type, audio_types[typeindex].type)) break; } return hdl; } static void _generate_md5(PerlIO *infile, const char *file, int size, int start_offset, HV *info) { md5_state_t md5; md5_byte_t digest[16]; char hexdigest[33]; Buffer buf; int audio_offset, audio_size, di; buffer_init(&buf, MD5_BUFFER_SIZE); md5_init(&md5); audio_offset = SvIV(*(my_hv_fetch(info, "audio_offset"))); audio_size = SvIV(*(my_hv_fetch(info, "audio_size"))); if (!start_offset) { // Read bytes from middle of file to reduce chance of silence generating false matches start_offset = audio_offset; start_offset += (audio_size / 2) - (size / 2); if (start_offset < audio_offset) start_offset = audio_offset; } if (size >= audio_size) { size = audio_size; } DEBUG_TRACE("Using %d bytes for audio MD5, starting at %d\n", size, start_offset); if (PerlIO_seek(infile, start_offset, SEEK_SET) < 0) { warn("Audio::Scan unable to determine MD5 for %s\n", file); goto out; } while (size > 0) { if ( !_check_buf(infile, &buf, 1, MIN(size, MD5_BUFFER_SIZE)) ) { warn("Audio::Scan unable to determine MD5 for %s\n", file); goto out; } md5_append(&md5, buffer_ptr(&buf), buffer_len(&buf)); size -= buffer_len(&buf); buffer_consume(&buf, buffer_len(&buf)); DEBUG_TRACE("%d bytes left\n", size); } md5_finish(&md5, digest); for (di = 0; di < 16; ++di) sprintf(hexdigest + di * 2, "%02x", digest[di]); my_hv_store(info, "audio_md5", newSVpvn(hexdigest, 32)); out: buffer_free(&buf); } static uint32_t _generate_hash(const char *file) { char hashstr[MAX_PATH_STR_LEN]; int mtime = 0; uint64_t size = 0; uint32_t hash; #ifdef _MSC_VER BOOL fOk; WIN32_FILE_ATTRIBUTE_DATA fileInfo; fOk = GetFileAttributesEx(file, GetFileExInfoStandard, (void *)&fileInfo); mtime = fileInfo.ftLastWriteTime.dwLowDateTime; size = (uint64_t)fileInfo.nFileSizeLow; #else struct stat buf; if (stat(file, &buf) != -1) { mtime = (int)buf.st_mtime; size = (uint64_t)buf.st_size; } #endif memset(hashstr, 0, sizeof(hashstr)); snprintf(hashstr, sizeof(hashstr) - 1, "%s%d%llu", file, mtime, size); hash = hashlittle(hashstr, strlen(hashstr), 0); return hash; } MODULE = Audio::Scan PACKAGE = Audio::Scan HV * _scan( char *, char *suffix, PerlIO *infile, SV *path, int filter, int md5_size, int md5_offset ) CODE: { taghandler *hdl; RETVAL = newHV(); // don't leak sv_2mortal( (SV*)RETVAL ); hdl = _get_taghandler(suffix); if (hdl) { HV *info = newHV(); // Ignore filter if a file type has only one function (FLAC/Ogg) if ( !hdl->get_fileinfo ) { filter = FILTER_TYPE_INFO | FILTER_TYPE_TAGS; } if ( hdl->get_fileinfo && (filter & FILTER_TYPE_INFO) ) { hdl->get_fileinfo(infile, SvPVX(path), info); } if ( hdl->get_tags && (filter & FILTER_TYPE_TAGS) ) { HV *tags = newHV(); hdl->get_tags(infile, SvPVX(path), info, tags); hv_store( RETVAL, "tags", 4, newRV_noinc( (SV *)tags ), 0 ); } // Generate audio MD5 value if ( md5_size > 0 && my_hv_exists(info, "audio_offset") && my_hv_exists(info, "audio_size") && !my_hv_exists(info, "audio_md5") ) { _generate_md5(infile, SvPVX(path), md5_size, md5_offset, info); } // Generate hash value my_hv_store(info, "jenkins_hash", newSVuv( _generate_hash(SvPVX(path)) )); // Info may be used in tag function, i.e. to find tag version hv_store( RETVAL, "info", 4, newRV_noinc( (SV *)info ), 0 ); } else { croak("Audio::Scan unsupported file type: %s (%s)", suffix, SvPVX(path)); } } OUTPUT: RETVAL int _find_frame( char *, char *suffix, PerlIO *infile, SV *path, int offset ) CODE: { taghandler *hdl; RETVAL = -1; hdl = _get_taghandler(suffix); if (hdl && hdl->find_frame) { RETVAL = hdl->find_frame(infile, SvPVX(path), offset); } } OUTPUT: RETVAL HV * _find_frame_return_info( char *, char *suffix, PerlIO *infile, SV *path, int offset ) CODE: { taghandler *hdl = _get_taghandler(suffix); RETVAL = newHV(); sv_2mortal((SV*)RETVAL); if (hdl && hdl->find_frame_return_info) { hdl->find_frame_return_info(infile, SvPVX(path), offset, RETVAL); } } OUTPUT: RETVAL int has_flac(void) CODE: { RETVAL = 1; } OUTPUT: RETVAL int is_supported(char *, SV *path) CODE: { char *suffix = strrchr( SvPVX(path), '.' ); if (suffix != NULL && *suffix == '.' && _get_taghandler(suffix + 1)) { RETVAL = 1; } else { RETVAL = 0; } } OUTPUT: RETVAL SV * type_for(char *, SV *suffix) CODE: { taghandler *hdl = NULL; char *suff = SvPVX(suffix); if (suff == NULL || *suff == '\0') { RETVAL = newSV(0); } else { hdl = _get_taghandler(suff); if (hdl == NULL) { RETVAL = newSV(0); } else { RETVAL = newSVpv(hdl->type, 0); } } } OUTPUT: RETVAL AV * get_types(void) CODE: { int i; RETVAL = newAV(); sv_2mortal((SV*)RETVAL); for (i = 0; audio_types[i].type; i++) { av_push(RETVAL, newSVpv(audio_types[i].type, 0)); } } OUTPUT: RETVAL AV * extensions_for(char *, SV *type) CODE: { int i, j; char *t = SvPVX(type); RETVAL = newAV(); sv_2mortal((SV*)RETVAL); for (i = 0; audio_types[i].type; i++) { #ifdef _MSC_VER if (!stricmp(audio_types[i].type, t)) { #else if (!strcasecmp(audio_types[i].type, t)) { #endif for (j = 0; audio_types[i].suffix[j]; j++) { av_push(RETVAL, newSVpv(audio_types[i].suffix[j], 0)); } break; } } } OUTPUT: RETVAL