commit 64f2b51e0e1be5503d83784fc04467f6403bf195
Author: phantomjinx <[email protected]>
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
[email protected]
https://lists.sourceforge.net/lists/listinfo/gtkpod-cvs2