1 /* EasyTAG - tag editor for audio files
2 * Copyright (C) 2014-2015 David King <amigadave@amigadave.com>
3 * Copyright (C) 2007 Maarten Maathuis (madman2003@gmail.com)
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #ifdef ENABLE_WAVPACK
23
24 #include <glib/gi18n.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <wavpack/wavpack.h>
28
29 #include "et_core.h"
30 #include "picture.h"
31 #include "charset.h"
32 #include "misc.h"
33 #include "wavpack_private.h"
34 #include "wavpack_tag.h"
35
36 #define MAXLEN 1024
37
38 /*
39 * For the APEv2 tags, the following field names are officially supported and
40 * recommended by WavPack (although there are no restrictions on what field names
41 * may be used):
42 *
43 * Artist
44 * Title
45 * Album
46 * Track
47 * Year
48 * Genre
49 * Comment
50 * Cuesheet (note: may include replay gain info as remarks)
51 * Replay_Track_Gain
52 * Replay_Track_Peak
53 * Replay_Album_Gain
54 * Replay_Album_Peak
55 * Cover Art (Front)
56 * Cover Art (Back)
57 */
58
59 /*
60 * Read tag data from a Wavpack file.
61 */
62 gboolean
wavpack_tag_read_file_tag(GFile * file,File_Tag * FileTag,GError ** error)63 wavpack_tag_read_file_tag (GFile *file,
64 File_Tag *FileTag,
65 GError **error)
66 {
67 WavpackStreamReader reader = { wavpack_read_bytes, wavpack_get_pos,
68 wavpack_set_pos_abs, wavpack_set_pos_rel,
69 wavpack_push_back_byte, wavpack_get_length,
70 wavpack_can_seek, NULL /* No writing. */ };
71 EtWavpackState state;
72 WavpackContext *wpc;
73 gchar message[80];
74 gchar field[MAXLEN] = { 0, };
75 gchar *field2;
76 guint length;
77 const int open_flags = OPEN_TAGS;
78
79 g_return_val_if_fail (file != NULL && FileTag != NULL, FALSE);
80 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
81
82 state.error = NULL;
83 state.istream = g_file_read (file, NULL, &state.error);
84
85 if (!state.istream)
86 {
87 g_propagate_error (error, state.error);
88 return FALSE;
89 }
90
91 state.seekable = G_SEEKABLE (state.istream);
92
93 /* NULL for the WavPack correction file. */
94 wpc = WavpackOpenFileInputEx (&reader, &state, NULL, message, open_flags,
95 0);
96
97 if (wpc == NULL)
98 {
99 if (state.error)
100 {
101 g_propagate_error (error, state.error);
102 }
103 else
104 {
105 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
106 message);
107 }
108
109 g_object_unref (state.istream);
110 return FALSE;
111 }
112
113 /*
114 * Title
115 */
116 length = WavpackGetTagItem(wpc, "title", field, MAXLEN);
117
118 if ( length > 0 && FileTag->title == NULL ) {
119 FileTag->title = Try_To_Validate_Utf8_String(field);
120 }
121
122 memset (field, '\0', MAXLEN);
123
124 /*
125 * Artist
126 */
127 length = WavpackGetTagItem(wpc, "artist", field, MAXLEN);
128
129 if ( length > 0 && FileTag->artist == NULL) {
130 FileTag->artist = Try_To_Validate_Utf8_String(field);
131 }
132
133 memset (field, '\0', MAXLEN);
134
135 /* Album artist. */
136 length = WavpackGetTagItem (wpc, "album artist", field, MAXLEN);
137
138 if (length > 0 && FileTag->album_artist == NULL)
139 {
140 FileTag->album_artist = Try_To_Validate_Utf8_String (field);
141 }
142
143 memset (field, '\0', MAXLEN);
144
145 /*
146 * Album
147 */
148 length = WavpackGetTagItem(wpc, "album", field, MAXLEN);
149
150 if ( length > 0 && FileTag->album == NULL ) {
151 FileTag->album = Try_To_Validate_Utf8_String(field);
152 }
153
154 memset (field, '\0', MAXLEN);
155
156 /*
157 * Discnumber + Disctotal.
158 */
159 length = WavpackGetTagItem (wpc, "part", field, MAXLEN);
160 field2 = strchr (field, '/');
161
162 /* Need to cut off the total tracks if present */
163 if (field2)
164 {
165 *field2 = 0;
166 field2++;
167 }
168
169 if (field2 && FileTag->disc_total == NULL)
170 {
171 gchar *tmp;
172
173 tmp = Try_To_Validate_Utf8_String (field2);
174 FileTag->disc_total = et_disc_number_to_string (atoi (tmp));
175 g_free (tmp);
176 }
177
178 if (length > 0 && FileTag->disc_number == NULL)
179 {
180 gchar *tmp;
181
182 tmp = Try_To_Validate_Utf8_String (field);
183 FileTag->disc_number = et_disc_number_to_string (atoi (tmp));
184 g_free (tmp);
185 }
186
187 memset (field, '\0', MAXLEN);
188
189 /*
190 * Year
191 */
192 length = WavpackGetTagItem(wpc, "year", field, MAXLEN);
193
194 if ( length > 0 && FileTag->year == NULL ) {
195 FileTag->year = Try_To_Validate_Utf8_String(field);
196 }
197
198 memset (field, '\0', MAXLEN);
199
200 /*
201 * Tracknumber + tracktotal
202 */
203 length = WavpackGetTagItem(wpc, "track", field, MAXLEN);
204 field2 = strchr (field, '/');
205
206 /* Need to cut off the total tracks if present */
207 if (field2) {
208 *field2 = 0;
209 field2++;
210 }
211
212 if (field2 && FileTag->track_total == NULL)
213 {
214 gchar *tmp;
215
216 tmp = Try_To_Validate_Utf8_String (field2);
217 FileTag->track_total = et_track_number_to_string (atoi (tmp));
218 g_free (tmp);
219 }
220
221 if (length > 0 && FileTag->track == NULL)
222 {
223 gchar *tmp;
224
225 tmp = Try_To_Validate_Utf8_String (field);
226 FileTag->track = et_track_number_to_string (atoi (tmp));
227 g_free (tmp);
228 }
229
230 memset (field, '\0', MAXLEN);
231
232 /*
233 * Genre
234 */
235 length = WavpackGetTagItem(wpc, "genre", field, MAXLEN);
236
237 if ( length > 0 && FileTag->genre == NULL ) {
238 FileTag->genre = Try_To_Validate_Utf8_String(field);
239 }
240
241 memset (field, '\0', MAXLEN);
242
243 /*
244 * Comment
245 */
246 length = WavpackGetTagItem(wpc, "comment", field, MAXLEN);
247
248 if ( length > 0 && FileTag->comment == NULL ) {
249 FileTag->comment = Try_To_Validate_Utf8_String(field);
250 }
251
252 memset (field, '\0', MAXLEN);
253
254 /*
255 * Composer
256 */
257 length = WavpackGetTagItem(wpc, "composer", field, MAXLEN);
258
259 if ( length > 0 && FileTag->composer == NULL ) {
260 FileTag->composer = Try_To_Validate_Utf8_String(field);
261 }
262
263 memset (field, '\0', MAXLEN);
264
265 /*
266 * Original artist
267 */
268 length = WavpackGetTagItem(wpc, "original artist", field, MAXLEN);
269
270 if ( length > 0 && FileTag->orig_artist == NULL ) {
271 FileTag->orig_artist = Try_To_Validate_Utf8_String(field);
272 }
273
274 memset (field, '\0', MAXLEN);
275
276 /*
277 * Copyright
278 */
279 length = WavpackGetTagItem(wpc, "copyright", field, MAXLEN);
280
281 if ( length > 0 && FileTag->copyright == NULL ) {
282 FileTag->copyright = Try_To_Validate_Utf8_String(field);
283 }
284
285 memset (field, '\0', MAXLEN);
286
287 /*
288 * URL
289 */
290 length = WavpackGetTagItem(wpc, "copyright url", field, MAXLEN);
291
292 if ( length > 0 && FileTag->url == NULL ) {
293 FileTag->url = Try_To_Validate_Utf8_String(field);
294 }
295
296 memset (field, '\0', MAXLEN);
297
298 /*
299 * Encoded by
300 */
301 length = WavpackGetTagItem(wpc, "encoded by", field, MAXLEN);
302
303 if ( length > 0 && FileTag->encoded_by == NULL ) {
304 FileTag->encoded_by = Try_To_Validate_Utf8_String(field);
305 }
306
307 WavpackCloseFile(wpc);
308
309 g_object_unref (state.istream);
310
311 return TRUE;
312 }
313
314 /*
315 * et_wavpack_append_or_delete_tag_item:
316 * @wpc: the #WavpackContext of which to modify tags
317 * @tag: the tag item name
318 * @value: the tag value to write, or %NULL to delete
319 *
320 * Appends @value to the @tag item of @wpc, or removes the tag item if @value
321 * is %NULL.
322 *
323 * Returns: %TRUE on success, %FALSE otherwise
324 */
325 static gboolean
et_wavpack_append_or_delete_tag_item(WavpackContext * wpc,const gchar * tag,const gchar * value)326 et_wavpack_append_or_delete_tag_item (WavpackContext *wpc,
327 const gchar *tag,
328 const gchar *value)
329 {
330 if (value)
331 {
332 return WavpackAppendTagItem (wpc, tag, value, strlen (value));
333 }
334 else
335 {
336 WavpackDeleteTagItem (wpc, tag);
337
338 /* It is not an error if there was no tag item to delete. */
339 return TRUE;
340 }
341 }
342
343 gboolean
wavpack_tag_write_file_tag(const ET_File * ETFile,GError ** error)344 wavpack_tag_write_file_tag (const ET_File *ETFile,
345 GError **error)
346 {
347 WavpackStreamReader writer = { wavpack_read_bytes, wavpack_get_pos,
348 wavpack_set_pos_abs, wavpack_set_pos_rel,
349 wavpack_push_back_byte, wavpack_get_length,
350 wavpack_can_seek, wavpack_write_bytes };
351 GFile *file;
352 EtWavpackWriteState state;
353 const gchar *filename;
354 const File_Tag *FileTag;
355 WavpackContext *wpc;
356 gchar message[80];
357 gchar *buffer;
358
359 g_return_val_if_fail (ETFile != NULL && ETFile->FileTag != NULL, FALSE);
360 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
361
362 filename = ((File_Name *)((GList *)ETFile->FileNameCur)->data)->value;
363 FileTag = (File_Tag *)ETFile->FileTag->data;
364
365 file = g_file_new_for_path (filename);
366 state.error = NULL;
367 state.iostream = g_file_open_readwrite (file, NULL, &state.error);
368 g_object_unref (file);
369
370 if (!state.iostream)
371 {
372 g_propagate_error (error, state.error);
373 return FALSE;
374 }
375
376 state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM (state.iostream)));
377 state.ostream = G_FILE_OUTPUT_STREAM (g_io_stream_get_output_stream (G_IO_STREAM (state.iostream)));
378 state.seekable = G_SEEKABLE (state.iostream);
379
380 /* NULL for the WavPack correction file. */
381 wpc = WavpackOpenFileInputEx (&writer, &state, NULL, message,
382 OPEN_EDIT_TAGS, 0);
383
384 if (wpc == NULL)
385 {
386 if (state.error)
387 {
388 g_propagate_error (error, state.error);
389 }
390 else
391 {
392 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
393 message);
394 }
395
396 g_object_unref (state.iostream);
397 return FALSE;
398 }
399
400 /* Title. */
401 if (!et_wavpack_append_or_delete_tag_item (wpc, "title", FileTag->title))
402 {
403 goto err;
404 }
405
406 /* Artist. */
407 if (!et_wavpack_append_or_delete_tag_item (wpc, "artist", FileTag->artist))
408 {
409 goto err;
410 }
411
412 /* Album artist. */
413 if (!et_wavpack_append_or_delete_tag_item (wpc, "album artist",
414 FileTag->album_artist))
415 {
416 goto err;
417 }
418
419 /* Album. */
420 if (!et_wavpack_append_or_delete_tag_item (wpc, "album", FileTag->album))
421 {
422 goto err;
423 }
424
425 /* Discnumber. */
426 if (FileTag->disc_number && FileTag->disc_total)
427 {
428 buffer = g_strdup_printf ("%s/%s", FileTag->disc_number,
429 FileTag->disc_total);
430
431 if (!et_wavpack_append_or_delete_tag_item (wpc, "part", buffer))
432 {
433 g_free (buffer);
434 goto err;
435 }
436 else
437 {
438 g_free (buffer);
439 }
440 }
441 else
442 {
443 if (!et_wavpack_append_or_delete_tag_item (wpc, "part",
444 FileTag->disc_number))
445 {
446 goto err;
447 }
448 }
449
450 /* Year. */
451 if (!et_wavpack_append_or_delete_tag_item (wpc, "year", FileTag->year))
452 {
453 goto err;
454 }
455
456 /* Tracknumber + tracktotal. */
457 if (FileTag->track_total)
458 {
459 buffer = g_strdup_printf ("%s/%s", FileTag->track,
460 FileTag->track_total);
461
462 if (!et_wavpack_append_or_delete_tag_item (wpc, "track", buffer))
463 {
464 g_free (buffer);
465 goto err;
466 }
467 else
468 {
469 g_free (buffer);
470 }
471 }
472 else
473 {
474 if (!et_wavpack_append_or_delete_tag_item (wpc, "track",
475 FileTag->track))
476 {
477 goto err;
478 }
479 }
480
481 /* Genre. */
482 if (!et_wavpack_append_or_delete_tag_item (wpc, "genre", FileTag->genre))
483 {
484 goto err;
485 }
486
487 /* Comment. */
488 if (!et_wavpack_append_or_delete_tag_item (wpc, "comment", FileTag->comment))
489 {
490 goto err;
491 }
492
493 /* Composer. */
494 if (!et_wavpack_append_or_delete_tag_item (wpc, "composer",
495 FileTag->composer))
496 {
497 goto err;
498 }
499
500 /* Original artist. */
501 if (!et_wavpack_append_or_delete_tag_item (wpc, "original artist",
502 FileTag->orig_artist))
503 {
504 goto err;
505 }
506
507 /* Copyright. */
508 if (!et_wavpack_append_or_delete_tag_item (wpc, "copyright",
509 FileTag->copyright))
510 {
511 goto err;
512 }
513
514 /* URL. */
515 if (!et_wavpack_append_or_delete_tag_item (wpc, "copyright url",
516 FileTag->url))
517 {
518 goto err;
519 }
520
521 /* Encoded by. */
522 if (!et_wavpack_append_or_delete_tag_item (wpc, "encoded by",
523 FileTag->encoded_by))
524 {
525 goto err;
526 }
527
528 if (WavpackWriteTag (wpc) == 0)
529 {
530 goto err;
531 }
532
533 WavpackCloseFile (wpc);
534
535 g_object_unref (state.iostream);
536
537 return TRUE;
538
539 err:
540 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
541 WavpackGetErrorMessage (wpc));
542 WavpackCloseFile (wpc);
543 return FALSE;
544 }
545
546 #endif /* ENABLE_WAVPACK */
547