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.
-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
gtkpod-cvs2 mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/gtkpod-cvs2