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