From: <tmz...@us...> - 2007-06-26 03:33:24
|
Revision: 1591 http://gtkpod.svn.sourceforge.net/gtkpod/?rev=1591&view=rev Author: tmzullinger Date: 2007-06-25 20:33:25 -0700 (Mon, 25 Jun 2007) Log Message: ----------- preliminary support for parsing gapless playback data from mp3 files. Thanks to Michael Tiffany. Modified Paths: -------------- gtkpod/trunk/ChangeLog_detailed gtkpod/trunk/src/file.c gtkpod/trunk/src/misc_track.c gtkpod/trunk/src/mp3file.c gtkpod/trunk/src/mp3file.h Modified: gtkpod/trunk/ChangeLog_detailed =================================================================== --- gtkpod/trunk/ChangeLog_detailed 2007-06-25 20:49:55 UTC (rev 1590) +++ gtkpod/trunk/ChangeLog_detailed 2007-06-26 03:33:25 UTC (rev 1591) @@ -1,3 +1,12 @@ +2007-06-25 Todd Zullinger <tmzullinger at users.sourceforge.net> + + * src/file.c + src/misc_track.c + src/mp3file.c + src/mp3file.h: + preliminary support for parsing gapless playback data from + mp3 files. Thanks to Michael Tiffany. + 2007-06-25 P.G. Richardson <phantom_sf at users.sourceforge.net> * display_itdb.h: added tartwork_changed flag to ExtraTrackData Modified: gtkpod/trunk/src/file.c =================================================================== --- gtkpod/trunk/src/file.c 2007-06-25 20:49:55 UTC (rev 1590) +++ gtkpod/trunk/src/file.c 2007-06-26 03:33:25 UTC (rev 1591) @@ -779,17 +779,20 @@ /* Copy "new" info read from file to an old Track structure. */ -static void copy_new_info (Track *from, Track *to) +/* Return value: TRUE: at least one item was changed. FALSE: track was + unchanged */ +static gboolean copy_new_info (Track *from, Track *to) { ExtraTrackData *efrom, *eto; T_item item; + gboolean changed = FALSE; - g_return_if_fail (from); - g_return_if_fail (to); + g_return_val_if_fail (from, FALSE); + g_return_val_if_fail (to, FALSE); efrom = from->userdata; eto = to->userdata; - g_return_if_fail (efrom); - g_return_if_fail (eto); + g_return_val_if_fail (efrom, FALSE); + g_return_val_if_fail (eto, FALSE); for (item=0; item<T_ITEM_NUM; ++item) { @@ -837,7 +840,7 @@ case T_SEASON_NR: case T_EPISODE_NR: case T_GROUPING: - track_copy_item (from, to, item); + changed |= track_copy_item (from, to, item); break; case T_CATEGORY: /* not implemented from tags */ @@ -856,27 +859,52 @@ /* not applicable */ break; case T_ITEM_NUM: - g_return_if_reached (); + g_return_val_if_reached (FALSE); } } - g_free (eto->thumb_path_locale); - eto->thumb_path_locale = g_strdup (efrom->thumb_path_locale); + if ((eto->charset == NULL) || (strcmp (efrom->charset, eto->charset) != 0)) + { + g_free (eto->charset); + eto->charset = g_strdup (efrom->charset); + changed = TRUE; + } - g_free (eto->pc_path_locale); - eto->pc_path_locale = g_strdup (efrom->pc_path_locale); - - g_free (eto->charset); - eto->charset = g_strdup (efrom->charset); - itdb_artwork_free (to->artwork); to->artwork = itdb_artwork_duplicate (from->artwork); - to->artwork_size = from->artwork_size; - to->artwork_count = from->artwork_count; - to->has_artwork = from->has_artwork; + if ((to->artwork_size != from->artwork_size) || + (to->artwork_count != from->artwork_count) || + (to->has_artwork != from->has_artwork)) + { /* FIXME -- artwork might have changed nevertheless */ + changed = TRUE; + to->artwork_size = from->artwork_size; + to->artwork_count = from->artwork_count; + to->has_artwork = from->has_artwork; + } - to->lyrics_flag = from->lyrics_flag; - to->movie_flag = from->movie_flag; + if ((to->lyrics_flag != from->lyrics_flag) || + (to->movie_flag != from->movie_flag)) + { + changed = TRUE; + to->lyrics_flag = from->lyrics_flag; + to->movie_flag = from->movie_flag; + } + + if ((to->pregap != from->pregap) || + (to->postgap != from->postgap) || + (to->samplecount != from->samplecount) || + (to->gapless_data != from->gapless_data) || + (to->gapless_track_flag != from->gapless_track_flag)) + { + changed = TRUE; + to->pregap = from->pregap; + to->postgap = from->postgap; + to->samplecount = from->samplecount; + to->gapless_data = from->gapless_data; + to->gapless_track_flag = from->gapless_track_flag; + } + + return changed; } /* Updates mserv data (rating only) of @track using filename @name to @@ -1226,8 +1254,15 @@ /* Set modification date to the files modified date */ nti->time_modified = enti->mtime; - /* Set added date to *now* */ - nti->time_added = time (NULL); + /* Set added date to *now* (unless orig_track is present) */ + if (orig_track) + { + nti->time_added = orig_track->time_added; + } + else + { + nti->time_added = time (NULL); + } /* Make sure all strings are initialized -- that way we don't have to worry about it when we are handling the @@ -1241,13 +1276,11 @@ if (orig_track) { /* we need to copy all information over to the original * track */ - guint32 time_added = orig_track->time_added; + ExtraTrackData *eorigtr=orig_track->userdata; - copy_new_info (nti, orig_track); + g_return_val_if_fail (eorigtr, NULL); - /* restore time_added */ - if (time_added != 0) - orig_track->time_added = time_added; + eorigtr->tchanged = copy_new_info (nti, orig_track); track = orig_track; itdb_track_free (nti); @@ -1591,7 +1624,7 @@ "gp_duplicate_remove (NULL, (void *)-1)"*/ void update_track_from_file (iTunesDB *itdb, Track *track) { - ExtraTrackData *etr; + ExtraTrackData *oetr; Track *oldtrack; gchar *prefs_charset = NULL; gchar *trackpath = NULL; @@ -1600,27 +1633,27 @@ g_return_if_fail (itdb); g_return_if_fail (track); - etr = track->userdata; - g_return_if_fail (etr); + oetr = track->userdata; + g_return_if_fail (oetr); /* remember size of track on iPod */ if (track->transferred) oldsize = track->size; else oldsize = 0; /* remember if charset was set */ - if (etr->charset) charset_set = TRUE; + if (oetr->charset) charset_set = TRUE; else charset_set = FALSE; if (!prefs_get_int("update_charset") && charset_set) { /* we should use the initial charset for the update */ prefs_charset = prefs_get_string("charset"); /* use the charset used when first importing the track */ - prefs_set_string("charset", etr->charset); + prefs_set_string("charset", oetr->charset); } trackpath = get_file_name_from_source (track, SOURCE_PREFER_LOCAL); - if (!(etr->pc_path_locale && *etr->pc_path_locale)) + if (!(oetr->pc_path_locale && *oetr->pc_path_locale)) { /* no path available */ if (trackpath) { @@ -1638,7 +1671,7 @@ } } } - else if (!g_file_test (etr->pc_path_locale, G_FILE_TEST_EXISTS)) + else if (!g_file_test (oetr->pc_path_locale, G_FILE_TEST_EXISTS)) { if (trackpath) { @@ -1658,14 +1691,16 @@ } if (trackpath && get_track_info_from_file (trackpath, track)) - { /* update successfull */ + { /* update successful */ + ExtraTrackData *netr = track->userdata; + /* remove track from sha1 hash and reinsert it (hash value may have changed!) */ - gchar *oldhash = etr->sha1_hash; + gchar *oldhash = oetr->sha1_hash; sha1_track_remove (track); /* need to remove the old value manually! */ - etr->sha1_hash = NULL; + oetr->sha1_hash = NULL; oldtrack = sha1_track_exists_insert (itdb, track); if (oldtrack) { /* track exists, remove old track and register the new version */ @@ -1682,9 +1717,9 @@ name_on_ipod = get_file_name_from_source (track, SOURCE_IPOD); if (name_on_ipod && (strcmp (name_on_ipod, trackpath) != 0)) { /* trackpath is not on the iPod */ - if (oldhash && etr->sha1_hash) + if (oldhash && oetr->sha1_hash) { /* do we really have to copy the track again? */ - if (strcmp (oldhash, etr->sha1_hash) != 0) + if (strcmp (oldhash, oetr->sha1_hash) != 0) { transfer_again = TRUE; } @@ -1718,25 +1753,27 @@ /* mark the track for deletion on the ipod */ mark_track_for_deletion (itdb, new_track); /* reschedule conversion/transfer of track */ + file_convert_add_track (track); - data_changed (itdb); + netr->tchanged = TRUE; } g_free (name_on_ipod); } - else + + /* notify display model */ + if (netr->tchanged) { + pm_track_changed (track); data_changed (itdb); + netr->tchanged = FALSE; } - - /* notify display model */ - pm_track_changed (track); display_updated (track, NULL); g_free (oldhash); } else if (trackpath) { /* update not successful -- log this track for later display */ - display_non_updated (track, _("update failed (format no supported?)")); + display_non_updated (track, _("update failed (format not supported?)")); } if (!prefs_get_int("update_charset") && charset_set) Modified: gtkpod/trunk/src/misc_track.c =================================================================== --- gtkpod/trunk/src/misc_track.c 2007-06-25 20:49:55 UTC (rev 1590) +++ gtkpod/trunk/src/misc_track.c 2007-06-26 03:33:25 UTC (rev 1591) @@ -946,11 +946,17 @@ gboolean changed = FALSE; const gchar *fritem; gchar **toitem_ptr; + ExtraTrackData *efrtr, *etotr; g_return_val_if_fail (frtrack, FALSE); g_return_val_if_fail (totrack, FALSE); g_return_val_if_fail ((item > 0) && (item < T_ITEM_NUM), FALSE); + efrtr = frtrack->userdata; + etotr = totrack->userdata; + g_return_val_if_fail (efrtr, FALSE); + g_return_val_if_fail (etotr, FALSE); + if (frtrack == totrack) return FALSE; switch (item) @@ -1000,6 +1006,27 @@ changed = TRUE; } } + /* handle items that have two entries */ + if (item == T_PC_PATH) + { + if ((etotr->pc_path_locale == NULL) || + (strcmp (efrtr->pc_path_locale, etotr->pc_path_locale) != 0)) + { + g_free (etotr->pc_path_locale); + etotr->pc_path_locale = g_strdup (efrtr->pc_path_locale); + changed = TRUE; + } + } + if (item == T_THUMB_PATH) + { + if ((etotr->thumb_path_locale == NULL) || + (strcmp (efrtr->thumb_path_locale, etotr->thumb_path_locale) != 0)) + { + g_free (etotr->thumb_path_locale); + etotr->thumb_path_locale = g_strdup (efrtr->thumb_path_locale); + changed = TRUE; + } + } break; case T_IPOD_ID: if (frtrack->id != totrack->id) Modified: gtkpod/trunk/src/mp3file.c =================================================================== --- gtkpod/trunk/src/mp3file.c 2007-06-25 20:49:55 UTC (rev 1590) +++ gtkpod/trunk/src/mp3file.c 2007-06-26 03:33:25 UTC (rev 1591) @@ -1,4 +1,4 @@ -/* Time-stamp: <2007-06-23 01:34:40 jcs> +/* Time-stamp: <2007-06-26 00:39:11 jcs> | | Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net> | Part of the gtkpod project. @@ -53,6 +53,7 @@ */ typedef struct _File_Tag File_Tag; typedef struct _GainData GainData; +typedef struct _GaplessData GaplessData; struct _File_Tag { @@ -98,6 +99,13 @@ gboolean audiophile_gain_set;/* has the audiophile gain been set? */ }; +struct _GaplessData +{ + guint32 pregap; /* number of pregap samples */ + guint64 samplecount; /* number of actual music samples */ + guint32 postgap; /* number of postgap samples */ + guint32 gapless_data; /* number of bytes from the first sync frame to the 8th to last frame */ +}; /* This code is taken from the mp3info code. Only the code needed for * the playlength calculation has been extracted */ @@ -2139,7 +2147,7 @@ * * Returns TRUE if the soundcheck field could be set. */ -gboolean mp3_read_soundcheck (gchar *path, Track *track) +gboolean mp3_read_soundcheck (gchar *path, Track *track) { GainData gd; @@ -2170,6 +2178,251 @@ + + + +/* mp3 slot size in bytes */ +int slotsize[3] = {4,1,1}; /* layer 1, layer 2, layer 3 */ + +int samplesperframe[2][3] = { + { /* MPEG 2.0 */ + 384,1152,576 /* layer 1, layer 2, layer 3 */ + }, + + { /* MPEG 1.0 */ + 384,1152,1152 /* layer 1, layer 2, layer 3 */ + } +}; + + +/* + * mp3_get_track_lame_gapless - read the specified file and scan for LAME Tag + * gapless information. + * + * @path: localtion of the file + * @track: structure holding track information + * + * TODO: Split off non-LAME stuff (samplecount, gapless_data) to a separate function since it's generic + */ +gboolean mp3_get_track_lame_gapless (gchar *path, GaplessData *gd) +{ + FILE *file = NULL; + char buf[4], version[5]; + unsigned char ubuf[4]; + int sideinfo; + int i; + + g_return_val_if_fail (gd, FALSE); + + if (!path) + goto gp_fail; + + file = fopen (path, "rb"); + + if (!file) + goto gp_fail; + + /* use get_first_header() to seek to the first mp3 header */ + MP3Info *mp3i = NULL; + mp3i = g_malloc0 (sizeof (MP3Info)); + mp3i->filename = path; + mp3i->file = file; + get_mp3_info (mp3i); + get_first_header (mp3i, 0); + + int xing_header_offset = ftell (file); + + MP3Header h; + if (!get_header (file, &h)) + goto gp_fail; + + int mysamplesperframe = samplesperframe[h.version & 1][3 - h.layer]; + + /* Determine offset of Xing header based on sideinfo size */ + if (h.version & 0x1) + { + sideinfo = (h.mode & 0x2) ? + SIDEINFO_MPEG1_MONO : SIDEINFO_MPEG1_MULTI; + } + else + { + sideinfo = (h.mode & 0x2) ? + SIDEINFO_MPEG2_MONO : SIDEINFO_MPEG2_MULTI; + } + + if (fseek (file, sideinfo, SEEK_CUR) || + (fread (&buf[0], 1, 4, file) != 4)) + goto gp_fail; + + /* Is this really a Xing or Info Header? + * FIXME: Apparently (according to madplay sources) there is a different + * possible location for the Xing header ("due to an unfortunate + * historical event"). I do not thing we need to care though since + * ReplayGain information is only contained in recent files. */ + if (strncmp (buf, "Xing", 4) && strncmp (buf, "Info", 4)) + goto gp_fail; + + /* Determine the offset of the LAME tag based on contents of the Xing header */ + int flags; + fread (&flags, 4, 1, file); + int toskip = 0; + if (flags | 0x1) + { /* frames field is set */ + toskip += 4; + } + if (flags | 0x2) + { /* bytes field is set */ + toskip += 4; + } + if (flags | 0x4) + { /* TOC field is set */ + toskip += 100; + } + if (flags | 0x8) + { /* quality field is set */ + toskip += 4; + } + + /* Check for LAME Tag */ + if (fseek (file, toskip, SEEK_CUR) || (fread (&buf[0], 1, 4, file) != 4)) + goto gp_fail; + if (strncmp (buf, "LAME", 4)) + goto gp_fail; + + /* Check LAME Version */ + if (fread (version, 1, 5, file) != 5) + goto gp_fail; + + /* XXX skip old LAME versions, or just assume that pre/postgap + * turn out zeros anyway, or check the CRC to vaidate the tag? */ + + gboolean cbr = FALSE; + if (fread (ubuf, 1, 1, file) != 1) + goto gp_fail; + + if ((ubuf[0] & 0xf) == 0x1) + cbr = TRUE; + + if (fseek (file, 0xB, SEEK_CUR) || (fread (ubuf, 1, 4, file) != 4)) + goto gp_fail; + + /* set pregap and postgap directly from LAME header */ + gd->pregap = (ubuf[0] << 4) + (ubuf[1] >> 4); + gd->postgap = ((ubuf[1] & 0xf) << 8) + ubuf[2]; + + /* jump the end of the frame with the xing header */ + if (fseek (file, xing_header_offset + frame_length (&h), SEEK_SET)) + goto gp_fail; + + /* counts bytes from the start of the 1st sync frame */ + int totaldatasize = frame_length (&h); + + /* keeps track of the last 8 frame sizes */ + int lastframes[8]; + + /* counts number of music frames */ + int totalframes = 0; + + /* quickly parse the file, reading only frame headers */ + int l = 0; + while ((l = get_header (file, &h)) != 0) + { + for (i = 7; i > 0; i--) + { + lastframes[i] = lastframes[i - 1]; + } + lastframes[0] = l; + totaldatasize += l; + totalframes++; + + if (fseek (file, l - FRAME_HEADER_SIZE, SEEK_CUR)) + goto gp_fail; + + } + + int finaleight = 0; + for (i = 0; i < 8; i++) + { + finaleight += lastframes[i]; + } + + if (cbr) + totalframes++; + + /* all but last eight frames */ + gd->gapless_data = totaldatasize - finaleight; + /* total samples minus pre/postgap */ + gd->samplecount = totalframes * mysamplesperframe - gd->pregap - gd->postgap; + + return TRUE; + + + gp_fail: + if (file) + fclose (file); + return FALSE; + +} + + + +/** + * mp3_read_gapless: + * + * try to read the gapless values from the LAME Tag and set + * the track's pregap, postgap, samplecount, and gapless_data fields + * accordingly. + * + * @path: location of the file + * @track: structure holding track information + * + * The function always rereads the data from the file. + * + * Returns TRUE if all four gapless fields could be + * set. etrack->tchanged is set to TRUE if data has been changed, + * FALSE otherwise. + */ +gboolean mp3_read_gapless (char *path, Track *track) +{ + GaplessData gd; + ExtraTrackData *etr; + + g_return_val_if_fail (track, FALSE); + + etr = track->userdata; + + memset (&gd, 0, sizeof (GaplessData)); + + gd.pregap = 0; + gd.samplecount = 0; + gd.postgap = 0; + gd.gapless_data = 0; + + mp3_get_track_lame_gapless (path, &gd); + + etr->tchanged = FALSE; + + if ((gd.pregap) && (gd.samplecount) && (gd.postgap) && (gd.gapless_data)) + { + if ((track->pregap != gd.pregap) || + (track->samplecount != gd.samplecount) || + (track->postgap != gd.postgap) || + (track->gapless_data != gd.gapless_data) || + (track->gapless_track_flag == FALSE)) + { + etr->tchanged = TRUE; + track->pregap = gd.pregap; + track->samplecount = gd.samplecount; + track->postgap = gd.postgap; + track->gapless_data = gd.gapless_data; + track->gapless_track_flag = TRUE; + } + } + return FALSE; +} + + + /* ---------------------------------------------------------------------- From here starts original gtkpod code @@ -2385,6 +2638,17 @@ mp3_read_soundcheck (name, track); + mp3_read_gapless (name, track); + +#if LOCALDEBUG + printf("%s\n", name); + printf("\tpregap: %i\n", track->pregap); + printf("\tpostgap: %i\n", track->postgap); + printf("\tsamplecount: %li\n", track->samplecount); + printf("\tgaplessdata: %i\n", track->gapless_data); +#endif + + /* Get additional info (play time and bitrate */ if (mp3i) { Modified: gtkpod/trunk/src/mp3file.h =================================================================== --- gtkpod/trunk/src/mp3file.h 2007-06-25 20:49:55 UTC (rev 1590) +++ gtkpod/trunk/src/mp3file.h 2007-06-26 03:33:25 UTC (rev 1591) @@ -35,5 +35,6 @@ gboolean mp3_write_file_info (gchar *filename, Track *track); Track *mp3_get_file_info (gchar *name); gboolean mp3_read_soundcheck (gchar *path, Track *track); +gboolean mp3_read_gapless (gchar *path, Track *track); #endif This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |