tracker-0.16.2/src/tracker-extract/tracker-cue-sheet.c

No issues found

  1 /*
  2  * Copyright (C) 2011, ARQ Media <sam.thursfield@codethink.co.uk>
  3  *
  4  * This library is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2.1 of the License, or (at your option) any later version.
  8  *
  9  * This library is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with this library; if not, write to the
 16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 17  * Boston, MA  02110-1301, USA.
 18  *
 19  * Author: Sam Thursfield <sam.thursfield@codethink.co.uk>
 20  */
 21 
 22 #include "config.h"
 23 
 24 #include <stdlib.h>
 25 #include <string.h>
 26 
 27 #include <glib.h>
 28 #include <gio/gio.h>
 29 #include <gst/gst.h>
 30 #include <gst/tag/tag.h>
 31 
 32 #if defined(HAVE_LIBCUE)
 33 #include <libcue/libcue.h>
 34 #endif
 35 
 36 #include <libtracker-common/tracker-file-utils.h>
 37 
 38 #include "tracker-cue-sheet.h"
 39 
 40 #if defined(HAVE_LIBCUE)
 41 
 42 static TrackerToc *
 43 tracker_toc_new (void)
 44 {
 45 	TrackerToc *toc;
 46 
 47 	toc = g_slice_new (TrackerToc);
 48 	toc->tag_list = gst_tag_list_new (NULL);
 49 	toc->entry_list = NULL;
 50 
 51 	return toc;
 52 }
 53 
 54 #endif /* HAVE_LIBCUE */
 55 
 56 void
 57 tracker_toc_free (TrackerToc *toc)
 58 {
 59 	TrackerTocEntry *entry;
 60 	GList *n;
 61 
 62 	if (!toc) {
 63 		return;
 64 	}
 65 
 66 	for (n = toc->entry_list; n != NULL; n = n->next) {
 67 		entry = n->data;
 68 		gst_tag_list_free (entry->tag_list);
 69 		g_slice_free (TrackerTocEntry, entry);
 70 	}
 71 
 72 	g_list_free (toc->entry_list);
 73 
 74 	g_slice_free (TrackerToc, toc);
 75 }
 76 
 77 #if defined(HAVE_LIBCUE)
 78 
 79 static void
 80 add_cdtext_string_tag (Cdtext      *cd_text,
 81                        enum Pti     index,
 82                        GstTagList  *tag_list,
 83                        const gchar *tag)
 84 {
 85 	const gchar *text;
 86 
 87 	text = cdtext_get (index, cd_text);
 88 
 89 	if (text != NULL) {
 90 		gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, tag, text, NULL);
 91 	}
 92 }
 93 
 94 static void
 95 add_cdtext_comment_date_tag (Rem         *cd_comments,
 96                              enum Cmt     index,
 97                              GstTagList  *tag_list,
 98                              const gchar *tag)
 99 {
100 	const gchar *text;
101 	gint year;
102 	GDate *date;
103 
104 	text = rem_get (index, cd_comments);
105 
106 	if (text != NULL) {
107 		year = atoi (text);
108 
109 		if (year >= 1860) {
110 			date = g_date_new_dmy (1, 1, year);
111 			gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, tag, date, NULL);
112 			g_date_free (date);
113 		}
114 	}
115 }
116 
117 static void
118 add_cdtext_comment_double_tag (Rem         *cd_comments,
119                                enum Cmt     index,
120                                GstTagList  *tag_list,
121                                const gchar *tag)
122 {
123 	const gchar *text;
124 	gdouble value;
125 
126 	text = rem_get (index, cd_comments);
127 
128 	if (text != NULL) {
129 		value = strtod (text, NULL);
130 
131 		/* Shortcut: it just so happens that 0.0 is meaningless for the replay
132 		 * gain properties so we can get away with testing for errors this way.
133 		 */
134 		if (value != 0.0)
135 			gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, tag, value, NULL);
136 	}
137 }
138 
139 static void
140 set_album_tags_from_cdtext (GstTagList *tag_list,
141                             Cdtext     *cd_text,
142                             Rem        *cd_comments)
143 {
144 	if (cd_text != NULL) {
145 		add_cdtext_string_tag (cd_text, PTI_TITLE, tag_list, GST_TAG_ALBUM);
146 		add_cdtext_string_tag (cd_text, PTI_PERFORMER, tag_list, GST_TAG_ALBUM_ARTIST);
147 	}
148 
149 	if (cd_comments != NULL) {
150 		add_cdtext_comment_date_tag (cd_comments, REM_DATE, tag_list, GST_TAG_DATE);
151 
152 		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_ALBUM_GAIN, tag_list, GST_TAG_ALBUM_GAIN);
153 		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_ALBUM_PEAK, tag_list, GST_TAG_ALBUM_PEAK);
154 	}
155 }
156 
157 static void
158 set_track_tags_from_cdtext (GstTagList *tag_list,
159                             Cdtext     *cd_text,
160                             Rem        *cd_comments)
161 {
162 	if (cd_text != NULL) {
163 		add_cdtext_string_tag (cd_text, PTI_TITLE, tag_list, GST_TAG_TITLE);
164 		add_cdtext_string_tag (cd_text, PTI_PERFORMER, tag_list, GST_TAG_PERFORMER);
165 		add_cdtext_string_tag (cd_text, PTI_COMPOSER, tag_list, GST_TAG_COMPOSER);
166 	}
167 
168 	if (cd_comments != NULL) {
169 		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_TRACK_GAIN, tag_list, GST_TAG_TRACK_GAIN);
170 		add_cdtext_comment_double_tag (cd_comments, REM_REPLAYGAIN_TRACK_PEAK, tag_list, GST_TAG_TRACK_PEAK);
171 	}
172 }
173 
174 /* Some simple heuristics to fill in missing tag information. */
175 static void
176 process_toc_tags (TrackerToc *toc)
177 {
178 	GList *node;
179 	gint track_count;
180 
181 	gchar *album_artist = NULL;
182 
183 	if (gst_tag_list_get_tag_size (toc->tag_list, GST_TAG_TRACK_COUNT) == 0) {
184 		track_count = g_list_length (toc->entry_list);
185 		gst_tag_list_add (toc->tag_list,
186 		                  GST_TAG_MERGE_REPLACE,
187 		                  GST_TAG_TRACK_COUNT,
188 		                  track_count,
189 		                  NULL);
190 	}
191 
192 	gst_tag_list_get_string (toc->tag_list, GST_TAG_ALBUM_ARTIST, &album_artist);
193 
194 	for (node = toc->entry_list; node; node = node->next) {
195 		TrackerTocEntry *entry = node->data;
196 
197 		if (album_artist != NULL) {
198 			if (gst_tag_list_get_tag_size (entry->tag_list, GST_TAG_ARTIST) == 0 &&
199 			    gst_tag_list_get_tag_size (entry->tag_list, GST_TAG_PERFORMER) == 0)
200 				gst_tag_list_add (entry->tag_list,
201 				                  GST_TAG_MERGE_REPLACE,
202 				                  GST_TAG_ARTIST,
203 				                  album_artist,
204 				                  NULL);
205 		}
206 	}
207 
208 	g_free (album_artist);
209 }
210 
211 /* This function runs in two modes: for external CUE sheets, it will check
212  * the FILE field for each track and build a TrackerToc for all the tracks
213  * contained in @file_name. If @file_name does not appear in the CUE sheet,
214  * %NULL will be returned. For embedded CUE sheets, @file_name will be NULL
215  * the whole TOC will be returned regardless of any FILE information.
216  */
217 static TrackerToc *
218 parse_cue_sheet_for_file (const gchar *cue_sheet,
219                           const gchar *file_name)
220 {
221 	TrackerToc *toc;
222 	TrackerTocEntry *toc_entry;
223 	Cd *cd;
224 	Track *track;
225 	gint i;
226 
227 	toc = NULL;
228 
229 	cd = cue_parse_string (cue_sheet);
230 
231 	if (cd == NULL) {
232 		g_debug ("Unable to parse CUE sheet for %s.",
233 		         file_name ? file_name : "(embedded in FLAC)");
234 		return NULL;
235 	}
236 
237 	for (i = 1; i <= cd_get_ntrack (cd); i++) {
238 		track = cd_get_track (cd, i);
239 
240 		/* CUE sheets generally have the correct basename but wrong
241 		 * extension in the FILE field, so this is what we test for.
242 		 */
243 		if (file_name != NULL) {
244 			if (!tracker_filename_casecmp_without_extension (file_name,
245 			                                                 track_get_filename (track))) {
246 				continue;
247 			}
248 		}
249 
250 		if (track_get_mode (track) != MODE_AUDIO)
251 			continue;
252 
253 		if (toc == NULL) {
254 			toc = tracker_toc_new ();
255 
256 			set_album_tags_from_cdtext (toc->tag_list,
257 			                            cd_get_cdtext (cd),
258 			                            cd_get_rem (cd));
259 		}
260 
261 		toc_entry = g_slice_new (TrackerTocEntry);
262 		toc_entry->tag_list = gst_tag_list_new (NULL);
263 		toc_entry->start = track_get_start (track) / 75.0;
264 		toc_entry->duration = track_get_length (track) / 75.0;
265 
266 		set_track_tags_from_cdtext (toc_entry->tag_list,
267 		                            track_get_cdtext (track),
268 		                            track_get_rem (track));
269 
270 		gst_tag_list_add (toc_entry->tag_list,
271 		                  GST_TAG_MERGE_REPLACE,
272 		                  GST_TAG_TRACK_NUMBER,
273 		                  i,
274 		                  NULL);
275 
276 
277 		toc->entry_list = g_list_prepend (toc->entry_list, toc_entry);
278 	}
279 
280 	cd_delete (cd);
281 
282 	if (toc != NULL)
283 		toc->entry_list = g_list_reverse (toc->entry_list);
284 
285 	return toc;
286 }
287 
288 TrackerToc *
289 tracker_cue_sheet_parse (const gchar *cue_sheet)
290 {
291 	TrackerToc *result;
292 
293 	result = parse_cue_sheet_for_file (cue_sheet, NULL);
294 
295 	if (result)
296 		process_toc_tags (result);
297 
298 	return result;
299 }
300 
301 static GList *
302 find_local_cue_sheets (GFile *audio_file)
303 {
304 	GFile *container;
305 	GFile *cue_sheet;
306 	GFileEnumerator *e;
307 	GFileInfo *file_info;
308 	gchar *container_path;
309 	const gchar *file_name;
310 	const gchar *file_content_type;
311 	gchar *file_path;
312 	GList *result = NULL;
313 	GError *error = NULL;
314 
315 	container = g_file_get_parent (audio_file);
316 	container_path = g_file_get_path (container);
317 
318 	e = g_file_enumerate_children (container,
319 	                               "standard::*",
320 	                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
321 	                               NULL,
322 	                               &error);
323 
324 	if (error != NULL) {
325 		g_debug ("Unable to enumerate directory: %s", error->message);
326 		g_object_unref (container);
327 		g_error_free (error);
328 		return NULL;
329 	}
330 
331 	while ((file_info = g_file_enumerator_next_file (e, NULL, NULL))) {
332 		file_name = g_file_info_get_attribute_byte_string (file_info,
333 		                                                   G_FILE_ATTRIBUTE_STANDARD_NAME);
334 
335 		file_content_type = g_file_info_get_content_type (file_info);
336 
337 		if (file_name == NULL || file_content_type == NULL) {
338 			g_debug ("Unable to get info for file %s/%s",
339 			         container_path,
340 			         g_file_info_get_display_name (file_info));
341 		} else if (strcmp (file_content_type, "application/x-cue") == 0) {
342 			file_path = g_build_filename (container_path, file_name, NULL);
343 			cue_sheet = g_file_new_for_path (file_path);
344 			result = g_list_prepend (result, cue_sheet);
345 			g_free (file_path);
346 		}
347 
348 		g_object_unref (file_info);
349 	}
350 
351 	g_object_unref (e);
352 	g_object_unref (container);
353 	g_free (container_path);
354 
355 	return result;
356 }
357 
358 TrackerToc *
359 tracker_cue_sheet_parse_uri (const gchar *uri)
360 {
361 	GFile *audio_file;
362 	gchar *audio_file_name;
363 	GList *cue_sheet_list;
364 	TrackerToc *toc;
365 	GError *error = NULL;
366 	GList *n;
367 
368 	audio_file = g_file_new_for_uri (uri);
369 	audio_file_name = g_file_get_basename (audio_file);
370 
371 	cue_sheet_list = find_local_cue_sheets (audio_file);
372 
373 	toc = NULL;
374 
375 	for (n = cue_sheet_list; n != NULL; n = n->next) {
376 		GFile *cue_sheet_file;
377 		gchar *buffer;
378 
379 		cue_sheet_file = n->data;
380 
381 		g_file_load_contents (cue_sheet_file, NULL, &buffer, NULL, NULL, &error);
382 
383 		if (error != NULL) {
384 			g_debug ("Unable to read cue sheet: %s", error->message);
385 			g_error_free (error);
386 			continue;
387 		}
388 
389 		toc = parse_cue_sheet_for_file (buffer, audio_file_name);
390 
391 		g_free (buffer);
392 
393 		if (toc != NULL) {
394 			char *path = g_file_get_path (cue_sheet_file);
395 			g_debug ("Using external CUE sheet: %s", path);
396 			g_free (path);
397 			break;
398 		}
399 	}
400 
401 	g_list_foreach (cue_sheet_list, (GFunc) g_object_unref, NULL);
402 	g_list_free (cue_sheet_list);
403 
404 	g_object_unref (audio_file);
405 	g_free (audio_file_name);
406 
407 	if (toc)
408 		process_toc_tags (toc);
409 
410 	return toc;
411 }
412 
413 #else  /* ! HAVE_LIBCUE */
414 
415 TrackerToc *
416 tracker_cue_sheet_parse (const gchar *cue_sheet)
417 {
418 	return NULL;
419 }
420 
421 TrackerToc *
422 tracker_cue_sheet_parse_uri (const gchar *uri)
423 {
424 	return NULL;
425 }
426 
427 #endif /* ! HAVE_LIBCUE */