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 */