commit 64f2b51e0e1be5503d83784fc04467f6403bf195 Author: phantomjinx <p.g.richard...@phantomjinx.co.uk> Date: Tue Apr 10 23:52:39 2012 +0100
Support for writing mp4 tags using atomic parsley * AtomicParsleyBridge * Include read / write lyric support for the first time * Include write metadata function * m4afile.[ch] * Mirror the function calls in mp4file.c * Could we make these common somehow?? libs/atomic-parsley/AtomicParsley.cpp | 38 ++-- libs/atomic-parsley/AtomicParsleyBridge.cpp | 385 +++++++++++++++++++++++++-- libs/atomic-parsley/AtomicParsleyBridge.h | 66 ++--- plugins/filetype_m4a/Makefile.am | 2 +- plugins/filetype_m4a/m4afile.c | 20 ++- plugins/filetype_m4a/m4afile.h | 3 + plugins/filetype_m4a/plugin.c | 6 +- plugins/filetype_mp4/Makefile.am | 2 +- plugins/filetype_mp4/mp4file.c | 21 ++- plugins/filetype_mp4/mp4file.h | 4 +- plugins/filetype_mp4/plugin.c | 6 +- 11 files changed, 456 insertions(+), 97 deletions(-) --- diff --git a/libs/atomic-parsley/AtomicParsley.cpp b/libs/atomic-parsley/AtomicParsley.cpp index 6080d9e..6b49cd1 100755 --- a/libs/atomic-parsley/AtomicParsley.cpp +++ b/libs/atomic-parsley/AtomicParsley.cpp @@ -318,7 +318,6 @@ void TestFileExistence(const char *filePath, bool errorOut) { a_file = APar_OpenFile(filePath, "rb"); if( (a_file == NULL) && errorOut ){ fprintf(stderr, "AtomicParsley error: can't open %s for reading: %s\n", filePath, strerror(errno)); - exit(1); } else { fclose(a_file); } @@ -348,13 +347,13 @@ int APar_TestArtworkBinaryData(const char* artworkPath) { artwork_dataType = AtomFlags_Data_JPEGBinary; } else { fprintf(stdout, "AtomicParsley error: %s\n\t image file is not jpg/png and cannot be embedded.\n", artworkPath); - exit(1); + artwork_dataType = -1; } fclose(artfile); } else { fprintf(stdout, "AtomicParsley error: %s\n\t image file could not be opened.\n", artworkPath); - exit(1); + artwork_dataType = -1; } return artwork_dataType; } @@ -423,7 +422,7 @@ void APar_FreeMemory() { free(file_progress_buffer); file_progress_buffer=NULL; - if (source_file) { + if (source_file && file_opened) { fclose(source_file); file_opened = false; } @@ -2197,7 +2196,6 @@ void APar_IdentifyBrand(char* file_brand ) { //what ISN'T supported case 0x71742020 : //'qt ' --this is listed at mp4ra, but there are features of the file that aren't supported (like the 4 NULL bytes after the last udta child atom fprintf(stdout, "AtomicParsley error: Quicktime movie files are not supported.\n"); - exit(2); break; //3gp-brands are listed in 3GPP/3GPP2 specification documents, not all are listed at mp4ra @@ -2245,7 +2243,6 @@ void APar_IdentifyBrand(char* file_brand ) { //other lesser unsupported brands; http://www.mp4ra.org/filetype.html like dv, mjpeg200, mp21 & ... whatever mpeg7 brand is default : fprintf(stdout, "AtomicParsley error: unsupported MPEG-4 file brand found '%s'\n", file_brand); - exit(2); break; } @@ -2281,7 +2278,6 @@ uint64_t APar_64bitAtomRead(FILE *file, uint32_t jump_point) { contains_unsupported_64_bit_atom = true; fprintf(stdout, "You must be off your block thinking I'm going to tag a file that is at LEAST %llu bytes long.\n", extended_dataSize); fprintf(stdout, "AtomicParsley doesn't have full 64-bit support"); - exit (2); } return extended_dataSize; } @@ -3503,7 +3499,7 @@ void APar_MetaData_atom_QuickInit(short atom_num, const uint32_t atomFlags, uint parsedAtoms[atom_num].AtomicData = (char*)calloc(1, sizeof(char)* allotment+50 ); if (parsedAtoms[atom_num].AtomicData == NULL) { fprintf(stdout, "AP error: there was insufficient memory available for allocation. Exiting.%c\n", '\a'); - exit(1); + return; } parsedAtoms[atom_num].AtomicLength = 16 + supplemental_length; // 4bytes atom length, 4 bytes atom length, 4 bytes version/flags, 4 bytes NULL @@ -4408,7 +4404,7 @@ void APar_ValidateAtoms() { if (atom_number > MAX_ATOMS) { fprintf(stderr, "AtomicParsley error: amount of atoms exceeds internal limit. Aborting.\n"); - exit(1); + return; } while (true) { @@ -4426,7 +4422,7 @@ void APar_ValidateAtoms() { #else fprintf(stderr, "atom %s is %u bytes long which is greater than the filesize of %llu\n", parsedAtoms[iter].AtomicName, parsedAtoms[iter].AtomicLength, (long long unsigned int)file_size); #endif - exit(1); //its conceivable to repair such an off length by the surrounding atoms constrained by file_size - just not anytime soon; probly would catch a foobar2000 0.9 tagged file + return; //its conceivable to repair such an off length by the surrounding atoms constrained by file_size - just not anytime soon; probly would catch a foobar2000 0.9 tagged file } } @@ -4440,13 +4436,13 @@ void APar_ValidateAtoms() { if (strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0 && parsedAtoms[iter].AtomicLevel != 1) { fprintf(stderr, "AtomicParsley error: mdat atom was found at an illegal (not at top level). Aborting. %c\n", '\a'); - exit(1); //the error which forced this was some bad atom length redetermination; probably won't be fixed + return; //the error which forced this was some bad atom length redetermination; probably won't be fixed } if (memcmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0 && parsedAtoms[iter+1].NextAtomNumber != 0) { //prevent writing any malformed tracks if (memcmp(parsedAtoms[ parsedAtoms[iter].NextAtomNumber ].AtomicName, "tkhd", 4) != 0) { fprintf(stderr, "AtomicParsley error: incorrect track structure. %c\n", '\a'); - exit(1); + return; } } @@ -4463,7 +4459,7 @@ void APar_ValidateAtoms() { fprintf(stderr, "AtomicParsley error: total existing atoms present as larger than filesize. Aborting. %c\n", '\a'); //APar_PrintAtomicTree(); fprintf(stdout, "%i %llu\n", percentage_difference, simple_tally); - exit(1); + return; } if (!atom_name_with_4_characters) { @@ -5015,7 +5011,7 @@ void APar_WriteFile(const char* m4aFile, const char* outfile, bool rewrite_origi } else { fprintf(stdout, "AtomicParsley error: an error occurred while trying to create a temp file.\n"); - exit(1); + return; } if (udta_dynamics.dynamic_updating) { @@ -5027,7 +5023,7 @@ void APar_WriteFile(const char* m4aFile, const char* outfile, bool rewrite_origi if (source_file == NULL) { fclose(temp_file); remove(temp_file_name); - exit(1); + return; } //update moov's length @@ -5056,7 +5052,10 @@ void APar_WriteFile(const char* m4aFile, const char* outfile, bool rewrite_origi fwrite(file_buffer, 1, free_padding_size-8, source_file); } } + fclose(source_file); + file_opened = false; + fclose(temp_file); remove(temp_file_name); fclose(temp_file); @@ -5064,6 +5063,7 @@ void APar_WriteFile(const char* m4aFile, const char* outfile, bool rewrite_origi } else if (rewrite_original && !outfile) { //disable overWrite when writing out to a specifically named file; presumably the enumerated output file was meant to be the final destination fclose(source_file); + file_opened = false; if ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { #if defined (_MSC_VER) /* native windows seems to require removing the file first; rename() on Mac OS X does the removing automatically as needed */ @@ -5114,19 +5114,19 @@ void APar_WriteFile(const char* m4aFile, const char* outfile, bool rewrite_origi case ENAMETOOLONG: { fprintf (stdout, "Some or all of the orginal path was too long."); - exit (-1); + return; } case ENOENT: { fprintf (stdout, "Some part of the original path was missing."); - exit (-1); + return; } case EACCES: { fprintf (stdout, "Unable to write to a directory lacking write permission."); - exit (-1); + return; } case ENOSPC: { fprintf (stdout, "Out of space."); - exit (-1); + return; } } } diff --git a/libs/atomic-parsley/AtomicParsleyBridge.cpp b/libs/atomic-parsley/AtomicParsleyBridge.cpp index 39efc9f..9a69cfe 100644 --- a/libs/atomic-parsley/AtomicParsleyBridge.cpp +++ b/libs/atomic-parsley/AtomicParsleyBridge.cpp @@ -35,10 +35,40 @@ extern "C" { #include <glib/gstdio.h> +#include <glib/gi18n-lib.h> #include "libgtkpod/prefs.h" #include "libgtkpod/charset.h" +#include "libgtkpod/gp_itdb.h" +#include "libgtkpod/gp_private.h" } +#define TITLE "\251nam" +#define ARTIST "\251ART" +#define ALBUM_ARTIST "aART" +#define COMPOSER "\251wrt" +#define COMMENT "\251cmt" +#define YEAR "\251day" +#define ALBUM "\251alb" +#define TRACK_NUM_AND_TOTAL "trkn" +#define DISK_NUM_AND_TOTAL "disk" +#define GROUPING "\251grp" +#define DESCRIPTION "desc" +#define STANDARD_GENRE "gnre" +#define CUSTOM_GENRE "\251gen" +#define TEMPO "tmpo" +#define LYRICS "\251lyr" +#define KEYWORD "keyw" +#define TV_SHOW "tvsh" +#define TV_EPISODE "tven" +#define TV_EPISODE_NO "tves" +#define TV_NETWORK_NAME "tvnn" +#define TV_SEASON_NO "tvsn" +#define MEDIA_TYPE "stik" +#define COMPILATION "cpil" +#define CATEGORY "catg" +#define PODCAST_URL "purl" +#define ARTWORK "covr" + static guint32 mediaTypeTagToMediaType(guint8 media_type) { switch (media_type) { case 0: /* Movie */ @@ -61,6 +91,26 @@ static guint32 mediaTypeTagToMediaType(guint8 media_type) { return 0; } +static guint8 mediaTypeToMediaTypeTag(guint32 media_type) { + switch (media_type) { + case ITDB_MEDIATYPE_MOVIE: /* Movie */ + return 9; + case ITDB_MEDIATYPE_AUDIO: /* Normal */ + return 1; + case ITDB_MEDIATYPE_AUDIOBOOK: /* Audiobook */ + return 2; + case ITDB_MEDIATYPE_MUSICVIDEO: /* Music Video */ + return 6; + case ITDB_MEDIATYPE_TVSHOW: /* TV Show */ + return 10; + case ITDB_MEDIATYPE_EPUB_BOOK: /* Booklet */ + return 11; + case ITDB_MEDIATYPE_RINGTONE: /* Ringtone */ + return 14; + } + return 0; +} + static AtomicInfo *find_atom(const char *meta) { char atomName[100]; @@ -78,14 +128,18 @@ static char* find_atom_value(const char* meta) { return NULL; } -void AP_extract_metadata(const char *path, Track *track) { +/** + * Open and scan the metadata of the m4a/mp4/... file + * and populate the given track. + */ +void AP_extract_metadata(const char *filePath, Track *track) { FILE *mp4File; Trackage *trackage; uint8_t track_cur; gboolean audio_or_video_found = FALSE; - APar_ScanAtoms(path, true); - mp4File = openSomeFile(path, true); + APar_ScanAtoms(filePath, true); + mp4File = openSomeFile(filePath, true); trackage = APar_ExtractDetails(mp4File, SHOW_TRACK_INFO); @@ -111,56 +165,70 @@ void AP_extract_metadata(const char *path, Track *track) { char* value = NULL; // MP4 Title - value = find_atom_value("\251nam"); + value = find_atom_value(TITLE); if (value) { track->title = charset_to_utf8(value); free(value); } // MP4 Artist - value = find_atom_value("\251ART"); + value = find_atom_value(ARTIST); if (value) { track->artist = charset_to_utf8(value); free(value); } // MP4 Album Artist - value = find_atom_value("aART"); + value = find_atom_value(ALBUM_ARTIST); if (value) { track->albumartist = charset_to_utf8(value); free(value); } // MP4 Composer - value = find_atom_value("\251wrt"); + value = find_atom_value(COMPOSER); if (value) { track->composer = charset_to_utf8(value); free(value); } // MP4 Comment - value = find_atom_value("\251cmt"); + value = find_atom_value(COMMENT); if (value) { track->comment = charset_to_utf8(value); free(value); } + // MP4 Description + value = find_atom_value(DESCRIPTION); + if (value) { + track->description = charset_to_utf8(value); + free(value); + } + + // MP4 Keywords + value = find_atom_value(KEYWORD); + if (value) { + track->keywords = charset_to_utf8(value); + free(value); + } + // MP4 Year - value = find_atom_value("\251day"); + value = find_atom_value(YEAR); if (value) { track->year = atoi(value); free(value); } // MP4 Album - value = find_atom_value("\251alb"); + value = find_atom_value(ALBUM); if (value) { track->album = charset_to_utf8(value); free(value); } // MP4 Track No. and Total - value = find_atom_value("trkn"); + value = find_atom_value(TRACK_NUM_AND_TOTAL); if (value) { const char* delimiter = " of "; char *result = NULL; @@ -176,7 +244,7 @@ void AP_extract_metadata(const char *path, Track *track) { } // MP4 Disk No. and Total - value = find_atom_value("disk"); + value = find_atom_value(DISK_NUM_AND_TOTAL); if (value) { const char* delimiter = " of "; char *result = NULL; @@ -192,7 +260,7 @@ void AP_extract_metadata(const char *path, Track *track) { } // MP4 Grouping - value = find_atom_value("\251grp"); + value = find_atom_value(GROUPING); if (value) { track->grouping = charset_to_utf8(value); free(value); @@ -200,13 +268,14 @@ void AP_extract_metadata(const char *path, Track *track) { // MP4 Genre - note: can be either a standard or custom genre // standard genre - value = find_atom_value("gnre"); + value = find_atom_value(STANDARD_GENRE); if (value) { track->genre = charset_to_utf8(value); // Should not free standard genres - } else { + } + else { // custom genre - value = find_atom_value("\251gen"); + value = find_atom_value(CUSTOM_GENRE); if (value) { track->genre = charset_to_utf8(value); free(value); @@ -214,68 +283,83 @@ void AP_extract_metadata(const char *path, Track *track) { } // MP4 Tempo / BPM - value = find_atom_value("tmpo"); + value = find_atom_value(TEMPO); if (value) { track->BPM = atoi(value); free(value); } // MP4 Lyrics - value = find_atom_value("\251lyr"); + value = find_atom_value(LYRICS); if (value) { track->lyrics_flag = 0x01; free(value); } // MP4 TV Show - value = find_atom_value("tvsh"); + value = find_atom_value(TV_SHOW); if (value) { track->tvshow = charset_to_utf8(value); free(value); } // MP4 TV Episode - value = find_atom_value("tven"); + value = find_atom_value(TV_EPISODE); if (value) { track->tvepisode = charset_to_utf8(value); free(value); } // MP4 TV Episode No. - value = find_atom_value("tves"); + value = find_atom_value(TV_EPISODE_NO); if (value) { track->episode_nr = atoi(value); free(value); } // MP4 TV Network - value = find_atom_value("tvnn"); + value = find_atom_value(TV_NETWORK_NAME); if (value) { track->tvnetwork = charset_to_utf8(value); free(value); } // MP4 TV Season No. - value = find_atom_value("tvsn"); + value = find_atom_value(TV_SEASON_NO); if (value) { track->season_nr = atoi(value); free(value); } // MP4 Media Type - value = find_atom_value("stik"); + value = find_atom_value(MEDIA_TYPE); if (value) { track->mediatype = mediaTypeTagToMediaType(atoi(value)); // Should not free standard media types } // MP4 Compilation flag - value = find_atom_value("cpil"); + value = find_atom_value(COMPILATION); if (value) { track->compilation = atoi(value); free(value); } + // MP4 Category + value = find_atom_value(CATEGORY); + if (value) { + track->category = charset_to_utf8(value); + free(value); + } + + // MP4 Podcast URL + value = find_atom_value(PODCAST_URL); + if (value) { + track->podcasturl = g_strdup(value); + free(value); + } + + fprintf(stdout, "Track title = %s\n", track->title); fprintf(stdout, "Track artist = %s\n", track->artist); fprintf(stdout, "Track album artist = %s\n", track->albumartist); @@ -310,14 +394,15 @@ void AP_extract_metadata(const char *path, Track *track) { tmp_file = APar_ExtractAAC_Artwork(info->AtomicNumber, tmp_file_prefix, 1); g_free(tmp_file_prefix); - if (tmp_file && g_file_test (tmp_file, G_FILE_TEST_EXISTS)) { + if (tmp_file && g_file_test(tmp_file, G_FILE_TEST_EXISTS)) { // Set the thumbnail using the tmp file itdb_track_set_thumbnails(track, tmp_file); if (track->artwork) { fprintf(stdout, "Track has artwork"); - } else { + } + else { fprintf(stdout, "No artwork applied to track"); } @@ -333,3 +418,249 @@ void AP_extract_metadata(const char *path, Track *track) { APar_FreeMemory(); } + +/** + * Read any lyrics from the given file + */ +char *AP_read_lyrics(const char *filePath, GError **error) { + APar_ScanAtoms(filePath, true); + openSomeFile(filePath, true); + + char *value = find_atom_value(LYRICS); + + fprintf(stdout, "Value of lyrics is %s\n", value); + + APar_FreeMemory(); + + return value; +} + +static void write_lyrics_internal(const char* lyrics, const char *filePath, GError **error) { + if (!lyrics || strlen(lyrics) == 0) + APar_RemoveAtom("moov.udta.meta.ilst.\251lyr.data", VERSIONED_ATOM, 0); + else { + short lyricsData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.\251lyr.data", lyrics, AtomFlags_Data_Text); + APar_Unified_atom_Put(lyricsData_atom, lyrics, UTF8_iTunesStyle_Unlimited, 0, 0); + } +} + +void AP_write_lyrics(const char *lyrics, const char *filePath, GError **error) { + APar_ScanAtoms(filePath); + write_lyrics_internal(lyrics, filePath, error); +} + +static void set_limited_text_atom_value(const char *meta, const char *value) { + char atomName[100]; + + sprintf(atomName, "moov.udta.meta.ilst.%s.data", meta); + + if (!value || strlen(value) == 0) + APar_RemoveAtom(atomName, VERSIONED_ATOM, 0); + else { + short atom = APar_MetaData_atom_Init(atomName, value, AtomFlags_Data_Text); + APar_Unified_atom_Put(atom, value, UTF8_iTunesStyle_256glyphLimited, 0, 0); + } +} + +/** + * Using the given track, set the metadata of the target + * file + */ +void AP_write_metadata(Track *track, const char *filePath, GError **error) { + ExtraTrackData *etr; + gchar *value; + + g_return_if_fail (track); + + APar_ScanAtoms(filePath); + + if (metadata_style != ITUNES_STYLE) { + gchar *fbuf = charset_to_utf8(filePath); + gtkpod_log_error(error, + g_strdup_printf(_("ERROR %s is not itunes style."), fbuf)); + g_free(fbuf); + return; + } + + // Title + set_limited_text_atom_value(TITLE, track->title); + + // Artist + set_limited_text_atom_value(ARTIST, track->artist); + + // Album Artist + set_limited_text_atom_value(ALBUM_ARTIST, track->albumartist); + + // Genre + APar_MetaData_atomGenre_Set(track->genre); + + // Track Number and Total + if (track->track_nr == 0) { + APar_RemoveAtom("moov.udta.meta.ilst.trkn.data", VERSIONED_ATOM, 0); + } else { + gchar *track_info = g_strdup_printf("%d / %d", track->track_nr, track->tracks); + short tracknumData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.trkn.data", track_info, AtomFlags_Data_Binary); + APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); + APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->track_nr, 16); + APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->tracks, 16); + APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); + g_free(track_info); + } + + // Disk Number and Total + if (track->cd_nr == 0) { + APar_RemoveAtom("moov.udta.meta.ilst.disk.data", VERSIONED_ATOM, 0); + } else { + gchar *disk_info = g_strdup_printf("%d / %d", track->cd_nr, track->cds); + short disknumData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.disk.data", disk_info, AtomFlags_Data_Binary); + APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); + APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->cd_nr, 16); + APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->cds, 16); + g_free(disk_info); + } + + // Comment + set_limited_text_atom_value(COMMENT, track->comment); + + // Year + gchar *yr = NULL; + if (track->year > 0) + yr = g_strdup_printf("%d", track->year); + + set_limited_text_atom_value(YEAR, yr); + + if (yr) + g_free(yr); + + // Lyrics + etr = (ExtraTrackData *) track->userdata; + if (etr) + write_lyrics_internal(etr->lyrics, filePath, error); + + // Composer + set_limited_text_atom_value(COMPOSER, track->composer); + + // Grouping + set_limited_text_atom_value(GROUPING, track->grouping); + + // Description + set_limited_text_atom_value(DESCRIPTION, track->description); + + // TV Network + set_limited_text_atom_value(TV_NETWORK_NAME, track->tvnetwork); + + // TV Show Name + set_limited_text_atom_value(TV_SHOW, track->tvshow); + + // TV Episode + set_limited_text_atom_value(TV_EPISODE, track->tvepisode); + + // Compilation + if (! track->compilation) { + APar_RemoveAtom("moov.udta.meta.ilst.cpil.data", VERSIONED_ATOM, 0); + } else { + //compilation: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t is already accounted for in APar_MetaData_atom_Init + value = g_strdup_printf("%d", track->compilation); + short compilationData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.cpil.data", value, AtomFlags_Data_UInt); + APar_Unified_atom_Put(compilationData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); //a hard coded uint8_t of: 1 is compilation + g_free(value); + } + + // Tempo / BPM + if (! track->BPM) { + APar_RemoveAtom("moov.udta.meta.ilst.tmpo.data", VERSIONED_ATOM, 0); + } else { + //bpm is [0, 0, 0, 0, 0, bpm_value]; BUT that first uint32_t is already accounted for in APar_MetaData_atom_Init + value = g_strdup_printf("%d", track->BPM); + short bpmData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.tmpo.data", value, AtomFlags_Data_UInt); + APar_Unified_atom_Put(bpmData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->BPM, 16); + g_free(value); + } + + // Media Type + guint8 mediaTypeTag = mediaTypeToMediaTypeTag(track->mediatype); + value = g_strdup_printf("%d", track->season_nr); + short stikData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.stik.data", value, AtomFlags_Data_UInt); + APar_Unified_atom_Put(stikData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, mediaTypeTag, 8); + g_free(value); + + // TV Season No. + if (track->season_nr == 0) { + APar_RemoveAtom("moov.udta.meta.ilst.tvsn.data", VERSIONED_ATOM, 0); + } else { + value = g_strdup_printf("%d", track->season_nr); + short tvseasonData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.tvsn.data", value, AtomFlags_Data_UInt); + APar_Unified_atom_Put(tvseasonData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); + APar_Unified_atom_Put(tvseasonData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->season_nr, 16); + g_free(value); + } + + // TV Episode No. + if(track->episode_nr == 0) { + APar_RemoveAtom("moov.udta.meta.ilst.tves.data", VERSIONED_ATOM, 0); + } else { + value = g_strdup_printf("%d", track->episode_nr); + short tvepisodenumData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.tves.data", value, AtomFlags_Data_UInt); + APar_Unified_atom_Put(tvepisodenumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); + APar_Unified_atom_Put(tvepisodenumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, track->episode_nr, 16); + g_free(value); + } + + // Keywords + set_limited_text_atom_value(KEYWORD, track->keywords); + + // Podcast Category + set_limited_text_atom_value(CATEGORY, track->category); + + // Podcast URL + if (! track->podcasturl || strlen(track->podcasturl) == 0) { + APar_RemoveAtom("moov.udta.meta.ilst.purl.data", VERSIONED_ATOM, 0); + } else { + short podcasturlData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.purl.data", track->podcasturl, AtomFlags_Data_Binary); + APar_Unified_atom_Put(podcasturlData_atom, track->podcasturl, UTF8_iTunesStyle_Binary, 0, 0); + } + + // Gapless Playback + if (! track->gapless_track_flag) { + APar_RemoveAtom("moov.udta.meta.ilst.pgap.data", VERSIONED_ATOM, 0); + } else { + value = g_strdup_printf("%d", track->gapless_track_flag); + short gaplessData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.pgap.data", value, AtomFlags_Data_UInt); + APar_Unified_atom_Put(gaplessData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); //a hard coded uint8_t of: 1 is gapl + g_free(value); + } + + GdkPixbuf *pixbuf = (GdkPixbuf*) itdb_artwork_get_pixbuf(track->itdb->device, track->artwork, -1, -1); + if (! pixbuf) { + // Destroy any existing artwork if any + APar_MetaData_atomArtwork_Set("REMOVE_ALL", NULL); + } + else { + gchar *tmp_file = g_build_filename(g_get_tmp_dir(), "ttt.jpg", NULL); + GError *pixbuf_err = NULL; + + gdk_pixbuf_save (pixbuf, tmp_file, "jpeg", &pixbuf_err, "quality", "100", NULL); + + if (!pixbuf_err) { + APar_MetaData_atomArtwork_Set(tmp_file, NULL); + g_remove(tmp_file); + } + else { + gtkpod_log_error(error, + g_strdup_printf(_("ERROR failed to change track file's artwork."))); + g_error_free(pixbuf_err); + return; + } + + g_free(tmp_file); + g_object_unref(pixbuf); + } + + //after all the modifications are enacted on the tree in memory + // THEN write out the changes + APar_DetermineAtomLengths(); + openSomeFile(filePath, true); + APar_WriteFile(filePath, NULL, true); + + APar_FreeMemory(); +} diff --git a/libs/atomic-parsley/AtomicParsleyBridge.h b/libs/atomic-parsley/AtomicParsleyBridge.h index 63ba7bc..196ad20 100644 --- a/libs/atomic-parsley/AtomicParsleyBridge.h +++ b/libs/atomic-parsley/AtomicParsleyBridge.h @@ -31,52 +31,38 @@ #include "libgtkpod/itdb.h" +/** + * Open and scan the metadata of the m4a/mp4/... file + * and populate the given track. + */ #ifdef __cplusplus extern "C" #endif -void AP_extract_metadata(const char *path, Track *track); - +void AP_extract_metadata(const char *filePath, Track *track); - -/* - * Want to use: - * APar_ExtractTrackDetails - * APar_ExtractMovieDetails - * APar_Extract_iods_Info +/** + * Using the given track, set the metadata of the target + * file */ +#ifdef __cplusplus +extern "C" +#endif +void AP_write_metadata(Track *track, const char *filePath, GError **error); -//char *MP4GetMetadataName(const ca, &value); -//MP4GetMetadataArtist(mp4File, &value) && value -//MP4GetMetadataAlbumArtist(mp4File, &value) && value -//MP4GetMetadataComposer(mp4File, &value) && value != NULL) -//MP4GetMetadataComment(mp4File, &value) && value -//MP4GetMetadataReleaseDate(mp4File, &value) && value != NULL) -//MP4GetMetadataAlbum(mp4File, &value) && value -//MP4GetMetadataTrack(mp4File, &numvalue, &numvalue2)) -//MP4GetMetadataDisk(mp4File, &numvalue, &numvalue2)) -//MP4GetMetadataGrouping(mp4File, &value) && value -//MP4GetMetadataGenre(mp4File, &value) && value -//MP4GetMetadataBPM(mp4File, &numvalue)) +/** + * Read any lyrics from the given file + */ +#ifdef __cplusplus +extern "C" +#endif +char *AP_read_lyrics(const char *filePath, GError **error); -//MP4GetMetadataLyrics(mp4File); -// -//MP4GetMetadataTVShow -// -//MP4TagsFetchFunc( mp4tags, mp4File); -//mp4tags->tvShow) -//track->tvshow = g_strdup(mp4tags->tvShow); -//mp4tags->tvEpisodeID) -//track->tvepisode = g_strdup(mp4tags->tvEpisodeID); -//mp4tags->tvNetwork) -//track->tvnetwork = g_strdup(mp4tags->tvNetwork); -//mp4tags->tvSeason) -//track->season_nr = *mp4tags->tvSeason; -//mp4tags->tvEpisode) -//track->episode_nr = *mp4tags->tvEpisode; -//mp4tags->tvEpisode) -//track->mediatype = mediaTypeTagToMediaType(*mp4tags->mediaType); -//MP4TagsFreeFunc( mp4tags); -// -//MP4GetMetadataCoverArt(mp4File, &image_data, &image_data_len, 0)) +/** + * Write lyrics to the file at filePath + */ +#ifdef __cplusplus +extern "C" +#endif +void AP_write_lyrics(const char *lyrics, const char *filePath, GError **error); #endif /* ATOMIC_PARSLEY_BRIDGE_H_ */ diff --git a/plugins/filetype_m4a/Makefile.am b/plugins/filetype_m4a/Makefile.am index a75d807..c4bd199 100644 --- a/plugins/filetype_m4a/Makefile.am +++ b/plugins/filetype_m4a/Makefile.am @@ -32,7 +32,7 @@ plugin_LTLIBRARIES = libfiletype_m4a.la libfiletype_m4a_la_SOURCES = plugin.c plugin.h \ m4afile.c m4afile.h -libfiletype_m4a_la_CFLAGS = -I $(top_srcdir)/libs/atomic-parsley +libfiletype_m4a_la_CFLAGS = -I $(top_builddir)/libs/atomic-parsley libfiletype_m4a_la_LDFLAGS = $(GTKPOD_PLUGIN_LDFLAGS) # Plugin dependencies diff --git a/plugins/filetype_m4a/m4afile.c b/plugins/filetype_m4a/m4afile.c index 8b761f2..894589c 100644 --- a/plugins/filetype_m4a/m4afile.c +++ b/plugins/filetype_m4a/m4afile.c @@ -56,7 +56,7 @@ static void m4a_set_media_type(const gchar *m4aFileName, Track *track) { track->mediatype = ITDB_MEDIATYPE_AUDIOBOOK; track->filetype = g_strdup(_("AAC audio book file")); } - else if (g_ascii_strcasecmp(value, ".mp4") == 0) { + else if (g_ascii_strcasecmp(value, ".m4a") == 0) { track->mediatype = ITDB_MEDIATYPE_MOVIE; track->movie_flag = 0x01; track->filetype = g_strdup(_("MP4 video file")); @@ -80,6 +80,24 @@ Track *m4a_get_file_info(const gchar *m4aFileName, GError **error) { } gboolean m4a_write_file_info(const gchar *filename, Track *track, GError **error) { + AP_write_metadata(track, filename, error); + + return error ? TRUE : FALSE; +} + +gboolean m4a_read_lyrics(const gchar *m4aFileName, gchar **lyrics, GError **error) { + gchar *value = AP_read_lyrics(m4aFileName, error); + *lyrics = value; + return error ? TRUE : FALSE; +} + +gboolean m4a_write_lyrics(const gchar *m4aFileName, const gchar *lyrics, GError **error) { + AP_write_lyrics(lyrics, m4aFileName, error); + + return error ? TRUE : FALSE; +} + +gboolean m4a_read_gapless(const gchar *filename, Track *track, GError **error) { return FALSE; } diff --git a/plugins/filetype_m4a/m4afile.h b/plugins/filetype_m4a/m4afile.h index cd488d1..4387ac8 100644 --- a/plugins/filetype_m4a/m4afile.h +++ b/plugins/filetype_m4a/m4afile.h @@ -33,6 +33,9 @@ Track *m4a_get_file_info (const gchar *m4aFileName, GError **error); gboolean m4a_write_file_info (const gchar *filename, Track *track, GError **error); +gboolean m4a_read_lyrics(const gchar *filename, gchar **lyrics, GError **error); +gboolean m4a_write_lyrics(const gchar *filename, const gchar *lyrics, GError **error); +gboolean m4a_read_gapless(const gchar *filename, Track *track, GError **error); gboolean m4a_can_convert(); gchar *m4a_get_conversion_cmd(); gchar *m4a_get_gain_cmd(); diff --git a/plugins/filetype_m4a/plugin.c b/plugins/filetype_m4a/plugin.c index 2d2090c..c519d64 100644 --- a/plugins/filetype_m4a/plugin.c +++ b/plugins/filetype_m4a/plugin.c @@ -86,9 +86,9 @@ static void m4a_filetype_iface_init(FileTypeInterface *iface) { iface->get_file_info = m4a_get_file_info; iface->write_file_info = m4a_write_file_info; iface->read_soundcheck = filetype_no_soundcheck; - iface->read_lyrics = filetype_no_read_lyrics; - iface->write_lyrics = filetype_no_write_lyrics; - iface->read_gapless = filetype_no_read_gapless; + iface->read_lyrics = m4a_read_lyrics; + iface->write_lyrics = m4a_write_lyrics; + iface->read_gapless = m4a_read_gapless; iface->can_convert = m4a_can_convert; iface->get_conversion_cmd = m4a_get_conversion_cmd; iface->get_gain_cmd = m4a_get_gain_cmd; diff --git a/plugins/filetype_mp4/Makefile.am b/plugins/filetype_mp4/Makefile.am index 114a277..ff1a1c3 100644 --- a/plugins/filetype_mp4/Makefile.am +++ b/plugins/filetype_mp4/Makefile.am @@ -32,7 +32,7 @@ plugin_LTLIBRARIES = libfiletype_mp4.la libfiletype_mp4_la_SOURCES = plugin.c plugin.h \ mp4file.c mp4file.h -libfiletype_mp4_la_CFLAGS = -I $(top_srcdir)/libs/atomic-parsley +libfiletype_mp4_la_CFLAGS = -I $(top_builddir)/libs/atomic-parsley libfiletype_mp4_la_LDFLAGS = $(GTKPOD_PLUGIN_LDFLAGS) # Plugin dependencies diff --git a/plugins/filetype_mp4/mp4file.c b/plugins/filetype_mp4/mp4file.c index b0eb448..e426515 100644 --- a/plugins/filetype_mp4/mp4file.c +++ b/plugins/filetype_mp4/mp4file.c @@ -152,12 +152,31 @@ Track *mp4_get_file_info(const gchar *mp4FileName, GError **error) { track = gp_track_new(); mp4_set_media_type(mp4FileName, track); - AP_extract_metadata(mp4FileName, track); return track; } gboolean mp4_write_file_info(const gchar *mp4FileName, Track *track, GError **error) { + AP_write_metadata(track, mp4FileName, error); + + return error ? TRUE : FALSE; +} + +gboolean mp4_read_lyrics(const gchar *mp4FileName, gchar **lyrics, GError **error) { + char *value = AP_read_lyrics(mp4FileName, error); + + *lyrics = value; + + return error ? TRUE : FALSE; +} + +gboolean mp4_write_lyrics(const gchar *mp4FileName, const gchar *lyrics, GError **error) { + AP_write_lyrics(lyrics, mp4FileName, error); + + return error ? TRUE : FALSE; +} + +gboolean mp4_read_gapless(const gchar *filename, Track *track, GError **error) { return FALSE; } diff --git a/plugins/filetype_mp4/mp4file.h b/plugins/filetype_mp4/mp4file.h index bf3dcdb..1008e3c 100644 --- a/plugins/filetype_mp4/mp4file.h +++ b/plugins/filetype_mp4/mp4file.h @@ -34,5 +34,7 @@ gboolean mp4_write_file_info (const gchar *filename, Track *track, GError **error); Track *mp4_get_file_info (const gchar *name, GError **error); -gboolean mp4_read_soundcheck (const gchar *filename, Track *track, GError **error); +gboolean mp4_read_lyrics(const gchar *filename, gchar **lyrics, GError **error); +gboolean mp4_write_lyrics(const gchar *filename, const gchar *lyrics, GError **error); +gboolean mp4_read_gapless(const gchar *filename, Track *track, GError **error); #endif diff --git a/plugins/filetype_mp4/plugin.c b/plugins/filetype_mp4/plugin.c index ea5f591..9b7cf4d 100644 --- a/plugins/filetype_mp4/plugin.c +++ b/plugins/filetype_mp4/plugin.c @@ -83,9 +83,9 @@ static void mp4_filetype_iface_init(FileTypeInterface *iface) { iface->get_file_info = mp4_get_file_info; iface->write_file_info = mp4_write_file_info; iface->read_soundcheck = filetype_no_soundcheck; - iface->read_lyrics = filetype_no_read_lyrics; - iface->write_lyrics = filetype_no_write_lyrics; - iface->read_gapless = filetype_no_read_gapless; + iface->read_lyrics = mp4_read_lyrics; + iface->write_lyrics = mp4_write_lyrics; + iface->read_gapless = mp4_read_gapless; iface->can_convert = filetype_no_convert; iface->get_conversion_cmd = filetype_no_conversion_cmd; iface->get_gain_cmd = filetype_no_gain_cmd; ------------------------------------------------------------------------------ For Developers, A Lot Can Happen In A Second. Boundary is the first to Know...and Tell You. Monitor Your Applications in Ultra-Fine Resolution. Try it FREE! http://p.sf.net/sfu/Boundary-d2dvs2 _______________________________________________ gtkpod-cvs2 mailing list gtkpod-cvs2@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/gtkpod-cvs2