1 /*
2 * A FLAC decoder plugin for the Audacious Media Player
3 * Copyright (C) 2005 Ralf Ertzinger
4 * Copyright (C) 2010 John Lindgren
5 * Copyright (C) 2010 Michał Lipski <tallica@o2.pl>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #include <stdlib.h>
23 #include <string.h>
24
25 #define WANT_VFS_STDIO_COMPAT
26 #include <libaudcore/runtime.h>
27 #include <libaudcore/i18n.h>
28 #include <libaudcore/audstrings.h>
29
30 #include "flacng.h"
31
read_cb(void * ptr,size_t size,size_t nmemb,FLAC__IOHandle handle)32 static size_t read_cb(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
33 {
34 int64_t read;
35
36 if (handle == nullptr)
37 {
38 AUDERR("Trying to read data from an uninitialized file!\n");
39 return -1;
40 }
41
42 read = ((VFSFile *) handle)->fread (ptr, size, nmemb);
43
44 switch (read)
45 {
46 case -1:
47 AUDERR("Error while reading from stream!\n");
48 return -1;
49
50 case 0:
51 AUDDBG("Stream reached EOF\n");
52 return 0;
53
54 default:
55 return read;
56 }
57 }
58
write_cb(const void * ptr,size_t size,size_t nmemb,FLAC__IOHandle handle)59 static size_t write_cb(const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
60 {
61 return ((VFSFile *) handle)->fwrite (ptr, size, nmemb);
62 }
63
seek_cb(FLAC__IOHandle handle,FLAC__int64 offset,int whence)64 static int seek_cb(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
65 {
66 if (((VFSFile *) handle)->fseek (offset, to_vfs_seek_type (whence)) != 0)
67 {
68 AUDERR("Could not seek to %ld!\n", (long)offset);
69 return -1;
70 }
71
72 return 0;
73 }
74
tell_cb(FLAC__IOHandle handle)75 static FLAC__int64 tell_cb(FLAC__IOHandle handle)
76 {
77 int64_t offset;
78
79 if ((offset = ((VFSFile *) handle)->ftell ()) < 0)
80 {
81 AUDERR("Could not tell current position!\n");
82 return -1;
83 }
84
85 AUDDBG ("Current position: %d\n", (int) offset);
86 return offset;
87 }
88
eof_cb(FLAC__IOHandle handle)89 static int eof_cb(FLAC__IOHandle handle)
90 {
91 return ((VFSFile *) handle)->feof ();
92 }
93
94 static FLAC__IOCallbacks io_callbacks = {
95 read_cb,
96 write_cb,
97 seek_cb,
98 tell_cb,
99 eof_cb,
100 nullptr
101 };
102
insert_str_tuple_to_vc(FLAC__StreamMetadata * vc_block,const Tuple & tuple,Tuple::Field field,const char * field_name)103 static void insert_str_tuple_to_vc (FLAC__StreamMetadata * vc_block,
104 const Tuple & tuple, Tuple::Field field, const char * field_name)
105 {
106 FLAC__StreamMetadata_VorbisComment_Entry entry;
107 String val = tuple.get_str (field);
108
109 if (! val)
110 return;
111
112 StringBuf str = str_printf ("%s=%s", field_name, (const char *) val);
113 entry.entry = (FLAC__byte *) (char *) str;
114 entry.length = strlen(str);
115 FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,
116 vc_block->data.vorbis_comment.num_comments, entry, true);
117 }
118
insert_int_tuple_to_vc(FLAC__StreamMetadata * vc_block,const Tuple & tuple,Tuple::Field field,const char * field_name)119 static void insert_int_tuple_to_vc (FLAC__StreamMetadata * vc_block,
120 const Tuple & tuple, Tuple::Field field, const char * field_name)
121 {
122 FLAC__StreamMetadata_VorbisComment_Entry entry;
123 int val = tuple.get_int (field);
124
125 if (val <= 0)
126 return;
127
128 StringBuf str = str_printf ("%s=%d", field_name, val);
129 entry.entry = (FLAC__byte *) (char *) str;
130 entry.length = strlen(str);
131 FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,
132 vc_block->data.vorbis_comment.num_comments, entry, true);
133 }
134
write_tuple(const char * filename,VFSFile & file,const Tuple & tuple)135 bool FLACng::write_tuple(const char *filename, VFSFile &file, const Tuple &tuple)
136 {
137 AUDDBG("Update song tuple.\n");
138
139 FLAC__Metadata_Iterator *iter;
140 FLAC__Metadata_Chain *chain;
141 FLAC__StreamMetadata *vc_block;
142 FLAC__Metadata_ChainStatus status;
143
144 chain = FLAC__metadata_chain_new();
145
146 if (!FLAC__metadata_chain_read_with_callbacks(chain, &file, io_callbacks))
147 goto ERR;
148
149 iter = FLAC__metadata_iterator_new();
150
151 FLAC__metadata_iterator_init(iter, chain);
152
153 while (FLAC__metadata_iterator_next(iter))
154 {
155 if (FLAC__metadata_iterator_get_block_type(iter) == FLAC__METADATA_TYPE_VORBIS_COMMENT)
156 {
157 FLAC__metadata_iterator_delete_block(iter, true);
158 break;
159 }
160 }
161
162 vc_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
163
164 insert_str_tuple_to_vc(vc_block, tuple, Tuple::Title, "TITLE");
165 insert_str_tuple_to_vc(vc_block, tuple, Tuple::Artist, "ARTIST");
166 insert_str_tuple_to_vc(vc_block, tuple, Tuple::Album, "ALBUM");
167 insert_str_tuple_to_vc(vc_block, tuple, Tuple::AlbumArtist, "ALBUMARTIST");
168 insert_str_tuple_to_vc(vc_block, tuple, Tuple::Genre, "GENRE");
169 insert_str_tuple_to_vc(vc_block, tuple, Tuple::Comment, "COMMENT");
170 insert_str_tuple_to_vc(vc_block, tuple, Tuple::Description, "DESCRIPTION");
171 insert_str_tuple_to_vc(vc_block, tuple, Tuple::MusicBrainzID, "musicbrainz_trackid");
172
173 insert_int_tuple_to_vc(vc_block, tuple, Tuple::Year, "DATE");
174 insert_int_tuple_to_vc(vc_block, tuple, Tuple::Track, "TRACKNUMBER");
175
176 FLAC__metadata_iterator_insert_block_after(iter, vc_block);
177
178 FLAC__metadata_iterator_delete(iter);
179 FLAC__metadata_chain_sort_padding(chain);
180
181 if (FLAC__metadata_chain_check_if_tempfile_needed(chain, true))
182 {
183 auto temp = VFSFile::tmpfile();
184 if (!temp)
185 goto ERR_RETURN;
186
187 if (!FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain, true,
188 &file, io_callbacks, &temp, io_callbacks))
189 goto ERR;
190
191 if (!file.replace_with(temp))
192 goto ERR_RETURN;
193 }
194 else /* no tempfile needed */
195 {
196 if (!FLAC__metadata_chain_write_with_callbacks(chain, true, &file, io_callbacks))
197 goto ERR;
198 }
199
200 FLAC__metadata_chain_delete(chain);
201 return true;
202
203 ERR:
204 status = FLAC__metadata_chain_status(chain);
205 AUDERR("An error occurred: %s\n", FLAC__Metadata_ChainStatusString[status]);
206 ERR_RETURN:
207 FLAC__metadata_chain_delete(chain);
208 return false;
209 }
210
add_text(Tuple & tuple,Tuple::Field field,const char * value)211 static void add_text (Tuple & tuple, Tuple::Field field, const char * value)
212 {
213 String cur = tuple.get_str (field);
214 if (cur)
215 tuple.set_str (field, str_concat ({cur, ", ", value}));
216 else
217 tuple.set_str (field, value);
218 }
219
parse_comment(Tuple & tuple,const char * key,const char * value)220 static void parse_comment (Tuple & tuple, const char * key, const char * value)
221 {
222 AUDDBG ("Found key %s <%s>\n", key, value);
223
224 static const struct {
225 const char * key;
226 Tuple::Field field;
227 } tfields[] = {
228 {"ARTIST", Tuple::Artist},
229 {"ALBUM", Tuple::Album},
230 {"ALBUMARTIST", Tuple::AlbumArtist},
231 {"TITLE", Tuple::Title},
232 {"COMMENT", Tuple::Comment},
233 {"GENRE", Tuple::Genre},
234 {"DESCRIPTION", Tuple::Description},
235 {"musicbrainz_trackid", Tuple::MusicBrainzID},
236 };
237
238 for (auto & tfield : tfields)
239 {
240 if (!strcmp_nocase(key, tfield.key))
241 {
242 add_text (tuple, tfield.field, value);
243 return;
244 }
245 }
246
247 if (!strcmp_nocase(key, "TRACKNUMBER"))
248 tuple.set_int(Tuple::Track, atoi(value));
249 else if (!strcmp_nocase(key, "DATE"))
250 tuple.set_int(Tuple::Year, atoi(value));
251 else if (!strcmp_nocase(key, "REPLAYGAIN_TRACK_GAIN"))
252 tuple.set_gain(Tuple::TrackGain, Tuple::GainDivisor, value);
253 else if (!strcmp_nocase(key, "REPLAYGAIN_TRACK_PEAK"))
254 tuple.set_gain(Tuple::TrackPeak, Tuple::PeakDivisor, value);
255 else if (!strcmp_nocase(key, "REPLAYGAIN_ALBUM_GAIN"))
256 tuple.set_gain(Tuple::AlbumGain, Tuple::GainDivisor, value);
257 else if (!strcmp_nocase(key, "REPLAYGAIN_ALBUM_PEAK"))
258 tuple.set_gain(Tuple::AlbumPeak, Tuple::PeakDivisor, value);
259 }
260
read_tag(const char * filename,VFSFile & file,Tuple & tuple,Index<char> * image)261 bool FLACng::read_tag (const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image)
262 {
263 AUDDBG("Probe for tuple.\n");
264
265 FLAC__Metadata_Iterator *iter;
266 FLAC__Metadata_Chain *chain;
267 FLAC__StreamMetadata *metadata = nullptr;
268 FLAC__Metadata_ChainStatus status;
269 FLAC__StreamMetadata_VorbisComment_Entry *entry;
270 char *key;
271 char *value;
272
273 tuple.set_str (Tuple::Codec, "Free Lossless Audio Codec (FLAC)");
274 tuple.set_str (Tuple::Quality, _("lossless"));
275
276 chain = FLAC__metadata_chain_new();
277
278 if (!FLAC__metadata_chain_read_with_callbacks(chain, &file, io_callbacks))
279 goto ERR;
280
281 iter = FLAC__metadata_iterator_new();
282
283 FLAC__metadata_iterator_init(iter, chain);
284
285 do
286 {
287 switch (FLAC__metadata_iterator_get_block_type(iter))
288 {
289 case FLAC__METADATA_TYPE_VORBIS_COMMENT:
290 {
291 metadata = FLAC__metadata_iterator_get_block(iter);
292
293 AUDDBG("Vorbis comment contains %d fields\n", metadata->data.vorbis_comment.num_comments);
294 AUDDBG("Vendor string: %s\n", metadata->data.vorbis_comment.vendor_string.entry);
295
296 entry = metadata->data.vorbis_comment.comments;
297
298 for (unsigned i = 0; i < metadata->data.vorbis_comment.num_comments; i++, entry++)
299 {
300 if (FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(*entry, &key, &value) == false)
301 AUDDBG("Could not parse comment\n");
302 else
303 {
304 parse_comment(tuple, key, value);
305 free(key);
306 free(value);
307 }
308 }
309 break;
310 }
311
312 case FLAC__METADATA_TYPE_STREAMINFO:
313 {
314 metadata = FLAC__metadata_iterator_get_block(iter);
315
316 /* Calculate the stream length (milliseconds) */
317 if (metadata->data.stream_info.sample_rate == 0)
318 {
319 AUDERR("Invalid sample rate for stream!\n");
320 tuple.set_int (Tuple::Length, -1);
321 }
322 else
323 {
324 tuple.set_int (Tuple::Length,
325 (metadata->data.stream_info.total_samples / metadata->data.stream_info.sample_rate) * 1000);
326 AUDDBG("Stream length: %d seconds\n", tuple.get_int (Tuple::Length));
327 }
328
329 int64_t size = file.fsize ();
330
331 if (size < 0 || metadata->data.stream_info.total_samples == 0)
332 tuple.set_int (Tuple::Bitrate, 0);
333 else
334 {
335 int bitrate = 8 * size *
336 (int64_t) metadata->data.stream_info.sample_rate / metadata->data.stream_info.total_samples;
337
338 tuple.set_int (Tuple::Bitrate, (bitrate + 500) / 1000);
339 }
340
341 if (metadata->data.stream_info.channels > 0)
342 tuple.set_int(Tuple::Channels, metadata->data.stream_info.channels);
343 break;
344 }
345
346 case FLAC__METADATA_TYPE_PICTURE:
347 {
348 if (image && !image->len())
349 {
350 metadata = FLAC__metadata_iterator_get_block(iter);
351
352 if (metadata->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER)
353 {
354 AUDDBG("FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER found.");
355 image->insert((const char *) metadata->data.picture.data, 0,
356 metadata->data.picture.data_length);
357 }
358 }
359 break;
360 }
361
362 default:
363 ;
364 }
365 } while (FLAC__metadata_iterator_next(iter));
366
367 FLAC__metadata_iterator_delete(iter);
368 FLAC__metadata_chain_delete(chain);
369
370 return true;
371
372 ERR:
373 status = FLAC__metadata_chain_status(chain);
374 FLAC__metadata_chain_delete(chain);
375
376 AUDERR("An error occurred: %s\n", FLAC__Metadata_ChainStatusString[status]);
377 return false;
378 }
379