[tracker/cuesheets: 17/18] tracker-extract: Add libcue-based CUE sheet parser



commit 1e650207911e231dd9c2331b47e1a98ccbb13075
Author: Sam Thursfield <sam thursfield codethink co uk>
Date:   Thu Aug 18 18:15:23 2011 +0100

    tracker-extract: Add libcue-based CUE sheet parser
    
    This is currently only usable by the GStreamer extractor due to the
    use of GstTagList.

 configure.ac                            |   34 +++
 src/tracker-extract/Makefile.am         |   11 +-
 src/tracker-extract/tracker-cue-sheet.c |  404 +++++++++++++++++++++++++++++++
 src/tracker-extract/tracker-cue-sheet.h |   51 ++++
 4 files changed, 497 insertions(+), 3 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 6324c85..95176df 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2068,6 +2068,39 @@ fi
 AM_CONDITIONAL(HAVE_LIBFLAC, test "x$have_libflac" = "xyes")
 
 ####################################################################
+# Check for tracker-extract-gstreamer: libcue
+####################################################################
+
+AC_ARG_ENABLE(libcue,
+              AS_HELP_STRING([--enable-libcue],
+                             [enable external cuesheet parsing  [[default=auto]]]),,
+              [enable_libcue=auto])
+
+if test "x$enable_libcue" != "xno" ; then
+   PKG_CHECK_MODULES(LIBCUE,
+                     [libcue],
+                     [have_libcue=yes],
+                     [have_libcue=no])
+
+   AC_SUBST(LIBCUE_CFLAGS)
+   AC_SUBST(LIBCUE_LIBS)
+
+   if test "x$have_libcue" = "xyes"; then
+      AC_DEFINE(HAVE_LIBCUE, [], [Define if we have libcue])
+   fi
+else
+   have_libcue="no  (disabled)"
+fi
+
+if test "x$enable_libcue" = "xyes"; then
+   if test "x$have_libcue" != "xyes"; then
+      AC_MSG_ERROR([Couldn't find libcue.])
+   fi
+fi
+
+AM_CONDITIONAL(HAVE_LIBCUE, test "x$have_libcue" = "xyes")
+
+####################################################################
 # Check for tracker-extract, tracker-writeback: totem-pl-parser
 ####################################################################
 
@@ -2345,6 +2378,7 @@ Metadata Extractors:
 	Support embedded / sidecar XMP:         $have_exempi
 	Support generic media formats:          $have_generic_media_handler ($have_generic_media_handler_app) (backend: $gstreamer_backend)
 	Support MP3 album art:                  $selected_for_albumart
+	Support cue sheet parsing:              $have_libcue
 	Support playlists (w/ Totem):           $have_playlist
 
 Data Miners:
diff --git a/src/tracker-extract/Makefile.am b/src/tracker-extract/Makefile.am
index 66a1d58..d74baef 100644
--- a/src/tracker-extract/Makefile.am
+++ b/src/tracker-extract/Makefile.am
@@ -312,16 +312,21 @@ libextract_pdf_la_LIBADD = \
 	$(POPPLER_LIBS)
 
 # GStreamer
-libextract_gstreamer_la_SOURCES = tracker-extract-gstreamer.c
+libextract_gstreamer_la_SOURCES = \
+	tracker-extract-gstreamer.c \
+	tracker-cue-sheet.h \
+	tracker-cue-sheet.c
 libextract_gstreamer_la_CFLAGS = \
 	$(TRACKER_EXTRACT_MODULES_CFLAGS) \
-	$(GSTREAMER_CFLAGS)
+	$(GSTREAMER_CFLAGS) \
+	$(LIBCUE_CFLAGS)
 libextract_gstreamer_la_LDFLAGS = $(module_flags)
 libextract_gstreamer_la_LIBADD = \
 	$(top_builddir)/src/libtracker-extract/libtracker-extract- TRACKER_API_VERSION@.la \
 	$(BUILD_LIBS) \
 	$(TRACKER_EXTRACT_MODULES_LIBS) \
-	$(GSTREAMER_LIBS)
+	$(GSTREAMER_LIBS) \
+	$(LIBCUE_LIBS)
 
 # GStreamer with GUPnP-DLNA backend?
 if HAVE_GUPNP_DLNA
diff --git a/src/tracker-extract/tracker-cue-sheet.c b/src/tracker-extract/tracker-cue-sheet.c
new file mode 100644
index 0000000..efd92d7
--- /dev/null
+++ b/src/tracker-extract/tracker-cue-sheet.c
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2011, ARQ Media <sam thursfield codethink co uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Sam Thursfield <sam thursfield codethink co uk>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gst/gst.h>
+#include <gst/tag/tag.h>
+
+#if defined(HAVE_LIBCUE)
+#include <libcue/libcue.h>
+#endif
+
+#include <libtracker-common/tracker-utils.h>
+
+#include "tracker-cue-sheet.h"
+
+static TrackerToc *
+tracker_toc_new ()
+{
+	TrackerToc *toc;
+
+	toc = g_slice_new (TrackerToc);
+	toc->tag_list = gst_tag_list_new ();
+	toc->entry_list = NULL;
+
+	return toc;
+}
+
+void
+tracker_toc_free (TrackerToc *toc)
+{
+	TrackerTocEntry *entry;
+	GList *n;
+
+	for (n = toc->entry_list; n != NULL; n = n->next) {
+		entry = n->data;
+		gst_tag_list_free (entry->tag_list);
+		g_slice_free (TrackerTocEntry, entry);
+	}
+
+	g_list_free (toc->entry_list);
+
+	g_slice_free (TrackerToc, toc);
+}
+
+#if defined(HAVE_LIBCUE)
+
+static void
+add_cdtext_string_tag (Cdtext      *cd_text,
+                       enum Pti     index,
+                       GstTagList  *tag_list,
+                       const gchar *tag)
+{
+	const gchar *text;
+
+	text = cdtext_get (index, cd_text);
+
+	if (text != NULL)
+		gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, tag, text, NULL);
+}
+
+static void
+add_cdtext_comment_date_tag (Rem         *cd_comments,
+                             enum Cmt     index,
+                             GstTagList  *tag_list,
+                             const gchar *tag)
+{
+	const gchar *text;
+	gint year;
+	GDate *date;
+
+	text = rem_get (index, cd_comments);
+
+	if (text != NULL) {
+		year = atoi (text);
+
+		if (year >= 1860) {
+			date = g_date_new_dmy (1, 1, year);
+			gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, tag, date, NULL);
+			g_date_free (date);
+		}
+	}
+}
+
+static void
+add_cdtext_comment_double_tag (Rem         *cd_comments,
+                               enum Cmt     index,
+                               GstTagList  *tag_list,
+                               const gchar *tag)
+{
+	const gchar *text;
+	gdouble value;
+
+	text = rem_get (index, cd_comments);
+
+	if (text != NULL) {
+		value = strtod (text, NULL);
+
+		/* Shortcut: it just so happens that 0.0 is meaningless for the replay
+		 * gain properties so we can get away with testing for errors this way.
+		 */
+		if (value != 0.0)
+			gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, tag, value, NULL);
+	}
+}
+
+static void
+set_album_tags_from_cdtext (GstTagList *tag_list,
+                            Cdtext     *cd_text,
+                            Rem        *cd_comments)
+{
+	if (cd_text != NULL) {
+		add_cdtext_string_tag (cd_text, PTI_TITLE, tag_list, GST_TAG_ALBUM);
+		add_cdtext_string_tag (cd_text, PTI_PERFORMER, tag_list, GST_TAG_ALBUM_ARTIST);
+	}
+
+	if (cd_comments != NULL) {
+		add_cdtext_comment_date_tag (cd_comments, REM_DATE, tag_list, GST_TAG_DATE);
+
+		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_ALBUM_GAIN, tag_list, GST_TAG_ALBUM_GAIN);
+		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_ALBUM_PEAK, tag_list, GST_TAG_ALBUM_PEAK);
+	}
+}
+
+static void
+set_track_tags_from_cdtext (GstTagList *tag_list,
+                            Cdtext     *cd_text,
+                            Rem        *cd_comments)
+{
+	if (cd_text != NULL) {
+		add_cdtext_string_tag (cd_text, PTI_TITLE, tag_list, GST_TAG_TITLE);
+		add_cdtext_string_tag (cd_text, PTI_PERFORMER, tag_list, GST_TAG_PERFORMER);
+		add_cdtext_string_tag (cd_text, PTI_COMPOSER, tag_list, GST_TAG_COMPOSER);
+	}
+
+	if (cd_comments != NULL) {
+		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_TRACK_GAIN, tag_list, GST_TAG_TRACK_GAIN);
+		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_TRACK_PEAK, tag_list, GST_TAG_TRACK_PEAK);
+	}
+}
+
+/* Some simple heuristics to fill in missing tag information. */
+static void
+process_toc_tags (TrackerToc *toc)
+{
+	GList *node;
+	gint track_count;
+	gint i;
+
+	if (gst_tag_list_get_tag_size (toc->tag_list, GST_TAG_TRACK_COUNT) == 0) {
+		track_count = g_list_length (toc->entry_list);
+		gst_tag_list_add (toc->tag_list,
+		                  GST_TAG_MERGE_REPLACE,
+		                  GST_TAG_TRACK_COUNT,
+		                  track_count,
+		                  NULL);
+	}
+
+	i = 1;
+	for (node=toc->entry_list; node; node=node->next, i++) {
+		TrackerTocEntry *entry = node->data;
+
+		if (gst_tag_list_get_tag_size (toc->tag_list, GST_TAG_TRACK_NUMBER) == 0)
+			gst_tag_list_add (entry->tag_list,
+			                  GST_TAG_MERGE_REPLACE,
+			                  GST_TAG_TRACK_NUMBER,
+			                  i,
+			                  NULL);
+	}
+}
+
+/* This function runs in two modes: for external CUE sheets, it will check
+ * the FILE field for each track and build a TrackerToc for all the tracks
+ * contained in @file_name. If @file_name does not appear in the CUE sheet,
+ * %NULL will be returned. For embedded CUE sheets, @file_name will be NULL
+ * the whole TOC will be returned regardless of any FILE information.
+ */
+static TrackerToc *
+parse_cue_sheet_for_file (const gchar *cue_sheet,
+                          const gchar *file_name)
+{
+	TrackerToc *toc;
+	TrackerTocEntry *toc_entry;
+	Cd *cd;
+	Track *track;
+	gint i;
+
+	toc = NULL;
+
+	cd = cue_parse_string (cue_sheet);
+
+	if (cd == NULL) {
+		g_debug ("Unable to parse CUE sheet for %s.",
+		         file_name ? file_name : "(embedded in FLAC)");
+		return NULL;
+	}
+
+	for (i = 1; i <= cd_get_ntrack (cd); i++) {
+		track = cd_get_track (cd, i);
+
+		/* CUE sheets generally have the correct basename but wrong
+		 * extension in the FILE field, so this is what we test for.
+		 */
+		if (file_name != NULL) {
+			if (!tracker_case_match_filename_without_extension
+			       (file_name, track_get_filename (track)))
+				continue;
+		}
+
+		if (track_get_mode (track) != MODE_AUDIO)
+			continue;
+
+		if (toc == NULL) {
+			toc = tracker_toc_new ();
+
+			set_album_tags_from_cdtext (toc->tag_list,
+			                            cd_get_cdtext (cd),
+			                            cd_get_rem (cd));
+		}
+
+		toc_entry = g_slice_new0 (TrackerTocEntry);
+		toc_entry->tag_list = gst_tag_list_new ();
+		toc_entry->start = track_get_start (track) / 75.0;
+		toc_entry->duration = track_get_length (track) / 75.0;
+
+		set_track_tags_from_cdtext (toc_entry->tag_list,
+		                            track_get_cdtext (track),
+		                            track_get_rem (track));
+
+		toc->entry_list = g_list_prepend (toc->entry_list, toc_entry);
+	}
+
+	cd_delete (cd);
+
+	if (toc != NULL)
+		toc->entry_list = g_list_reverse (toc->entry_list);
+
+	return toc;
+}
+
+TrackerToc *
+tracker_parse_cue_sheet (const gchar *cue_sheet)
+{
+	TrackerToc *result;
+
+	result = parse_cue_sheet_for_file (cue_sheet, NULL);
+
+	if (result)
+		process_toc_tags (result);
+
+	return result;
+}
+
+static GList *
+find_local_cue_sheets (GFile *audio_file)
+{
+	GFile *container;
+	GFile *cue_sheet;
+	GFileEnumerator *e;
+	GFileInfo *file_info;
+    gchar *container_path;
+	const gchar *file_name;
+	const gchar *file_content_type;
+    gchar *file_path;
+	GList *result = NULL;
+	GError *error = NULL;
+
+	container = g_file_get_parent (audio_file);
+	container_path = g_file_get_path (container);
+
+	e = g_file_enumerate_children (container,
+	                               "standard::*",
+	                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+	                               NULL,
+	                               &error);
+
+	if (error != NULL) {
+		g_debug ("Unable to enumerate directory: %s", error->message);
+		g_object_unref (container);
+		g_error_free (error);
+		return NULL;
+	}
+
+	while ((file_info = g_file_enumerator_next_file (e, NULL, NULL))) {
+		file_name = g_file_info_get_attribute_byte_string (file_info,
+		                                                   G_FILE_ATTRIBUTE_STANDARD_NAME);
+
+		file_content_type = g_file_info_get_content_type (file_info);
+
+		if (file_name == NULL || file_content_type == NULL) {
+			g_debug ("Unable to get info for file %s/%s",
+			         container_path,
+			         g_file_info_get_display_name (file_info));
+		} else
+		if (strcmp (file_content_type, "application/x-cue") == 0) {
+			file_path = g_build_filename (container_path, file_name, NULL);
+			cue_sheet = g_file_new_for_path (file_path);
+			result = g_list_prepend (result, cue_sheet);
+			g_free (file_path);
+		}
+
+		g_object_unref (file_info);
+	}
+
+	g_object_unref (e);
+	g_object_unref (container);
+	g_free (container_path);
+
+	return result;
+}
+
+TrackerToc *
+tracker_process_external_cue_sheets (const gchar *audio_uri)
+{
+	GFile *audio_file;
+	gchar *audio_file_name;
+	GList *cue_sheet_list;
+	TrackerToc *toc;
+	GError *error = NULL;
+	GList *n;
+
+	audio_file = g_file_new_for_uri (audio_uri);
+	audio_file_name = g_file_get_basename (audio_file);
+
+	cue_sheet_list = find_local_cue_sheets (audio_file);
+
+	toc = NULL;
+
+	for (n = cue_sheet_list; n != NULL; n = n->next) {
+		GFile *cue_sheet_file;
+		gchar *buffer;
+
+		cue_sheet_file = n->data;
+
+		g_file_load_contents (cue_sheet_file, NULL, &buffer, NULL, NULL, &error);
+
+		if (error != NULL) {
+			g_debug ("Unable to read cue sheet: %s", error->message);
+			g_error_free (error);
+			continue;
+		}
+
+		toc = parse_cue_sheet_for_file (buffer, audio_file_name);
+
+		g_free (buffer);
+
+		if (toc != NULL) {
+			char *path = g_file_get_path (cue_sheet_file);
+			g_debug ("Using external CUE sheet: %s", path);
+			g_free (path);
+			break;
+		}
+	}
+
+	g_list_foreach (cue_sheet_list, (GFunc) g_object_unref, NULL);
+	g_list_free (cue_sheet_list);
+
+	g_object_unref (audio_file);
+	g_free (audio_file_name);
+
+	if (toc)
+		process_toc_tags (toc);
+
+	return toc;
+}
+
+#else  /* ! HAVE_LIBCUE */
+
+TrackerToc *
+tracker_parse_cue_sheet (const gchar *cue_sheet)
+{
+	return NULL;
+}
+
+TrackerToc *
+tracker_process_external_cue_sheets (const gchar *audio_uri)
+{
+	return NULL;
+}
+
+#endif
diff --git a/src/tracker-extract/tracker-cue-sheet.h b/src/tracker-extract/tracker-cue-sheet.h
new file mode 100644
index 0000000..e705e48
--- /dev/null
+++ b/src/tracker-extract/tracker-cue-sheet.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011, ARQ Media <sam thursfield codethink co uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Sam Thursfield <sam thursfield codethink co uk>
+ */
+
+#ifndef __TRACKER_EXTRACT_CUE_SHEET_H__
+#define __TRACKER_EXTRACT_CUE_SHEET_H__
+
+#include <glib.h>
+#include <gst/gst.h>
+#include <gst/tag/tag.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+	/* Values in seconds */
+	gdouble start, duration;
+
+	GstTagList *tag_list;
+} TrackerTocEntry;
+
+typedef struct {
+	GstTagList *tag_list;
+
+	GList *entry_list;
+} TrackerToc;
+
+void tracker_toc_free (TrackerToc *toc);
+
+TrackerToc *tracker_parse_cue_sheet             (const gchar *cue_sheet);
+TrackerToc *tracker_process_external_cue_sheets (const gchar *audio_uri);
+
+G_END_DECLS
+
+#endif /* __TRACKER_EXTRACT_CUE_SHEET_H__ */



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