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

Reply via email to