[RFC] MP4 tag handler using taglib C++ API



This is very early version, it uses a GIO wrapper and the C++ API from
taglib. It also includes some quirks around taglib incompleteness. Tested
with taglib 1.8.

This aims to fix Bug 701506.
---
 Makefile.am   |  10 +-
 src/easytag.c |  18 +--
 src/picture.h |   2 +
 src/taglib.cc | 494 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 513 insertions(+), 11 deletions(-)
 create mode 100644 src/taglib.cc

diff --git a/Makefile.am b/Makefile.am
index 56fdf18..271eca2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,10 +24,16 @@ easytag_CPPFLAGS = \
        $(DEPRECATED_CPPFLAGS) \
        -DLOCALEDIR=\"$(localedir)\"
 
+easytag_CXXFLAGS = \
+       $(WARN_CFLAGS) \
+       $(EASYTAG_CFLAGS)
+
 easytag_CFLAGS = \
        $(WARN_CFLAGS) \
        $(EASYTAG_CFLAGS)
 
+#src/mp4_tag.c
+
 easytag_SOURCES = \
        src/id3lib/c_wrapper.cpp \
        src/libapetag/apetaglib.c \
@@ -56,7 +62,6 @@ easytag_SOURCES = \
        src/monkeyaudio_header.c \
        src/mpeg_header.c \
        src/mp4_header.c \
-       src/mp4_tag.c \
        src/musepack_header.c \
        src/ogg_header.c \
        src/ogg_tag.c \
@@ -70,7 +75,8 @@ easytag_SOURCES = \
        src/vcedit.c \
        src/wavpack_header.c \
        src/wavpack_tag.c \
-       src/win32/win32dep.c
+       src/win32/win32dep.c \
+       src/taglib.cc
 
 easytag_headers = \
        src/id3lib/id3_bugfix.h \
diff --git a/src/easytag.c b/src/easytag.c
index a03478b..f5997d5 100644
--- a/src/easytag.c
+++ b/src/easytag.c
@@ -4125,19 +4125,19 @@ void Tag_Area_Display_Controls (ET_File *ETFile)
 
 #ifdef ENABLE_MP4
         case MP4_TAG:
-            gtk_widget_hide(GTK_WIDGET(DiscNumberLabel));
-            gtk_widget_hide(GTK_WIDGET(DiscNumberEntry));
-            gtk_widget_hide(GTK_WIDGET(ComposerLabel));
-            gtk_widget_hide(GTK_WIDGET(ComposerEntry));
+            gtk_widget_show(GTK_WIDGET(DiscNumberLabel));
+            gtk_widget_show(GTK_WIDGET(DiscNumberEntry));
+            gtk_widget_show(GTK_WIDGET(ComposerLabel));
+            gtk_widget_show(GTK_WIDGET(ComposerEntry));
             gtk_widget_hide(GTK_WIDGET(OrigArtistLabel));
             gtk_widget_hide(GTK_WIDGET(OrigArtistEntry));
-            gtk_widget_hide(GTK_WIDGET(CopyrightLabel));
-            gtk_widget_hide(GTK_WIDGET(CopyrightEntry));
+            gtk_widget_show(GTK_WIDGET(CopyrightLabel));
+            gtk_widget_show(GTK_WIDGET(CopyrightEntry));
             gtk_widget_hide(GTK_WIDGET(URLLabel));
             gtk_widget_hide(GTK_WIDGET(URLEntry));
-            gtk_widget_hide(GTK_WIDGET(EncodedByLabel));
-            gtk_widget_hide(GTK_WIDGET(EncodedByEntry));
-            et_tag_notebook_hide_images_tab (TagNoteBook);
+            gtk_widget_show(GTK_WIDGET(EncodedByLabel));
+            gtk_widget_show(GTK_WIDGET(EncodedByEntry));
+            et_tag_notebook_show_images_tab (TagNoteBook);
             break;
 #endif
 
diff --git a/src/picture.h b/src/picture.h
index 14224fd..caf5616 100644
--- a/src/picture.h
+++ b/src/picture.h
@@ -24,6 +24,7 @@
 
 #include "et_core.h"
 
