[RFC] MP4 tag handler using taglib C++ API
- From: Santtu Lakkala <inz inz fi>
- To: easytag-list gnome org
- Subject: [RFC] MP4 tag handler using taglib C++ API
- Date: Wed, 9 Apr 2014 16:46:34 +0300
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]