/* * Copyright (C) 2010, Florent Viard, LaCie * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tracker-dbus.h" #define DATA_TO_UINT32(x) (((unsigned char)((x)[0]) << 24) | \ ((unsigned char)((x)[1]) << 16) | \ ((unsigned char)((x)[2]) << 8) | \ ((unsigned char)((x)[3]) << 0) ) #define DATA_TO_UINT16(x) (((unsigned char)((x)[0]) << 8) | \ ((unsigned char)((x)[1]) << 0) ) #define DATA_TO_UINT8(x) (((unsigned char)((x)[0]) << 0) ) static void extract_mp4 (const gchar *uri, TrackerSparqlBuilder *preupdate, TrackerSparqlBuilder *metadata); static TrackerExtractData extract_data[] = { /* TODO: Insert mime types and functions here. */ { "audio/mp4", extract_mp4 }, { "audio/x-m4a", extract_mp4 }, { "video/mp4", extract_mp4 }, { "video/x-m4v", extract_mp4 }, { NULL, NULL } }; enum e_ftype{ TYPE_UNKNOWN, TYPE_MP4, TYPE_M4A, TYPE_M4B, TYPE_M4P, TYPE_M4R, TYPE_M4V, TYPE_3GP }; enum e_mediatype{ TYPE_OTHER, TYPE_UNDEFINED, TYPE_AUDIO, TYPE_VIDEO, }; enum e_Tagged_mediatype{ TYPE_OLD_MOVIE = 0, TYPE_MUSIC = 1, //Normal TYPE_AUDIOBOOK = 2, TYPE_MUSICVIDEO = 6, TYPE_MOVIE = 9, TYPE_TVSHOW = 10, TYPE_BOOKLET = 11, TYPE_RINGTONE = 14 }; enum e_contentrating_type{ TYPE_RATING_NONE = 0, TYPE_RATING_CLEAN = 2, TYPE_RATING_EXPLICIT = 4 }; enum e_countrycode{ COUNTRY_AUSTRALIA = 143460, COUNTRY_AUSTRIA = 143445, COUNTRY_BELGIUM = 143446, COUNTRY_CANADA = 143455, COUNTRY_DENMARK = 143458, COUNTRY_FINLAND = 143447, COUNTRY_FRANCE = 143442, COUNTRY_GERMANY = 143443, COUNTRY_GREECE = 143448, COUNTRY_IRELAND = 143449, COUNTRY_ITALY = 143450, COUNTRY_JAPAN = 143462, COUNTRY_LUXEMBOURG = 143451, COUNTRY_NETHERLANDS = 143452, COUNTRY_NEWZEALAND = 143461, COUNTRY_NORWAY = 143457, COUNTRY_PORTUGAL = 143453, COUNTRY_SPAIN = 143454, COUNTRY_SWEDEN = 143456, COUNTRY_SWITZERLAND = 143459, COUNTRY_UNITEDKINGDOM = 143444, COUNTRY_UNITEDSTATES = 143441 }; enum e_meta_versionflagclass{ FLAG_UINT8 = 0, FLAG_TEXT = 1, FLAG_JPG = 13, FLAG_PNG = 14, FLAG_UINT8_21 = 21, FLAG_CUSTOM_GENRE = 100 }; typedef struct { /* Data input */ enum e_ftype ftype; enum e_mediatype mediatype; /* Coalesced input */ gchar *title; gchar *album_performer; gchar *performer; gchar *performer_uri; gchar *album; gchar *album_uri; gchar *custom_genre; gchar *predefined_genre; gchar *category; gchar *text; gchar *recording_time; gchar *purchase_date; gchar *copyright; gchar *comment; gchar *description; gchar *long_description; gchar *composer; gchar *composer_uri; gchar *encoder; gchar *grouping; // Don't know the usage of this gchar *tv_show_name; gchar *tv_episode_id; gchar *tv_network_name; guint32 tv_season_num; guint32 tv_episode_num; guint32 mediatype_from_meta; // alias stik guint32 isPodcast; // bool guint32 isCompilation; // bool guint32 isHDvd; // bool guint32 isGapless; // bool guint32 disk_number; guint32 disk_count; guint32 track_number; guint32 track_count; guint32 timescale_bitrate; guint32 duration; guint32 bpm; guint32 country_code; guint32 rating; // MPAA RATING } t_MP4Data; static const char *const predefined_genre_names[] = { "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alt. Rock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta Rap", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Cappella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "Synthpop" }; static guint mp4_getTagLen(FILE *f, guint *size_32, guint *is_64, guchar *tag) { guchar csize_c[4]; int ret = 0; int inc = 0; // Size (N.B.: the size include the tag/type field and the size field) ret = fread(csize_c, 1, 4, f); //printf("getTag: %c %c %c %c\n", csize_c[0],csize_c[1],csize_c[2],csize_c[3]); if (ret != 4) { // Error or feof return 0; } *size_32 = (((unsigned char)(csize_c[0]) << 24) | ((unsigned char)(csize_c[1]) << 16) | ((unsigned char)(csize_c[2]) << 8) | ((unsigned char)(csize_c[3]) << 0) ); inc += 4; if (*size_32 == 1) { // 64 bit value *is_64 = 1; ret = fread(&csize_c, 1, 4, f); if(ret != 4){ // Error or feof return 0; } *size_32 = (((unsigned char)(csize_c[0]) << 24) | ((unsigned char)(csize_c[1]) << 16) | ((unsigned char)(csize_c[2]) << 8) | ((unsigned char)(csize_c[3]) << 0) ); inc += 4; } else { *is_64 = 0; } // tag type ret = fread(tag, 1, 4, f); if (ret != 4){ // Error or feof return 0; } inc += 4; *size_32 -= inc; // Remove the tag and length bytes of the header to have the value size return inc; } static int mp4_getValue(FILE *f, guint csize, guchar *val) { guint inc = 0; int ret = 0; ret = fread(val, 1, csize, f); if (ret != csize) { // Error or feof return 0; } inc += csize; return inc; } static int tag_name_cmp(guchar *s1, guchar *s2) { // Return 1 if match, 0 otherwise int err = 0; if ((s1[0]==s2[0])&&(s1[1]==s2[1])&& (s1[2]==s2[2])&&(s1[3]==s2[3])) { err = 1; } return err; } static int mp4_parseftype(t_MP4Data *md, guchar *data) { int err = 0; // A comprehensive list of ftyps: http://www.ftyps.com if ( data[0] == 'M' && data[1] == '4' ) { switch( data[2]){ case 'A': md->ftype = TYPE_M4A; md->mediatype = TYPE_AUDIO; break; case 'B': md->ftype = TYPE_M4B; md->mediatype = TYPE_AUDIO; break; case 'P': md->ftype = TYPE_M4P; md->mediatype = TYPE_AUDIO; break; case 'R': md->ftype = TYPE_M4R; md->mediatype = TYPE_AUDIO; break; case 'V': md->ftype = TYPE_M4V; md->mediatype = TYPE_VIDEO; break; default: md->ftype = TYPE_UNKNOWN; md->mediatype = TYPE_OTHER; } } else if ( data[0] == '3' && data[1] == 'G' && data[2] == 'P' ) { md->ftype = TYPE_3GP; md->mediatype = TYPE_VIDEO; } else if ( data[0] == 'm' && data[1] == 'p' && data[2] == '4' ) { //MP41 - MP42 ... md->ftype = TYPE_MP4; md->mediatype = TYPE_VIDEO; } else if ( data[0] == 'i' && data[1] == 's' && data[2] == 'o' && data[3] == 'm' ) { md->ftype = TYPE_MP4; md->mediatype = TYPE_UNDEFINED; } else { md->ftype = TYPE_UNKNOWN; md->mediatype = TYPE_OTHER; } return err; } static int mp4_parseprocesstags(t_MP4Data *md, guchar *tag, guchar *data, guint size) { // err = 2 means tag unknown // Format is {Size(4b) Tag(4b)} Size(4b) "data"(4b) [version 1b + datatype/class 3b](4b) 0(4b) datas(...b) int err = 0; enum e_meta_versionflagclass tag_class; void *dest_field = NULL; void *dest_field2 = NULL; do { if ( size < 16 ) { // 4 + 4 + 4 +4 err = -1; break; } // Skip ---- atoms, currently non processed as useless in our case if (tag_name_cmp(tag, (guchar *) "----") == 1) { err = 2; break; } // Update size with the inner data size size = DATA_TO_UINT32(data); size -= 4; size -= 4; // Skip 4 0's bytes of "data" (+ 4) tag_class = DATA_TO_UINT32(data + 8); size -= 4; size -= 4; // Skip 4 0's bytes (+ 12) // Value offset: data + 16 switch (tag[0]) { case 0xA9: //(C) switch (tag[1]) { case 'n': if (tag[2]=='a' && tag[3]=='m') { // ©nam dest_field = &md->title; } else { err = 2; } break; case 'A': if (tag[2]=='R' && tag[3]=='T') { // ©ART - Album artist name dest_field = &md->album_performer; } else { err = 2; } break; case 'a': if (tag[2]=='l' && tag[3]=='b' ) { // ©alb dest_field = &md->album; } else { err = 2; } break; case 'w': if (tag[2]=='r' && tag[3]=='t') { // ©wrt dest_field = &md->composer; } else { err = 2; } break; case 'd': if (tag[2]=='a' && tag[3]=='y') { // ©day dest_field = &md->recording_time; } else { err = 2; } break; case 't': if (tag[2]=='o' && tag[3]=='o') { // ©too dest_field = &md->encoder; } else { err = 2; } break; case 'g': if (tag[2]=='e' && tag[3]=='n') { // ©gen dest_field = &md->custom_genre; } else if (tag[2]=='r' && tag[3]=='p') { // ©com dest_field = &md->grouping; } else { err = 2; } break; case 'c': if (tag[2]=='o' && tag[3]=='m') { // ©com dest_field = &md->comment; } else { err = 2; } break; default: err = 2; } break; case 't': switch (tag[1]) { case 'm': if (tag[2]=='p' && tag[3]=='o') { // tmpo dest_field = &md->bpm; } else { err = 2; } break; case 'r': if (tag[2]=='k' && tag[3]=='n') { // trkn dest_field = &md->track_number; dest_field2 = &md->track_count; } else { err = 2; } break; case 'v': switch (tag[2]) { case 'e': if (tag[3]=='n') { // tven dest_field = &md->tv_episode_id; } else if (tag[3]=='s') { // tves dest_field = &md->tv_episode_num; } else { err = 2; } break; case 's': if (tag[3]=='n') { // tvsn dest_field = &md->tv_season_num; } else if (tag[3]=='h') { // tvsh dest_field = &md->tv_show_name; } else { err = 2; } break; case 'n': if (tag[3]=='n') { // tvnn dest_field = &md->tv_network_name; } else { err = 2; } break; default: err = 2; } break; default: err = 2; } break; case 'c': switch (tag[1]) { case 'p': if (tag[2]=='i' && tag[3]=='l') { // cpil dest_field = &md->isCompilation; } else if (tag[2]=='r' && tag[3]=='t') { // cprt dest_field = &md->copyright; } else { err = 2; } break; case 'a': if (tag[2]=='t' && tag[3]=='g') { // catg dest_field = &md->category; } else { err = 2; } break; case 'o': if (tag[2]=='v' && tag[3]=='r') { // covr err = 2; // skip } else { err = 2; } break; default: err = 2; } break; case 'd': switch (tag[1]) { case 'i': if (tag[2]=='s' && tag[3]=='k') { // disk dest_field = &md->disk_number; dest_field2 = &md->disk_count; } else { err = 2; } break; case 'e': if (tag[2]=='s' && tag[3]=='c') { // desc dest_field = &md->description; } else { err = 2; } break; default: err = 2; } break; case 'l': if (tag[1]=='d' && tag[2]=='e' && tag[3]=='s' ) { // ldes : long description dest_field = &md->long_description; } else { err = 2; } break; case 'r': if (tag[1]=='t' && tag[2]=='n' && tag[3]=='g' ) { // rtng dest_field = &md->rating; } else { err = 2; } break; case 's': switch (tag[1]) { case 't': if (tag[2]=='i' && tag[3]=='k') { // stik dest_field = &md->mediatype_from_meta; } else { err = 2; } break; case 'f': if (tag[2]=='I' && tag[3]=='D') { // sfID dest_field = &md->country_code; } else { err = 2; } break; default: err = 2; } break; case 'a': switch (tag[1]) { case 'A': if (tag[2]=='R' && tag[3]=='T') { // aART dest_field = &md->performer; } else { err = 2; } break; default: err = 2; } break; case 'g': switch (tag[1]) { case 'n': if (tag[2]=='r' && tag[3]=='e') { // gnre // Predefined genre: Standard genres according to ID3, the iTunes tag is one greater // to the corresponding ID3 Tag // Manage it ourselve with the help of a custom class tag_class = FLAG_CUSTOM_GENRE; dest_field = &md->predefined_genre; } else { err = 2; } break; default: err = 2; } break; case 'p': switch (tag[1]) { case 'g': if (tag[2]=='a' && tag[3]=='p') { // pgap dest_field = &md->isGapless; } else { err = 2; } break; case 'u': if (tag[2]=='r' && tag[3]=='d') { // purd dest_field = &md->purchase_date; } else { err = 2; } break; case 'c': if (tag[2]=='s' && tag[3]=='t') { // pcst dest_field = &md->isPodcast; } else { err = 2; } break; default: err = 2; } break; default: err = 2; } if(err) break; if (dest_field != NULL) { if (tag_class == FLAG_TEXT) { *((char **)dest_field) = (void *) g_strndup((gchar *) data + 16, size); //printf(" Value: %s ... ", *((char **) dest_field)); } else if (tag_class == FLAG_UINT8 || tag_class == FLAG_UINT8_21) { switch (size) { // In bytes case 1: // 8bits *((guint32 *)dest_field) = DATA_TO_UINT8(data + 16); break; case 2: // 16bits if(dest_field2 != NULL) { *((guint32 *)dest_field) = DATA_TO_UINT8(data + 16); *((guint32 *)dest_field2) = DATA_TO_UINT8(data + 17); } else { if (DATA_TO_UINT16(data + 16) <= 255) { *((guint32 *)dest_field) = DATA_TO_UINT16(data + 16); } else { *((guint32 *)dest_field) = 0; } } break; case 4: // 32bits if (dest_field2 != NULL) { if (DATA_TO_UINT16(data + 16) <= 255) { *((guint32 *)dest_field) = DATA_TO_UINT16(data + 16); } else { *((guint32 *)dest_field) = 0; } if (DATA_TO_UINT16(data + 18) <= 255) { *((guint32 *)dest_field2) = DATA_TO_UINT16(data + 18); } else { *((guint32 *)dest_field2) = 0; } } else { *((guint32 *)dest_field) = DATA_TO_UINT32(data + 16); } break; case 6: // 48 bits if (dest_field2 != NULL) { if (DATA_TO_UINT32(data + 16) <= 255) { *((guint32 *)dest_field) = DATA_TO_UINT32(data + 16); } else { *((guint32 *)dest_field) = 0; } if (DATA_TO_UINT16(data + 20) <= 255) { *((guint32 *)dest_field2) = DATA_TO_UINT16(data + 20); } else { *((guint32 *)dest_field2) = 0; } } else { *((guint32 *)dest_field) = DATA_TO_UINT32(data + 16); } break; case 8: // 64bits if (dest_field2 != NULL) { if (DATA_TO_UINT32(data + 16) <= 255) { *((guint32 *)dest_field) = DATA_TO_UINT32(data + 16); } else { *((guint32 *)dest_field) = 0; } if (DATA_TO_UINT32(data + 20) <= 255) { *((guint32 *)dest_field2) = DATA_TO_UINT32(data + 20); } else { *((guint32 *)dest_field2) = 0; } } else { // Incorrectly support 64bits to avoid buffer overflow // by having an incorrect size in the meta tag // But except for plID that we don't support (useless) no other field is 64b //*((guint32 *)dest_field) = DATA_TO_UINT32(data + 16); err = 2; break; } break; default: err = 2; } /* Debug print printf(" Value: %d : size=%d :val=%d ", tag_class, size, *((guint32 *)dest_field)); if(dest_field2 != NULL){ //printf(":val2=%d ... ", *((guint32 *)dest_field2)); } */ } else if (tag_class == FLAG_CUSTOM_GENRE) { // Here the size is supposed to be 2bytes if ( G_UNLIKELY(size != 2) ) { err = 2; break; } if (DATA_TO_UINT16(data + 16) <= 255) { *((gchar **)dest_field) = g_strdup( (gchar *)predefined_genre_names[DATA_TO_UINT16(data + 16) - 1] ); //printf(" Value: %s ... ", *((char **) dest_field)); } else { err = 2; break; } } else { err = 2; // or do we have to consider it as an error and set err = -1 ? //printf("Warning - new type : %d!", tag_class); break; } } }while(0); return err; } static int parse_mp4(FILE *f, goffset size, t_MP4Data *md) { // Tag structure: SIZE(32) TAG(32) VALUE(...) int err = 0; guint csize; guint csize_is64; guchar tag_c[4]; guchar hdr_size; guchar *val; int offset; guint moov_size; guint pos_from_moov; guint meta_size; guint pos_from_meta; guint ilst_size; guint pos_from_ilst; int ret = 0; int found = 0; do { // Parsing ftyp TAG if( (hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) != 8 || tag_name_cmp(tag_c,(guchar *) "ftyp") != 1 ) { err = -1; break; } //printf("tag: (%d) %c%c%c%c\n", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); val = (guchar *) g_malloc( csize * sizeof(unsigned char) ); if (mp4_getValue(f, csize, val) != csize ) { err = -1; break; } //printf("value: \"%s\"\n", val); if (csize < 4) { err = -1; break; } mp4_parseftype(md, val); if (md->ftype == TYPE_UNKNOWN) { err = -1; break; } g_free(val); // Parsing moov tag if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8 || tag_name_cmp(tag_c,(guchar *) "moov") != 1 ) { err = -1; break; } moov_size = csize; pos_from_moov = 0; //printf("tag: (%d) %c%c%c%c\n", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); // Parsing mvhd (movie header tag) // Format: Tag(32) Size(32) version(8+24'0) if v=0: creation-date(32) mod-date(32) bitrate(32) duration(32) ... if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8 || tag_name_cmp(tag_c,(guchar *) "mvhd") != 1 ) { err = -1; break; } pos_from_moov += hdr_size; //printf("tag: (%d) %c%c%c%c\n", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); val = (guchar *) g_malloc( csize * sizeof(unsigned char) ); if (mp4_getValue(f, csize, val) != csize ) { err = -1; break; } pos_from_moov += csize; if (val[0] == 0 ) { // Version == 0 offset = 12; // Skip version, crea and mod dates. md->timescale_bitrate = DATA_TO_UINT32( val + offset ); if (md->timescale_bitrate) { md->duration = ( DATA_TO_UINT32( val + offset + 4) / md->timescale_bitrate ); } } else { // If version == 1, (not fully supported as we store the datas as int32 but should not occur in theory) offset = 20; // Skip version, crea and mod dates.(4 + 8 + 8) offset += 4; // Just look at the 32bits value part of the 64bits value md->timescale_bitrate = DATA_TO_UINT32( val + offset ); if (md->timescale_bitrate) { md->duration = ( DATA_TO_UINT32( val + offset + 4) / md->timescale_bitrate ); } } //printf("Duration: %ds\n", md->duration); g_free(val); // Skip every atom until udta ret = 0; found = 0; do{ if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8) { ret = 2; break; } pos_from_moov += hdr_size; //printf("tag: (%d) %c%c%c%c ...", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); if ( tag_name_cmp(tag_c,(guchar *) "udta") != 1 ) { ret = fseek(f, csize, SEEK_CUR); // ret == -1 if fseek failed. (eof or error) if(ret == -1){ break; } pos_from_moov += csize; //printf("Skipped! \n"); } else { found = 1; //printf("Located!\n"); } if (pos_from_moov > moov_size) { ret = 2; } } while ( (found == 0)&&(ret == 0) ); if ( (ret != 0)||(found == 0) ) { err = -1; break; } // Parsing meta (parent: moov->udta) tag // Format: Size(32) Tag(32) version(8+24'0) then contained atoms. if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8 || tag_name_cmp(tag_c,(guchar *) "meta") != 1) { err = -1; break; } pos_from_moov += hdr_size; if (pos_from_moov + csize > moov_size) { err = -1; break; } meta_size = csize; pos_from_meta = 0; //printf("tag: (%d) %c%c%c%c\n", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); // skip (version) : 4 0's ret = fseek(f, 4, SEEK_CUR); if (ret == -1) { err = -1; break; } pos_from_meta += 4; //printf("Meta begining... \n"); // skip hdlr if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8 || tag_name_cmp(tag_c,(guchar *) "hdlr") != 1) { err = -1; break; } pos_from_meta += hdr_size; ret = fseek(f, csize, SEEK_CUR); if (ret == -1) { err = -1; break; } pos_from_meta += csize; if (pos_from_meta > meta_size) { err = -1; break; } //printf("tag: (%d) %c%c%c%c\n", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); // Go mining ilst if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8 || tag_name_cmp(tag_c,(guchar *) "ilst") != 1) { err = -1; break; } pos_from_meta += hdr_size; pos_from_meta += csize; if (pos_from_meta > meta_size) { err = -1; break; } ilst_size = csize; pos_from_ilst = 0; //printf("tag: (%d) %c%c%c%c\n", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); // Parse tags inside ilst ret = 0; found = 0; while ( (pos_from_ilst < ilst_size)&&(ret == 0) ) { if ((hdr_size = mp4_getTagLen(f, &csize, &csize_is64, tag_c)) < 8) { ret = 2; break; } pos_from_ilst += hdr_size; if (pos_from_ilst + csize > ilst_size) { ret = 2; break; } //printf("tag: (%d) %c%c%c%c ...", csize, tag_c[0], tag_c[1],tag_c[2],tag_c[3]); val = (guchar *) g_malloc( csize * sizeof(guchar) ); if (mp4_getValue(f, csize, val) != csize) { ret = 2; break; } if ( mp4_parseprocesstags(md, tag_c, val, csize) == 0 ) { pos_from_ilst += csize; //printf("Processed! \n"); } else { // Tag unknown or an error occured pos_from_ilst += csize; //printf("Skipped!\n"); } g_free(val); if (pos_from_moov > moov_size) { ret = 2; } }; if (ret != 0) { err = -1; break; } // Mediatype specific operations? /* Not needed for the moment if(md->mediatype == TYPE_AUDIO){ printf("Type Audio ... going on\n"); } */ //printf("Meta finished. \n"); }while(0); return err; } static void extract_mp4 (const gchar *uri, TrackerSparqlBuilder *preupdate, TrackerSparqlBuilder *metadata) { /* File information */ FILE *f; gchar *filename; goffset size; t_MP4Data md; /* Coalesced inputs */ gchar *title = NULL; gchar *title_alternative = NULL; gchar *performer = NULL; gchar *genre = NULL; gchar *text = NULL; // Initialize md memset (&md, 0, sizeof(t_MP4Data)); filename = g_filename_from_uri(uri, NULL, NULL); size = tracker_file_get_size(filename); if (size == 0) { g_free(filename); return; } /* Open file */ f = fopen(filename, "r"); if (!f) { g_free(filename); return; } /* Get data from file. */ parse_mp4(f, size, &md); /* Close file */ fclose (f); /* Make sure we coalesce duplicate values */ if (md.title) { title = md.title; #if defined(__linux__) } else { //Unix dependant - look at the filename itself as title at the end of the path title_alternative = g_strrstr(filename,"/"); if ((title_alternative != NULL) &&(*(title_alternative+1) != '\0')) { // +1 Skip the character '/' title = g_strdup(title_alternative+1); } // !!! WARNING Don't free title_alternative as it is a part of filename that would be freed later !!! #endif } performer = tracker_coalesce(2, md.performer, md.album_performer); genre = tracker_coalesce(2, md.custom_genre, md.predefined_genre); text = tracker_coalesce(2, md.long_description, md.description); if (md.mediatype == TYPE_UNKNOWN) { if ((md.mediatype_from_meta == TYPE_MUSIC)|| (md.mediatype_from_meta == TYPE_AUDIOBOOK)|| (md.mediatype_from_meta == TYPE_RINGTONE)) { md.mediatype = TYPE_AUDIO; } else if (md.mediatype_from_meta == TYPE_BOOKLET) { md.mediatype = TYPE_OTHER; } else { md.mediatype = TYPE_VIDEO; } } /* Do any pre-updates * * (This involves creating any database nodes for consistent * data objects, for example, an artist which might be used n times) */ if (performer) { md.performer_uri = tracker_uri_printf_escaped ("urn:artist:%s", performer); tracker_sparql_builder_insert_open (preupdate, NULL); tracker_sparql_builder_subject_iri (preupdate, md.performer_uri); tracker_sparql_builder_predicate (preupdate, "a"); tracker_sparql_builder_object (preupdate, "nmm:Artist"); tracker_sparql_builder_predicate (preupdate, "nmm:artistName"); tracker_sparql_builder_object_unvalidated (preupdate, performer); tracker_sparql_builder_insert_close (preupdate); } else { md.performer_uri = NULL; } if (md.composer) { md.composer_uri = tracker_uri_printf_escaped ("urn:artist:%s", md.composer); tracker_sparql_builder_insert_open (preupdate, NULL); tracker_sparql_builder_subject_iri (preupdate, md.composer_uri); tracker_sparql_builder_predicate (preupdate, "a"); tracker_sparql_builder_object (preupdate, "nmm:Artist"); tracker_sparql_builder_predicate (preupdate, "nmm:artistName"); tracker_sparql_builder_object_unvalidated (preupdate, md.composer); tracker_sparql_builder_insert_close (preupdate); } else { md.composer_uri = NULL; } if (md.mediatype == TYPE_AUDIO) { if (md.album) { md.album_uri = tracker_uri_printf_escaped ("urn:album:%s", md.album); tracker_sparql_builder_insert_open (preupdate, NULL); tracker_sparql_builder_subject_iri (preupdate, md.album_uri); tracker_sparql_builder_predicate (preupdate, "a"); tracker_sparql_builder_object (preupdate, "nmm:MusicAlbum"); tracker_sparql_builder_predicate (preupdate, "nmm:albumTitle"); tracker_sparql_builder_object_unvalidated (preupdate, md.album); tracker_sparql_builder_insert_close (preupdate); if (md.track_count > 0) { tracker_sparql_builder_delete_open (preupdate, NULL); tracker_sparql_builder_subject_iri (preupdate, md.album_uri); tracker_sparql_builder_predicate (preupdate, "nmm:albumTrackCount"); tracker_sparql_builder_object_variable (preupdate, "unknown"); tracker_sparql_builder_delete_close (preupdate); tracker_sparql_builder_where_open (preupdate); tracker_sparql_builder_subject_iri (preupdate, md.album_uri); tracker_sparql_builder_predicate (preupdate, "nmm:albumTrackCount"); tracker_sparql_builder_object_variable (preupdate, "unknown"); tracker_sparql_builder_where_close (preupdate); tracker_sparql_builder_insert_open (preupdate, NULL); tracker_sparql_builder_subject_iri (preupdate, md.album_uri); tracker_sparql_builder_predicate (preupdate, "nmm:albumTrackCount"); tracker_sparql_builder_object_int64 (preupdate, md.track_count); tracker_sparql_builder_insert_close (preupdate); } } else { md.album_uri = NULL; } } /* Do any metadata updates * * (This is where you can use entities created in the * pre-updates part, like an artist). */ if (md.mediatype == TYPE_AUDIO) { tracker_sparql_builder_predicate (metadata, "a"); tracker_sparql_builder_object (metadata, "nmm:MusicPiece"); tracker_sparql_builder_object (metadata, "nfo:Audio"); } else if (md.mediatype == TYPE_VIDEO) { tracker_sparql_builder_predicate (metadata, "a"); tracker_sparql_builder_object (metadata, "nmm:Video"); } if (title) { tracker_sparql_builder_predicate (metadata, "nie:title"); tracker_sparql_builder_object_unvalidated (metadata, title); g_free (title); } if (md.mediatype == TYPE_AUDIO) { if (md.performer_uri) { tracker_sparql_builder_predicate (metadata, "nmm:performer"); tracker_sparql_builder_object_iri (metadata, md.performer_uri); g_free (md.performer_uri); g_free (performer); } if (md.composer_uri) { tracker_sparql_builder_predicate (metadata, "nmm:composer"); tracker_sparql_builder_object_iri (metadata, md.composer_uri); g_free (md.composer_uri); g_free (md.composer); } if (md.album_uri) { tracker_sparql_builder_predicate (metadata, "nmm:musicAlbum"); tracker_sparql_builder_object_iri (metadata, md.album_uri); g_free (md.album_uri); g_free (md.album); } if (md.track_number > 0) { tracker_sparql_builder_predicate (metadata, "nmm:trackNumber"); tracker_sparql_builder_object_int64 (metadata, md.track_number); } } else if (md.mediatype == TYPE_VIDEO) { if (md.performer_uri) { tracker_sparql_builder_predicate (metadata, "nmm:leadActor"); tracker_sparql_builder_object_iri (metadata, md.performer_uri); g_free (md.performer_uri); g_free (performer); } if (md.composer_uri) { tracker_sparql_builder_predicate (metadata, "nmm:director"); tracker_sparql_builder_object_iri (metadata, md.composer_uri); g_free (md.composer_uri); g_free (md.composer); } if (md.category) { tracker_sparql_builder_predicate (metadata, "nmm:category"); tracker_sparql_builder_object_unvalidated (metadata, md.category); g_free (md.category); } if (md.rating == TYPE_RATING_CLEAN) { tracker_sparql_builder_predicate (metadata, "nmm:MPAARating"); tracker_sparql_builder_object_unvalidated (metadata, "Clean"); } else if (md.rating == TYPE_RATING_EXPLICIT) { tracker_sparql_builder_predicate (metadata, "nmm:MPAARating"); tracker_sparql_builder_object_unvalidated (metadata, "Explicit"); } if (md.mediatype_from_meta == TYPE_TVSHOW ) { // Is a serie! tracker_sparql_builder_predicate (metadata, "nmm:isSeries"); tracker_sparql_builder_object_boolean (metadata, 1); if (md.tv_episode_num > 0) { tracker_sparql_builder_predicate (metadata, "nmm:episodeNumber"); tracker_sparql_builder_object_int64 (metadata, md.tv_episode_num); } if (md.tv_season_num > 0) { tracker_sparql_builder_predicate (metadata, "nmm:season"); tracker_sparql_builder_object_int64 (metadata, md.tv_season_num); } } } if (md.recording_time) { tracker_sparql_builder_predicate (metadata, "nie:contentCreated"); tracker_sparql_builder_object_unvalidated (metadata, md.recording_time); g_free (md.recording_time); } if (genre) { tracker_sparql_builder_predicate (metadata, "nfo:genre"); tracker_sparql_builder_object_unvalidated (metadata, genre); g_free (genre); } if (md.copyright) { tracker_sparql_builder_predicate (metadata, "nie:copyright"); tracker_sparql_builder_object_unvalidated (metadata, md.copyright); g_free (md.copyright); } if (md.comment) { tracker_sparql_builder_predicate (metadata, "nie:comment"); tracker_sparql_builder_object_unvalidated (metadata, md.comment); g_free (md.comment); } if (md.duration > 0) { tracker_sparql_builder_predicate (metadata, "nfo:duration"); tracker_sparql_builder_object_int64 (metadata, md.duration); } if (text) { /* This is for Full Text Search (FTS) indexing content */ tracker_sparql_builder_predicate (metadata, "nie:plainTextContent"); tracker_sparql_builder_object_unvalidated (metadata, text); g_free (text); } // Set audio/video properties: tracker_sparql_builder_predicate (metadata, "nfo:codec"); tracker_sparql_builder_object_string (metadata, "MPEG"); if (md.timescale_bitrate > 0) { tracker_sparql_builder_predicate (metadata, "nfo:sampleRate"); tracker_sparql_builder_object_int64 (metadata, md.timescale_bitrate); } if (md.bpm > 0) { tracker_sparql_builder_predicate (metadata, "nfo:averageBitrate"); tracker_sparql_builder_object_int64 (metadata, md.bpm); } // Free md's unused vars g_free(md.purchase_date); g_free(md.encoder); g_free(md.grouping); g_free(md.tv_show_name); g_free(md.tv_episode_id); g_free(md.tv_network_name); /* Clean up */ g_free (filename); } TrackerExtractData * tracker_extract_get_data (void) { /* NOTE: This function has to exist, tracker-extract checks * the symbole table for this function and if it doesn't * exist, the module is not loaded to be used as an extractor. */ return extract_data; }