this.file=file;}publicMetadataDateTime?get_creation_date_time(){returnnewMetadataDateTime((time_t)get_creation_date_time_for_quicktime());}publicstring?get_title(){// Not supported.returnnull;}// Checks if the given file is a QuickTime file.publicboolis_supported(){QuickTimeAtomtest=newQuickTimeAtom(file);boolret=false;try{

}else{// Some versions of QuickTime don't have// an ftyp section, so we'll just look// for the mandatory moov section.while(true){if("moov"==test.get_current_atom_name()){ret=true;break;}test.next_atom();test.read_atom();if(test.is_last_atom()){break;}}}}catch(GLib.Errore){

// Some Android phones package videos recorded with their internal cameras in a 3GP// container that looks suspiciously like a QuickTime container but really isn't -- for// the timestamps of these Android 3GP videos are relative to the UNIX epoch// (January 1, 1970) instead of the QuickTime epoch (January 1, 1904). So, if we detect a// QuickTime movie with a negative timestamp, we can be pretty sure it isn't a valid// QuickTime movie that was shot before 1904 but is instead a non-compliant 3GP video// file. If we detect such a video, we correct its time. See this Redmine ticket// (http://redmine.yorba.org/issues/3314) for more information.if(timestamp<0)timestamp+=QUICKTIME_EPOCH_ADJUSTMENT;return(ulong)timestamp;

publicQuickTimeAtomget_first_child_atom(){// Child will simply have the input stream// but not the size/offset. This works because// child atoms follow immediately after a header,// so no skipping is required to access the child// from the current position.

privateclassAVIMetadataLoader{privateFilefile=null;// A numerical date string, i.e 2010:01:28 14:54:25privateconstintNUMERICAL_DATE_LENGTH=19;// Marker for timestamp section in a Nikon nctg blob.privateconstuint16NIKON_NCTG_TIMESTAMP_MARKER=0x13;// Size limit to ensure we don't parse forever on a bad file.privateconstintMAX_STRD_LENGTH=100;publicAVIMetadataLoader(Filefile){this.file=file;}publicMetadataDateTime?get_creation_date_time(){returnnewMetadataDateTime((time_t)get_creation_date_time_for_avi());}publicstring?get_title(){// Not supported.returnnull;}// Checks if the given file is an AVI file.publicboolis_supported(){AVIChunkchunk=newAVIChunk(file);boolret=false;try{chunk.open_file();chunk.read_chunk();// Look for the header and identifier.if("RIFF"==chunk.get_current_chunk_name()&&"AVI "==chunk.read_name()){ret=true;}}catch(GLib.Errore){debug("Error while testing for AVI file: %s",e.message);}try{chunk.close_file();}catch(GLib.Errore){debug("Error while closing AVI file: %s",e.message);}returnret;}// Parses a Nikon nctg tag. Based losely on avi_read_nikon() in FFmpeg.privatestringread_nikon_nctg_tag(AVIChunkchunk)throwsGLib.Error{boolfound_date=false;while(chunk.section_size_remaining()>sizeof(uint16)*2){uint16tag=chunk.read_uint16();uint16size=chunk.read_uint16();if(NIKON_NCTG_TIMESTAMP_MARKER==tag){found_date=true;break;}chunk.skip(size);}if(found_date){// Read numerical date string, example: 2010:01:28 14:54:25GLib.StringBuildersb=newGLib.StringBuilder();for(inti=0;i<NUMERICAL_DATE_LENGTH;i++){sb.append_c((char)chunk.read_byte());}returnsb.str;}return"";}// Parses a Fujifilm strd tag. Based on information from:// http://www.eden-foundation.org/products/code/film_date_stamp/index.htmlprivatestringread_fuji_strd_tag(AVIChunkchunk)throwsGLib.Error{chunk.skip(98);// Ignore 98-byte binary blob.chunk.skip(8);// Ignore the string "FUJIFILM"// Read until we find four colons, then two more chars.intcolons=0;intpost_colons=0;GLib.StringBuildersb=newGLib.StringBuilder();// End of date is two chars past the fourth colon.while(colons<=4&&post_colons<2){charc=(char)chunk.read_byte();if(4==colons){post_colons++;}if(':'==c){colons++;}if(c.isprint()){sb.append_c(c);}if(sb.len>MAX_STRD_LENGTH){return"";// Give up searching.}}if(sb.str.length<NUMERICAL_DATE_LENGTH){return"";}// Date is now at the end of the string.returnsb.str.substring(sb.str.length-NUMERICAL_DATE_LENGTH);}// Recursively read file until the section is found.privatestring?read_section(AVIChunkchunk)throwsGLib.Error{while(true){chunk.read_chunk();stringname=chunk.get_current_chunk_name();if("IDIT"==name){returnchunk.section_to_string();}elseif("nctg"==name){returnread_nikon_nctg_tag(chunk);}elseif("strd"==name){returnread_fuji_strd_tag(chunk);}if("LIST"==name){chunk.read_name();// Read past list name.stringresult=read_section(chunk.get_first_child_chunk());if(null!=result){returnresult;}}if(chunk.is_last_chunk()){break;}chunk.next_chunk();}returnnull;}// Parses a date from a string.// Largely based on GStreamer's avi/gstavidemux.c // and the information here: // http://www.eden-foundation.org/products/code/film_date_stamp/index.htmlprivateulongparse_date(stringsdate){

// watch for overflow (happens on quasi-bogus dates, like Year 200)time_ttm=time.mktime();ulongresult=tm+seconds;if(result<tm){debug("Overflow for timestamp in video file %s",file.get_path());return0;}returnresult;

// don't use checked reads here because they advance the section offset, which we're trying// to determine hereGLib.StringBuildersb=newGLib.StringBuilder();sb.append_c((char)input.read_byte());sb.append_c((char)input.read_byte());sb.append_c((char)input.read_byte());sb.append_c((char)input.read_byte());section_name=sb.str;section_size=input.read_uint32();