/** Action for removing chapters from the <b>job.file</b> * * * @param job the job to process * @return mp4v2::util::SUCCESS if successful, mp4v2::util::FAILURE otherwise */ bool ChapterUtility::actionRemove( JobContext& job ) { ostringstream oss; oss << "Deleting " << getChapterTypeName( _ChapterType ) << " chapters from 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() ); } MP4ChapterType chtp = MP4DeleteChapters( job.fileHandle, _ChapterType ); if( MP4ChapterTypeNone == chtp ) { return FAILURE; } fixQtScale( job.fileHandle ); job.optimizeApplicable = true; return SUCCESS; }
/** 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; }
bool ArtUtility::actionRemove( JobContext& job ) { 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() ); if( _artFilter == numeric_limits<uint32_t>::max() ) verbose1f( "removing covr-box (all) from %s\n", job.file.c_str() ); else verbose1f( "removing covr-box (index=%d) from %s\n", _artFilter, job.file.c_str() ); if( dryrunAbort() ) return SUCCESS; if( CoverArtBox::remove( job.fileHandle, _artFilter )) return herrf( "remove failed\n" ); return SUCCESS; }
bool Strip_Tag(LPCSTR filename) { // TODO: // remove tag from file. // do whatever is need to remove the supported tag from filename // return true for successfull strip, false for failure MP4FileHandle file; file = MP4Modify(filename, 0, 0); if (file == MP4_INVALID_FILE_HANDLE) return false; MP4MetadataDelete(file); MP4Close(file); return true; }
bool ArtUtility::actionReplace( JobContext& job ) { File in( _artImageFile, File::MODE_READ ); if( in.open() ) return herrf( "unable to open %s for read: %s\n", _artImageFile.c_str(), sys::getLastErrorStr() ); const uint32_t max = numeric_limits<uint32_t>::max(); if( in.size > max ) return herrf( "file too large: %s (exceeds %u bytes)\n", _artImageFile.c_str(), max ); CoverArtBox::Item item; item.size = static_cast<uint32_t>( in.size ); item.buffer = static_cast<uint8_t*>( malloc( item.size )); item.autofree = true; File::Size nin; if( in.read( item.buffer, item.size, nin )) return herrf( "read failed: %s\n", _artImageFile.c_str() ); in.close(); if( _artFilter == numeric_limits<uint32_t>::max() ) verbose1f( "replacing %s -> %s (all)\n", _artImageFile.c_str(), job.file.c_str() ); else verbose1f( "replacing %s -> %s (index=%d)\n", _artImageFile.c_str(), job.file.c_str(), _artFilter ); 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() ); if( CoverArtBox::set( job.fileHandle, item, _artFilter )) return herrf( "unable to add covr-box: %s\n", job.file.c_str() ); return SUCCESS; }
int main(int argc, char** argv) { static struct option long_options[] = { { "help", 0, 0, OPT_HELP }, { "version", 0, 0, OPT_VERSION }, { "album", 1, 0, OPT_ALBUM }, { "artist", 1, 0, OPT_ARTIST }, { "comment", 1, 0, OPT_COMMENT }, { "disk", 1, 0, OPT_DISK }, { "disks", 1, 0, OPT_DISKS }, { "genre", 1, 0, OPT_GENRE }, { "song", 1, 0, OPT_SONG }, { "tempo", 1, 0, OPT_TEMPO }, { "track", 1, 0, OPT_TRACK }, { "tracks", 1, 0, OPT_TRACKS }, { "writer", 1, 0, OPT_WRITER }, { "year", 1, 0, OPT_YEAR }, { NULL, 0, 0, 0 } }; /* Sparse arrays of tag data: some space is wasted, but it's more convenient to say tags[OPT_SONG] than to enumerate all the metadata types (again) as a struct. */ char *tags[UCHAR_MAX] = { 0 }; int nums[UCHAR_MAX] = { 0 }; /* Any modifications requested? */ int mods = 0; /* Option-processing loop. */ int c = getopt_long_only(argc, argv, OPT_STRING, long_options, NULL); while (c != -1) { int r = 2; switch(c) { /* getopt() returns '?' if there was an error. It already printed the error message, so just return. */ case '?': return 1; /* Help and version requests handled here. */ case OPT_HELP: fprintf(stderr, "usage %s %s", argv[0], help_text); return 0; case OPT_VERSION: fprintf(stderr, "%s - %s version %s\n", argv[0], MPEG4IP_PACKAGE, MPEG4IP_VERSION); return 0; /* Numeric arguments: convert them using sscanf(). */ case OPT_DISK: case OPT_DISKS: case OPT_TRACK: case OPT_TRACKS: case OPT_TEMPO: r = sscanf(optarg, "%d", &nums[c]); if (r < 1) { fprintf(stderr, "%s: option requires numeric argument -- %c\n", argv[0], c); return 2; } /* Break not, lest ye be broken. :) */ /* All arguments: all valid options end up here, and we just stuff the string pointer into the tags[] array. */ default: tags[c] = optarg; mods++; } /* end switch */ c = getopt_long_only(argc, argv, OPT_STRING, long_options, NULL); } /* end while */ /* Check that we have at least one non-option argument */ if ((argc - optind) < 1) { fprintf(stderr, "%s: You must specify at least one MP4 file.\n", argv[0]); fprintf(stderr, "usage %s %s", argv[0], help_text); return 3; } /* Check that we have at least one requested modification. Probably it's useful instead to print the metadata if no modifications are requested? */ if (!mods) { fprintf(stderr, "%s: You must specify at least one tag modification.\n", argv[0]); fprintf(stderr, "usage %s %s", argv[0], help_text); return 4; } /* Loop through the non-option arguments, and modify the tags as requested. */ while (optind < argc) { char *mp4 = argv[optind++]; MP4FileHandle h = MP4Modify(mp4); if (h == MP4_INVALID_FILE_HANDLE) { fprintf(stderr, "Could not open '%s'... aborting\n", mp4); return 5; } /* Track/disk numbers need to be set all at once, but we'd like to allow users to just specify -T 12 to indicate that all existing track numbers are out of 12. This means we need to look up the current info if it is not being set. */ uint16_t n0, m0, n1, m1; if (tags[OPT_TRACK] || tags[OPT_TRACKS]) { MP4GetMetadataTrack(h, &n0, &m0); n1 = tags[OPT_TRACK]? nums[OPT_TRACK] : n0; m1 = tags[OPT_TRACKS]? nums[OPT_TRACKS] : m0; MP4SetMetadataTrack(h, n1, m1); } if (tags[OPT_DISK] || tags[OPT_DISKS]) { MP4GetMetadataDisk(h, &n0, &m0); n1 = tags[OPT_DISK]? nums[OPT_DISK] : n0; m1 = tags[OPT_DISKS]? nums[OPT_DISKS] : m0; MP4SetMetadataDisk(h, n1, m1); } /* Set the other relevant attributes */ for (int i = 0; i < UCHAR_MAX; i++) { if (tags[i]) { switch(i) { case OPT_ALBUM: MP4SetMetadataAlbum(h, tags[i]); break; case OPT_ARTIST: MP4SetMetadataArtist(h, tags[i]); break; case OPT_COMMENT: MP4SetMetadataComment(h, tags[i]); break; case OPT_GENRE: MP4SetMetadataGenre(h, tags[i]); break; case OPT_SONG: MP4SetMetadataName(h, tags[i]); break; case OPT_WRITER: MP4SetMetadataWriter(h, tags[i]); break; case OPT_YEAR: MP4SetMetadataYear(h, tags[i]); break; case OPT_TEMPO: MP4SetMetadataTempo(h, nums[i]); break; } } } MP4Close(h); } /* end while optind < argc */ return 0; }
/** 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; }
bool Write_Tag(LPCSTR filename, void* tagHandle) { // TODO: // read metadata from tagHandle and set each field to supported tag // only TAGFIELD_* are supported (see QCDModTagEditor.h) // example of how to get value from tagHandle // use SetFieldA for ASCII or MultiBytes strings. // use SetFieldW for UNICODE strings // // szwValue = ModInitTag.GetFieldW(tagHandle, TAGFIELD_ORCHESTRA); // write tag to file MP4FileHandle file = MP4_INVALID_FILE_HANDLE; char dummy1[1024]; char temp[1024]; short dummy, dummy2; #ifdef DEBUG_OUTPUT in_mp4_DebugOutput("mp4_tag_write"); #endif /* save Metadata changes */ tag_delete(&tags); file = MP4Read(filename, 0); if (file != MP4_INVALID_FILE_HANDLE) { ReadMP4Tag(file, &tags); MP4Close(file); file = MP4Modify(filename, 0, 0); if (file != MP4_INVALID_FILE_HANDLE) { MP4MetadataDelete(file); MP4Close(file); } } file = MP4Modify(filename, 0, 0); if (file == MP4_INVALID_FILE_HANDLE) { tag_delete(&tags); //EndDialog(hwndDlg, wParam); return false; } uGetDlgItemText(tagHandle, TAGFIELD_TITLE, dummy1, 1024); tag_set_field(&tags, "title", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_COMPOSER, dummy1, 1024); tag_set_field(&tags, "writer", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_ARTIST, dummy1, 1024); tag_set_field(&tags, "artist", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_ALBUM, dummy1, 1024); tag_set_field(&tags, "album", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_COMMENT, dummy1, 1024); tag_set_field(&tags, "comment", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_GENRE, dummy1, 1024); tag_set_field(&tags, "genre", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_YEAR, dummy1, 1024); tag_set_field(&tags, "year", dummy1); dummy = 0; MP4GetMetadataTrack(file, (unsigned __int16*)&dummy, (unsigned __int16*)&dummy2); memcpy(dummy1, ModInitTag.GetFieldA(tagHandle, TAGFIELD_TRACK), sizeof(dummy1)); dummy = atoi(dummy1); wsprintf(temp, "%d/%d", dummy, dummy2); tag_set_field(&tags, "track", temp); //GetDlgItemText(hwndDlg, IDC_METADISK1, dummy1, 1024); //dummy = atoi(dummy1); //GetDlgItemText(hwndDlg, IDC_METADISK2, dummy1, 1024); //dummy2 = atoi(dummy1); //wsprintf(temp, "%d/%d", dummy, dummy2); //tag_set_field(&tags, "disc", temp); //GetDlgItemText(hwndDlg, IDC_METATEMPO, dummy1, 1024); //tag_set_field(&tags, "tempo", dummy1); //dummy3 = SendMessage(GetDlgItem(hwndDlg, IDC_METACOMPILATION), BM_GETCHECK, 0, 0); //tag_set_field(&tags, "compilation", (dummy3 ? "1" : "0")); uGetDlgItemText(tagHandle, TAGFIELD_ENCODER, dummy1, 1024); tag_set_field(&tags, "tool", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_CONDUCTOR, dummy1, 1024); tag_set_field(&tags, "CONDUCTOR", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_ORCHESTRA, dummy1, 1024); tag_set_field(&tags, "ORCHESTRA", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_YEARCOMPOSED, dummy1, 1024); tag_set_field(&tags, "YEARCOMPOSED", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_ORIGARTIST, dummy1, 1024); tag_set_field(&tags, "ORIGARTIST", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_LABEL, dummy1, 1024); tag_set_field(&tags, "LABEL", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_COPYRIGHT, dummy1, 1024); tag_set_field(&tags, "COPYRIGHT", dummy1); uGetDlgItemText(tagHandle, TAGFIELD_CDDBTAGID, dummy1, 1024); tag_set_field(&tags, "CDDBTAGID", dummy1); WriteMP4Tag(file, &tags); MP4Close(file); MP4Optimize(filename, NULL, 0); /* ! */ return true; }
DWORD CTag_Mp4::Save(LPCTSTR szFileName) { DWORD dwWin32errorCode = ERROR_SUCCESS; if(!m_bEnable) { return -1; } char *pFileName = TstrToDataAlloc(szFileName, -1, NULL, DTC_CODE_UTF8); if (pFileName == NULL) { return -1; } MP4FileHandle mp4file = MP4Modify(pFileName); free(pFileName); if(mp4file == MP4_INVALID_FILE_HANDLE) { return -1; } #ifdef USE_OLD_TAG_API if(m_strMetadata_Name.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Name, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataName(mp4file,buf); free(buf); } } else { MP4DeleteMetadataName(mp4file); } if(m_strMetadata_Artist.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Artist, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataArtist(mp4file,buf); free(buf); } } else { MP4DeleteMetadataArtist(mp4file); } if(m_strMetadata_Album.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Album, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataAlbum(mp4file,buf); free(buf); } } else { MP4DeleteMetadataAlbum(mp4file); } if(m_strMetadata_Group.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Group, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataGrouping(mp4file,buf); free(buf); } } else { MP4DeleteMetadataGrouping(mp4file); } if(m_strMetadata_Composer.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Composer, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataWriter(mp4file,buf); free(buf); } } else { MP4DeleteMetadataWriter(mp4file); } if(m_strMetadata_Genre.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Genre, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataGenre(mp4file,buf); free(buf); } } else { MP4DeleteMetadataGenre(mp4file); } if((m_iMetadata_Track1 == -1) && (m_iMetadata_Track2 == -1)) { MP4DeleteMetadataTrack(mp4file); } else if(m_iMetadata_Track1 == -1) { MP4SetMetadataTrack(mp4file,0,m_iMetadata_Track2); } else if(m_iMetadata_Track2 == -1) { MP4SetMetadataTrack(mp4file,m_iMetadata_Track1,0); } else { MP4SetMetadataTrack(mp4file,m_iMetadata_Track1,m_iMetadata_Track2); } if((m_iMetadata_Disc1 == -1) && (m_iMetadata_Disc2 == -1)) { MP4DeleteMetadataDisk(mp4file); } else if(m_iMetadata_Disc1 == -1) { MP4SetMetadataDisk(mp4file,0,m_iMetadata_Disc2); } else if(m_iMetadata_Disc2 == -1) { MP4SetMetadataDisk(mp4file,m_iMetadata_Disc1,0); } else { MP4SetMetadataDisk(mp4file,m_iMetadata_Disc1,m_iMetadata_Disc2); } if(m_iMetadata_Tempo == -1) { MP4DeleteMetadataTempo(mp4file); } else { MP4SetMetadataTempo(mp4file,m_iMetadata_Tempo); } if(m_strMetadata_Year.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Year, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataYear(mp4file,buf); free(buf); } } else { MP4DeleteMetadataYear(mp4file); } if(m_iMetadata_Compilation != -1) { MP4SetMetadataCompilation(mp4file,1); } else { MP4DeleteMetadataCompilation(mp4file); } if(m_strMetadata_Comment.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Comment, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataComment(mp4file,buf); free(buf); } } else { MP4DeleteMetadataComment(mp4file); } if(m_strMetadata_Tool.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Tool, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataTool(mp4file,buf); free(buf); } } else { MP4DeleteMetadataTool(mp4file); } #else const MP4Tags* tags = MP4TagsAlloc(); if(tags) { MP4TagsFetch(tags, mp4file); if(m_strMetadata_Name.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Name, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetName(tags, buf); free(buf); } } else { MP4TagsSetName(tags, NULL); } if(m_strMetadata_Artist.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Artist, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetArtist(tags, buf); free(buf); } } else { MP4TagsSetArtist(tags, NULL); } if(m_strMetadata_Album.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Album, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetAlbum(tags, buf); free(buf); } } else { MP4TagsSetAlbum(tags, NULL); } if(m_strMetadata_Group.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Group, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetGrouping(tags, buf); free(buf); } } else { MP4TagsSetGrouping(tags, NULL); } if(m_strMetadata_Composer.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Composer, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetComposer(tags, buf); free(buf); } } else { MP4TagsSetComposer(tags, NULL); } if(m_strMetadata_Genre.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Genre, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetGenre(tags, buf); free(buf); } } else { MP4TagsSetGenre(tags, NULL); } if((m_iMetadata_Track1 == -1) && (m_iMetadata_Track2 == -1)) { MP4TagsSetTrack(tags, NULL); } else { MP4TagTrack track; track.index = (m_iMetadata_Track1 == -1) ? 0 : m_iMetadata_Track1; track.total = (m_iMetadata_Track2 == -1) ? 0 : m_iMetadata_Track2; MP4TagsSetTrack(tags, &track); } if((m_iMetadata_Disc1 == -1) && (m_iMetadata_Disc2 == -1)) { MP4TagsSetDisk(tags, NULL); } else { MP4TagDisk disk; disk.index = (m_iMetadata_Disc1 == -1) ? 0 : m_iMetadata_Disc1; disk.total = (m_iMetadata_Disc2 == -1) ? 0 : m_iMetadata_Disc2; MP4TagsSetDisk(tags, &disk); } if(m_iMetadata_Tempo == -1) { MP4TagsSetTempo(tags, NULL); } else { uint16_t tempo = m_iMetadata_Tempo; MP4TagsSetTempo(tags, &tempo); } if(m_strMetadata_Year.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Year, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetReleaseDate(tags, buf); free(buf); } } else { MP4TagsSetReleaseDate(tags, NULL); } if(m_iMetadata_Compilation != -1) { uint8_t compilation = 1; MP4TagsSetCompilation(tags, &compilation); } else { MP4TagsSetCompilation(tags, NULL); } if(m_strMetadata_Comment.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Comment, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetComments(tags, buf); free(buf); } } else { MP4TagsSetComments(tags, NULL); } if(m_strMetadata_Tool.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Tool, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetEncodingTool(tags, buf); free(buf); } } else { MP4TagsSetEncodingTool(tags, NULL); } MP4TagsStore(tags, mp4file); MP4TagsFree(tags); } #endif MP4Close(mp4file); return dwWin32errorCode; }
bool MP4::File::save() { MP4Close(mp4file); MP4FileHandle handle = MP4Modify(name()); if(handle == MP4_INVALID_FILE_HANDLE) { mp4file = MP4Read(name()); return false; } #ifdef MP4V2_HAS_WRITE_BUG /* according to gtkpod we have to delete all meta data before modifying it, save the stuff we would not touch */ // need to fetch/rewrite this only if we aren't going to anyway uint8_t compilation = 0; bool has_compilation = mp4tag->compilation() == MP4::Tag::Undefined ? MP4GetMetadataCompilation(handle, &compilation) : false; char *tool = NULL; MP4GetMetadataTool(handle, &tool); MP4MetadataDelete(handle); #endif #define setmeta(val, tag) \ if(mp4tag->val().isNull()) { \ /*MP4DeleteMetadata##tag(handle);*/ \ MP4SetMetadata##tag(handle, ""); \ } else { \ MP4SetMetadata##tag(handle, mp4tag->val().toCString(true)); \ } setmeta(title, Name); setmeta(artist, Artist); setmeta(album, Album); setmeta(comment, Comment); setmeta(genre, Genre); char buf[100] = ""; if(mp4tag->year()) snprintf(buf, sizeof(buf), "%u", mp4tag->year()); MP4SetMetadataYear(handle, buf); u_int16_t t1, t2; MP4GetMetadataTrack(handle, &t1, &t2); MP4SetMetadataTrack(handle, mp4tag->track(), t2); if(mp4tag->bpm() != 0) MP4SetMetadataTempo(handle, mp4tag->bpm()); if(mp4tag->compilation() != MP4::Tag::Undefined) { MP4SetMetadataCompilation(handle, mp4tag->compilation()); } MP4SetMetadataCoverArt(handle, mp4tag->cover().size() ? const_cast<u_int8_t *>( reinterpret_cast<const u_int8_t *>( mp4tag->cover().data() ) ) : 0, mp4tag->cover().size()); #ifdef MP4V2_HAS_WRITE_BUG // set the saved data again if(has_compilation) MP4SetMetadataCompilation(handle, compilation); if(tool) { MP4SetMetadataTool(handle, tool); free(tool); } #endif MP4Close(handle); mp4file = MP4Read(name()); if(mp4file == MP4_INVALID_FILE_HANDLE) { fprintf(stderr, "reopen failed\n"); return false; } return true; }
bool MP4Metadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; // Open the file for modification MP4FileHandle file = MP4Modify(reinterpret_cast<const char *>(buf)); if(MP4_INVALID_FILE_HANDLE == file) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the tags const MP4Tags *tags = MP4TagsAlloc(); if(NULL == tags) { MP4Close(file), file = NULL; if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); return false; } MP4TagsFetch(tags, file); // Album Title CFStringRef str = GetAlbumTitle(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetAlbum(tags, cString); } else MP4TagsSetAlbum(tags, NULL); // Artist str = GetArtist(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetArtist(tags, cString); } else MP4TagsSetArtist(tags, NULL); // Album Artist str = GetAlbumArtist(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetAlbumArtist(tags, cString); } else MP4TagsSetAlbumArtist(tags, NULL); // Genre str = GetGenre(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetGenre(tags, cString); } else MP4TagsSetGenre(tags, NULL); // Release date str = GetReleaseDate(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetReleaseDate(tags, cString); } else MP4TagsSetReleaseDate(tags, NULL); // Composer str = GetComposer(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetComposer(tags, cString); } else MP4TagsSetComposer(tags, NULL); // Comment str = GetComment(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetComments(tags, cString); } else MP4TagsSetComments(tags, NULL); // Track title str = GetTitle(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetName(tags, cString); } else MP4TagsSetName(tags, NULL); // Track number and total MP4TagTrack trackInfo; memset(&trackInfo, 0, sizeof(MP4TagTrack)); if(GetTrackNumber()) CFNumberGetValue(GetTrackNumber(), kCFNumberSInt32Type, &trackInfo.index); if(GetTrackTotal()) CFNumberGetValue(GetTrackTotal(), kCFNumberSInt32Type, &trackInfo.total); MP4TagsSetTrack(tags, &trackInfo); // Disc number and total MP4TagDisk discInfo; memset(&discInfo, 0, sizeof(MP4TagDisk)); if(GetDiscNumber()) CFNumberGetValue(GetDiscNumber(), kCFNumberSInt32Type, &discInfo.index); if(GetDiscTotal()) CFNumberGetValue(GetDiscTotal(), kCFNumberSInt32Type, &discInfo.total); MP4TagsSetDisk(tags, &discInfo); // Compilation if(GetCompilation()) { uint8_t comp = CFBooleanGetValue(GetCompilation()); MP4TagsSetCompilation(tags, &comp); } else MP4TagsSetCompilation(tags, NULL); // Lyrics str = GetLyrics(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetLyrics(tags, cString); } else MP4TagsSetLyrics(tags, NULL); // Album art CFDataRef artData = GetFrontCoverArt(); if(artData) { MP4TagArtwork artwork; artwork.data = reinterpret_cast<void *>(const_cast<UInt8 *>(CFDataGetBytePtr(artData))); artwork.size = static_cast<uint32_t>(CFDataGetLength(artData)); artwork.type = MP4_ART_UNDEFINED; MP4TagsAddArtwork(tags, &artwork); } // Save our changes MP4TagsStore(tags, file); // Replay Gain // Reference loudness if(GetReplayGainReferenceLoudness()) { float f; if(!CFNumberGetValue(GetReplayGainReferenceLoudness(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } char value [8]; snprintf(value, sizeof(value), "%2.1f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_reference_loudness"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_reference_loudness"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Track gain if(GetReplayGainTrackGain()) { float f; if(!CFNumberGetValue(GetReplayGainTrackGain(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [10]; snprintf(value, sizeof(value), "%+2.2f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_track_gain"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_gain"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Track peak if(GetReplayGainTrackPeak()) { float f; if(!CFNumberGetValue(GetReplayGainTrackPeak(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [12]; snprintf(value, sizeof(value), "%1.8f", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_track_peak"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_peak"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Album gain if(GetReplayGainAlbumGain()) { float f; if(!CFNumberGetValue(GetReplayGainAlbumGain(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [10]; snprintf(value, sizeof(value), "%+2.2f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_album_gain"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_gain"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Album peak if(GetReplayGainAlbumPeak()) { float f; if(!CFNumberGetValue(GetReplayGainAlbumPeak(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [12]; snprintf(value, sizeof(value), "%1.8f", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_album_peak"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_peak"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Clean up MP4TagsFree(tags), tags = NULL; MP4Close(file), file = NULL; MergeChangedMetadataIntoMetadata(); return true; }