/** Action for setting chapters every n second in <b>job.file</b> * * * @param job the job to process * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise */ bool ChapterUtility::actionEvery( JobContext& job ) { ostringstream oss; oss << "Setting " << getChapterTypeName( _ChapterType ) << " chapters every " << _ChaptersEvery << " seconds in file " << '"' << job.file << '"' << endl; verbose1f( "%s", oss.str().c_str() ); if( dryrunAbort() ) { return SUCCESS; } job.fileHandle = MP4Modify( job.file.c_str() ); if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) { return herrf( "unable to open for write: %s\n", job.file.c_str() ); } bool isVideoTrack = false; MP4TrackId refTrackId = getReferencingTrack( job.fileHandle, isVideoTrack ); if( !MP4_IS_VALID_TRACK_ID(refTrackId) ) { return herrf( "unable to find a video or audio track in file %s\n", job.file.c_str() ); } Timecode refTrackDuration( MP4GetTrackDuration( job.fileHandle, refTrackId ), MP4GetTrackTimeScale( job.fileHandle, refTrackId ) ); refTrackDuration.setScale( CHAPTERTIMESCALE ); Timecode chapterDuration( _ChaptersEvery * 1000, CHAPTERTIMESCALE ); chapterDuration.setFormat( Timecode::DECIMAL ); vector<MP4Chapter_t> chapters; do { MP4Chapter_t chap; chap.duration = refTrackDuration.duration > chapterDuration.duration ? chapterDuration.duration : refTrackDuration.duration; sprintf(chap.title, "Chapter %lu", (unsigned long)chapters.size()+1); chapters.push_back( chap ); refTrackDuration -= chapterDuration; } while( refTrackDuration.duration > 0 ); if( 0 < chapters.size() ) { MP4SetChapters(job.fileHandle, &chapters[0], (uint32_t)chapters.size(), _ChapterType); } fixQtScale( job.fileHandle ); job.optimizeApplicable = true; return SUCCESS; }
void _mp4v2_write_chapters(MP4V2Handles *handle) { VALUE self = handle->self; MP4FileHandle mp4v2 = handle->file; VALUE chapters = rb_check_array_type(GET(chapters)), chapter, title; double last_stamp = 0, stamp; MP4Chapter_t *chaps; uint32_t count = 0; switch(TYPE(chapters)) { case T_ARRAY: RARRAY_ALL_INSTANCE(chapters, rb_cChapter, chapter); // Make chapters go in order of timestamp rb_ary_sort_bang(chapters); count = RARRAY_LEN(chapters); chaps = handle->chapters = (MP4Chapter_t *)malloc(sizeof(MP4Chapter_t) * count); if (!chaps) { rb_raise(rb_eNoMemError, "unable to save all changes to file"); } for (uint32_t i = 0; i < count; i++) { // Calculate the duration of chapter from previous timestamp and current chapter = rb_ary_entry(chapters, i); stamp = NUM2DBL(rb_funcall(rb_ivar_get(chapter, rb_intern("@timestamp")), rb_intern("milliseconds"), 0)); chaps[i].duration = stamp - last_stamp; last_stamp = stamp; // Get the title of the chapter title = rb_encode_utf8(rb_ivar_get(chapter, rb_intern("@title"))); if (RSTRING_LEN(title) > MP4V2_CHAPTER_TITLE_MAX) { rb_raise(rb_eStandardError, "chapter title '%s' is too long, it should be at most %d bytes", RSTRING_PTR(title), MP4V2_CHAPTER_TITLE_MAX); } memcpy(chaps[i].title, RSTRING_PTR(title), RSTRING_LEN(title)+1); } if (count > 0) { MP4SetChapters(mp4v2, chaps, count, MP4ChapterTypeAny); } else { MP4DeleteChapters(mp4v2, MP4ChapterTypeAny); } free(chaps); handle->chapters = NULL; break; case T_NIL: MP4DeleteChapters(mp4v2, MP4ChapterTypeAny); break; default:; } }
/** Action for importing chapters into the <b>job.file</b> * * * @param job the job to process * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise */ bool ChapterUtility::actionImport( JobContext& job ) { vector<MP4Chapter_t> chapters; Timecode::Format format; // create the chapter file name string inName = job.file; if( _ChapterFile.empty() ) { FileSystem::pathnameStripExtension( inName ); inName.append( ".chapters.txt" ); } else { inName = _ChapterFile; } if( parseChapterFile( inName, chapters, format ) ) { return FAILURE; } ostringstream oss; oss << "Importing " << chapters.size() << " " << getChapterTypeName( _ChapterType ); oss << " chapters from file " << inName << " into file " << '"' << job.file << '"' << endl; verbose1f( "%s", oss.str().c_str() ); if( dryrunAbort() ) { return SUCCESS; } if( 0 == chapters.size() ) { return herrf( "No chapters found in file %s\n", inName.c_str() ); } job.fileHandle = MP4Modify( job.file.c_str() ); if( job.fileHandle == MP4_INVALID_FILE_HANDLE ) { return herrf( "unable to open for write: %s\n", job.file.c_str() ); } bool isVideoTrack = false; MP4TrackId refTrackId = getReferencingTrack( job.fileHandle, isVideoTrack ); if( !MP4_IS_VALID_TRACK_ID(refTrackId) ) { return herrf( "unable to find a video or audio track in file %s\n", job.file.c_str() ); } if( Timecode::FRAME == format && !isVideoTrack ) { // we need a video track for this return herrf( "unable to find a video track in file %s but chapter file contains frame timestamps\n", job.file.c_str() ); } // get duration and recalculate scale Timecode refTrackDuration( MP4GetTrackDuration( job.fileHandle, refTrackId ), MP4GetTrackTimeScale( job.fileHandle, refTrackId ) ); refTrackDuration.setScale( CHAPTERTIMESCALE ); // check for chapters starting after duration of reftrack for( vector<MP4Chapter_t>::iterator it = chapters.begin(); it != chapters.end(); ) { Timecode curr( (*it).duration, CHAPTERTIMESCALE ); if( refTrackDuration <= curr ) { hwarnf( "Chapter '%s' start: %s, playlength of file: %s, chapter cannot be set\n", (*it).title, curr.svalue.c_str(), refTrackDuration.svalue.c_str() ); it = chapters.erase( it ); } else { ++it; } } if( 0 == chapters.size() ) { return SUCCESS; } // convert start time into duration uint32_t framerate = static_cast<uint32_t>( CHAPTERTIMESCALE ); if( Timecode::FRAME == format ) { // get the framerate MP4SampleId sampleCount = MP4GetTrackNumberOfSamples( job.fileHandle, refTrackId ); Timecode tmpcd( refTrackDuration.svalue, CHAPTERTIMESCALE ); framerate = static_cast<uint32_t>( std::ceil( ((double)sampleCount / (double)tmpcd.duration) * CHAPTERTIMESCALE ) ); } for( vector<MP4Chapter_t>::iterator it = chapters.begin(); it != chapters.end(); ++it ) { MP4Duration currDur = (*it).duration; MP4Duration nextDur = chapters.end() == it+1 ? refTrackDuration.duration : (*(it+1)).duration; if( Timecode::FRAME == format ) { // convert from frame nr to milliseconds currDur = convertFrameToMillis( (*it).duration, framerate ); if( chapters.end() != it+1 ) { nextDur = convertFrameToMillis( (*(it+1)).duration, framerate ); } } (*it).duration = nextDur - currDur; } // now set the chapters MP4SetChapters( job.fileHandle, &chapters[0], (uint32_t)chapters.size(), _ChapterType ); fixQtScale( job.fileHandle ); job.optimizeApplicable = true; return SUCCESS; }