+G_BEGIN_DECLS
 
 /***************
  * Declaration *
@@ -126,5 +127,6 @@ const gchar   *Picture_Mime_Type_String (Picture_Format format);
 gboolean Picture_Entry_View_Button_Pressed (GtkTreeView *treeview, GdkEventButton *event, gpointer data);
 gboolean Picture_Entry_View_Key_Pressed    (GtkTreeView *treeview, GdkEvent *event, gpointer data);
 
+G_END_DECLS
 
 #endif /* __PICTURE_H__ */
diff --git a/src/taglib.cc b/src/taglib.cc
new file mode 100644
index 0000000..d0c9b8d
--- /dev/null
+++ b/src/taglib.cc
@@ -0,0 +1,494 @@
+/* EasyTAG - tag editor for audio files
+ * Copyright (C) 2014 Santtu Lakkala <inz inz fi>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <tiostream.h>
+#include <mp4file.h>
+#include <tpropertymap.h>
+#include <cstring>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+using std::strlen;
+
+#include "mp4_tag.h"
+#include "mp4_header.h"
+#include "et_core.h"
+#include "picture.h"
+
+class GIO_InputStream : public TagLib::IOStream
+{
+public:
+    GIO_InputStream ()
+    {
+    }
+
+    GIO_InputStream (GFile *file_) : file ((GFile *)g_object_ref (gpointer (file_))),
+    filename (g_file_get_uri (file))
+    {
+        stream = g_file_read (file, NULL, NULL);
+    }
+
+    virtual ~GIO_InputStream ()
+    {
+        g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL);
+        g_free (filename);
+        g_object_unref (G_OBJECT (file));
+    }
+
+    virtual TagLib::FileName name () const
+    {
+        return TagLib::FileName (filename);
+    }
+
+    virtual TagLib::ByteVector readBlock (ulong length)
+    {
+        TagLib::ByteVector rv (length, 0);
+        rv = rv.mid (0, g_input_stream_read (G_INPUT_STREAM (stream),
+                                             (void *)rv.data (), length,
+                                             NULL, NULL));
+
+        return rv;
+    }
+
+    virtual void writeBlock (const TagLib::ByteVector &data)
+    {
+        g_warning ("Trying to write to read-only file!");
+    }
+
+    virtual void insert (const TagLib::ByteVector &data, ulong start = 0, ulong replace = 0)
+    {
+        g_warning ("Trying to write to read-only file!");
+    }
+
+    virtual void removeBlock (ulong start = 0, ulong length = 0)
+    {
+        g_warning ("Trying to write to read-only file!");
+    }
+
+    virtual bool readOnly () const
+    {
+        return true;
+    }
+
+    virtual bool isOpen () const
+    {
+        return !!stream;
+    }
+
+    virtual void seek (long offset, TagLib::IOStream::Position p = TagLib::IOStream::Beginning)
+    {
+        GSeekType type;
+        switch (p) {
+        case TagLib::IOStream::Beginning:
+            type = G_SEEK_SET;
+            break;
+        case TagLib::IOStream::Current:
+            type = G_SEEK_CUR;
+            break;
+        case TagLib::IOStream::End:
+            type = G_SEEK_END;
+            break;
+        default:
+            g_warning ("Unknown seek");
+            return;
+        }
+
+        g_seekable_seek (G_SEEKABLE (stream), offset, type, NULL, NULL);
+    }
+
+    virtual void clear ()
+    {
+    }
+
+    virtual long tell () const
+    {
+        return g_seekable_tell (G_SEEKABLE (stream));
+    }
+
+    virtual long length ()
+    {
+        long rv;
+        GFileInfo *info = g_file_input_stream_query_info (stream,
+                                                          G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                                          NULL, NULL);
+        rv = g_file_info_get_size (info);
+        g_object_unref (info);
+
+        return rv;
+    }
+
+    virtual void truncate (long length)
+    {
+        g_warning ("Trying to truncate read-only file");
+    }
+
+private:
+    GFile *file;
+    GFileInputStream *stream;
+    char *filename;
+};
+
+
+class GIO_IOStream : public TagLib::IOStream
+{
+public:
+    GIO_IOStream ()
+    {
+    }
+
+    GIO_IOStream (GFile *file_) : file (file_), filename (g_file_get_uri (file_))
+    {
+        stream = g_file_open_readwrite (file, NULL, NULL);
+    }
+
+    virtual ~GIO_IOStream ()
+    {
+        g_io_stream_close (G_IO_STREAM (stream), NULL, NULL);
+        g_free (filename);
+        g_object_unref (G_OBJECT (file));
+    }
+
+    virtual TagLib::FileName name () const
+    {
+        return TagLib::FileName (filename);
+    }
+
+    virtual TagLib::ByteVector readBlock (ulong length)
+    {
+        TagLib::ByteVector rv (length, 0);
+        GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (stream));
+        rv = rv.mid (0, g_input_stream_read (istream,
+                                             (void *)rv.data (), length,
+                                             NULL, NULL));
+
+        return rv;
+    }
+
+    virtual void writeBlock (const TagLib::ByteVector &data)
+    {
+        GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM (stream));
+        g_output_stream_write (ostream, data.data (), data.size (),
+                               NULL, NULL);
+    }
+
+    virtual void insert (const TagLib::ByteVector &data, ulong start = 0, ulong replace = 0)
+    {
+        if (data.size () == replace)
+        {
+            seek (start, TagLib::IOStream::Beginning);
+            writeBlock (data);
+            return;
+        }
+        else if (data.size () < replace)
+        {
+            removeBlock (start, replace - data.size());
+            seek (start);
+            writeBlock (data);
+            return;
+        }
+
+        GFileIOStream *tstr;
+        GFile *tmp = g_file_new_tmp("easytag-XXXXXX", &tstr, NULL);
+        char *buffer[4096];
+        gsize r;
+        GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM (tstr));
+        GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (stream));
+
+        seek(0);
+        while (g_input_stream_read_all (istream, buffer, MIN(sizeof(buffer), start), &r,
+                                        NULL, NULL) && r > 0)
+        {
+            gsize w;
+            g_output_stream_write_all (ostream, buffer, r, &w, NULL, NULL);
+            if (w != r)
+                g_warning ("Unable to write all bytes");
+            start -= r;
+            g_warning("Wrote %lu bytees of %lu: %.*s", r, start, (int)r, buffer);
+        }
+
+        g_output_stream_write (ostream, data.data (), data.size (),
+                               NULL, NULL);
+        seek(replace, TagLib::IOStream::Current);
+        while (g_input_stream_read_all (istream, buffer, sizeof(buffer), &r,
+                                        NULL, NULL) && r > 0)
+        {
+            gsize w;
+            g_output_stream_write_all (ostream, buffer, r, &w, NULL, NULL);
+            if (w != r)
+                g_warning ("Unable to write all bytes");
+        }
+
+        g_io_stream_close (G_IO_STREAM (tstr), NULL, NULL);
+        g_io_stream_close (G_IO_STREAM (stream), NULL, NULL);
+        g_file_move (tmp, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL);
+        g_object_unref (gpointer(tmp));
+        stream = g_file_open_readwrite (file, NULL, NULL);
+    }
+
+    virtual void removeBlock (ulong start = 0, ulong len = 0)
+    {
+        if (start + len >= (ulong)length()) {
+            truncate(start);
+            return;
+        }
+
+        char *buffer[4096];
+        gsize r;
+        GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (stream));
+        GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM (stream));
+        seek(start + len);
+        while (g_input_stream_read_all (istream, buffer, sizeof(buffer), &r,
+                                        NULL, NULL) && r > 0) {
+            gsize w;
+            seek(start);
+            g_output_stream_write_all (ostream, buffer, r, &w, NULL, NULL);
+            if (w != r)
+                g_warning("Unable to write all bytes");
+            start += r;
+            seek(start + len);
+        }
+        truncate(start);
+    }
+
+    virtual bool readOnly () const
+    {
+        return !stream;
+    }
+
+    virtual bool isOpen () const
+    {
+        return !!stream;
+    }
+
+    virtual void seek (long offset, TagLib::IOStream::Position p = TagLib::IOStream::Beginning)
+    {
+        GSeekType type;
+        switch (p) {
+        case TagLib::IOStream::Beginning:
+            type = G_SEEK_SET;
+            break;
+        case TagLib::IOStream::Current:
+            type = G_SEEK_CUR;
+            break;
+        case TagLib::IOStream::End:
+            type = G_SEEK_END;
+            break;
+        default:
+            g_warning ("Unknown seek");
+            return;
+        }
+
+        g_seekable_seek (G_SEEKABLE (stream), offset, type, NULL, NULL);
+    }
+
+    virtual void clear ()
+    {
+    }
+
+    virtual long tell () const
+    {
+        return g_seekable_tell (G_SEEKABLE (stream));
+    }
+
+    virtual long length ()
+    {
+        long rv;
+        GFileInfo *info = g_file_io_stream_query_info (stream,
+                                                       G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                                       NULL, NULL);
+        rv = g_file_info_get_size (info);
+        g_object_unref (info);
+
+        return rv;
+    }
+
+    virtual void truncate (long length)
+    {
+        g_seekable_truncate (G_SEEKABLE (stream), length, NULL, NULL);
+    }
+
+private:
+    GFile *file;
+    GFileIOStream *stream;
+    char *filename;
+};
+
+static struct {
+    const char *field;
+    size_t offset;
+} fields[] = {
+    { "TITLE", G_STRUCT_OFFSET(File_Tag, title) },
+    { "ARTIST", G_STRUCT_OFFSET(File_Tag, artist) },
+    { "ALBUMARTIST", G_STRUCT_OFFSET(File_Tag, album_artist) },
+    { "ALBUM", G_STRUCT_OFFSET(File_Tag, album) },
+    { "DISCNUMBER", G_STRUCT_OFFSET(File_Tag, disc_number) },
+    { "DISCTOTAL", G_STRUCT_OFFSET(File_Tag, disc_total) },
+    { "DATE", G_STRUCT_OFFSET(File_Tag, year) },
+    { "TRACKNUMBER", G_STRUCT_OFFSET(File_Tag, track) },
+    { "TRACKTOTAL", G_STRUCT_OFFSET(File_Tag, track_total) },
+    { "GENRE", G_STRUCT_OFFSET(File_Tag, genre) },
+    { "COMMENT", G_STRUCT_OFFSET(File_Tag, comment) },
+    { "COMPOSER", G_STRUCT_OFFSET(File_Tag, composer) },
+    { "PERFORMER", G_STRUCT_OFFSET(File_Tag, orig_artist) },
+    { "COPYRIGHT", G_STRUCT_OFFSET(File_Tag, copyright) },
+    { "CONTACT", G_STRUCT_OFFSET(File_Tag, url) },
+    { "ENCODED-BY", G_STRUCT_OFFSET(File_Tag, encoded_by) },
+};
+
+static struct {
+    const char *field;
+    size_t offset;
+} item_fields[] = {
+    { "aART", G_STRUCT_OFFSET(File_Tag, album_artist) },
+    /* { "disk", G_STRUCT_OFFSET(File_Tag, disc_number) }, */
+    { "\251wrt", G_STRUCT_OFFSET(File_Tag, composer) },
+    /* { "PERFORMER", G_STRUCT_OFFSET(File_Tag, orig_artist) }, */
+    { "cprt", G_STRUCT_OFFSET(File_Tag, copyright) },
+    /* { "CONTACT", G_STRUCT_OFFSET(File_Tag, url) }, */
+    { "\251too", G_STRUCT_OFFSET(File_Tag, encoded_by) },
+};
+
+gboolean Mp4tag_Read_File_Tag  (gchar *filename, File_Tag *FileTag)
+{
+    GFile *f = g_file_new_for_path (filename);
+    GIO_InputStream *stream = new GIO_InputStream (f);
+
+    TagLib::MP4::File *a = new TagLib::MP4::File (stream, false);
+    TagLib::MP4::Tag *tag = a->tag ();
+    TagLib::PropertyMap props = tag->properties ();
+
+    size_t i;
+
+    for (i = 0; i < sizeof(fields) / sizeof(*fields); i++)
+    {
+        TagLib::StringList l = props[fields[i].field];
+
+        if (l.size())
+            G_STRUCT_MEMBER(gchar *, FileTag, fields[i].offset) = g_strdup(l[0].toCString(true));
+        else
+            G_STRUCT_MEMBER(gchar *, FileTag, fields[i].offset) = NULL;
+    }
+
+    TagLib::MP4::ItemListMap &items = tag->itemListMap ();
+    TagLib::MP4::ItemListMap::Iterator item;
+
+    for (i = 0; i < sizeof(item_fields) / sizeof(*item_fields); i++)
+    {
+        if (G_STRUCT_MEMBER(gchar *, FileTag, item_fields[i].offset) == NULL)
+        {
+            item = items.find (item_fields[i].field);
+            if (item != items.end ())
+            {
+                G_STRUCT_MEMBER(gchar *, FileTag, item_fields[i].offset) = 
g_strdup(item->second.toStringList()[0].toCString(true));
+            }
+        }
+    }
+
+    if ((item = items.find ("covr")) != items.end())
+    {
+        TagLib::MP4::CoverArt &art = item->second.toCoverArtList ().front ();
+        FileTag->picture = Picture_Allocate ();
+
+        FileTag->picture->size = art.data ().size ();
+        FileTag->picture->data = (guchar *)g_memdup (art.data ().data (), art.data ().size ());
+    }
+    else
+    {
+        FileTag->picture = NULL;
+    }
+
+    FileTag->other = NULL;
+
+    delete a;
+    delete stream;
+    g_object_unref (f);
+
+    return TRUE;
+}
+
+gboolean Mp4tag_Write_File_Tag (ET_File *ETFile)
+{
+    File_Tag *FileTag = (File_Tag *)ETFile->FileTag->data;
+    gchar    *filename = ((File_Name *)ETFile->FileNameCur->data)->value;
+    GFile *f = g_file_new_for_path (filename);
+    GIO_IOStream *stream = new GIO_IOStream (f);
+
+    TagLib::MP4::File *a = new TagLib::MP4::File (stream, false);
+    TagLib::MP4::Tag *tag = a->tag ();
+    TagLib::PropertyMap props;
+
+    size_t i;
+
+    for (i = 0; i < sizeof(fields) / sizeof(*fields); i++)
+    {
+        gchar *value = G_STRUCT_MEMBER(gchar *, FileTag, fields[i].offset);
+        if (value)
+        {
+            props[fields[i].field] = TagLib::StringList().append(TagLib::String(value, 
TagLib::String::UTF8));
+        }
+    }
+
+    TagLib::MP4::ItemListMap &items = tag->itemListMap ();
+
+    for (i = 0; i < sizeof(item_fields) / sizeof(*item_fields); i++)
+    {
+        gchar *value = G_STRUCT_MEMBER(gchar *, FileTag, item_fields[i].offset);
+        if (value)
+        {
+            items.insert(item_fields[i].field,
+                         TagLib::MP4::Item(TagLib::StringList().append(TagLib::String(value, 
TagLib::String::UTF8))));
+        }
+    }
+
+    if (FileTag->picture)
+    {
+        Picture_Format pf = Picture_Format_From_Data (FileTag->picture);
+        TagLib::MP4::CoverArt::Format f;
+        switch (pf)
+        {
+        case PICTURE_FORMAT_JPEG:
+            f = TagLib::MP4::CoverArt::JPEG;
+            break;
+        case PICTURE_FORMAT_PNG:
+            f = TagLib::MP4::CoverArt::PNG;
+            break;
+        case PICTURE_FORMAT_GIF:
+            f = TagLib::MP4::CoverArt::GIF;
+            break;
+        default:
+            g_warning("Unknown format");
+            f = TagLib::MP4::CoverArt::JPEG;
+            break;
+        }
+        TagLib::MP4::CoverArt art(f, TagLib::ByteVector((char *)FileTag->picture->data,
+                                                        FileTag->picture->size));
+
+        items.insert("covr",
+                     TagLib::MP4::Item(TagLib::MP4::CoverArtList().append(art)));
+    }
+
+    tag->setProperties(props);
+    a->save();
+
+    delete a;
+    delete stream;
+
+    return TRUE;
+}
-- 
1.8.3.2



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]