diff options
Diffstat (limited to 'apps/metadata')
-rw-r--r-- | apps/metadata/wave.c | 192 |
1 files changed, 166 insertions, 26 deletions
diff --git a/apps/metadata/wave.c b/apps/metadata/wave.c index eeb4f0f343..cbf628a599 100644 --- a/apps/metadata/wave.c +++ b/apps/metadata/wave.c | |||
@@ -27,6 +27,7 @@ | |||
27 | #include "metadata.h" | 27 | #include "metadata.h" |
28 | #include "metadata_common.h" | 28 | #include "metadata_common.h" |
29 | #include "metadata_parsers.h" | 29 | #include "metadata_parsers.h" |
30 | #include "rbunicode.h" | ||
30 | #include "logf.h" | 31 | #include "logf.h" |
31 | 32 | ||
32 | /* Wave(RIFF)/Wave64 format */ | 33 | /* Wave(RIFF)/Wave64 format */ |
@@ -49,6 +50,7 @@ enum { | |||
49 | FMT_CHUNK, | 50 | FMT_CHUNK, |
50 | FACT_CHUNK, | 51 | FACT_CHUNK, |
51 | DATA_CHUNK, | 52 | DATA_CHUNK, |
53 | LIST_CHUNK, | ||
52 | }; | 54 | }; |
53 | 55 | ||
54 | /* Wave chunk names */ | 56 | /* Wave chunk names */ |
@@ -59,7 +61,8 @@ static const unsigned char *wave_chunklist = "RIFF" | |||
59 | "WAVE" | 61 | "WAVE" |
60 | "fmt " | 62 | "fmt " |
61 | "fact" | 63 | "fact" |
62 | "data"; | 64 | "data" |
65 | "LIST"; | ||
63 | 66 | ||
64 | /* Wave64 GUIDs */ | 67 | /* Wave64 GUIDs */ |
65 | #define WAVE64_CHUNKNAME_LENGTH 16 | 68 | #define WAVE64_CHUNKNAME_LENGTH 16 |
@@ -70,8 +73,38 @@ static const unsigned char *wave64_chunklist | |||
70 | "wave\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" | 73 | "wave\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" |
71 | "fmt \xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" | 74 | "fmt \xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" |
72 | "fact\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" | 75 | "fact\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" |
73 | "data\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a"; | 76 | "data\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" |
77 | "\xbc\x94\x5f\x92\x5a\x52\xd2\x11\x86\xdc\x00\xc0\x4f\x8e\xdb\x8a"; | ||
74 | 78 | ||
79 | enum { | ||
80 | INFO_TITLE = 0, | ||
81 | INFO_ARTIST, | ||
82 | INFO_ALBUM_ARTIST, | ||
83 | INFO_ALBUM, | ||
84 | INFO_COMPOSER, | ||
85 | INFO_COMMENT, | ||
86 | INFO_GROUPING, | ||
87 | INFO_GENRE, | ||
88 | INFO_DATE, | ||
89 | INFO_TRACK, | ||
90 | INFO_DISC, | ||
91 | }; | ||
92 | |||
93 | /* info chunk names are common wave/wave64 */ | ||
94 | static const unsigned char infochunk_list[][4] | ||
95 | = { | ||
96 | "INAM", /* title */ | ||
97 | "IART", /* artist */ | ||
98 | "ISBJ", /* albumartist */ | ||
99 | "IPRD", /* album */ | ||
100 | "IWRI", /* composer */ | ||
101 | "ICMT", /* comment */ | ||
102 | "ISRF", /* grouping */ | ||
103 | "IGNR", /* genre */ | ||
104 | "ICRD", /* date */ | ||
105 | "IPRT", /* track/trackcount */ | ||
106 | "IFRM", /* disc/disccount */ | ||
107 | }; | ||
75 | 108 | ||
76 | /* support formats */ | 109 | /* support formats */ |
77 | enum | 110 | enum |
@@ -102,6 +135,19 @@ struct wave_fmt { | |||
102 | uint64_t numbytes; | 135 | uint64_t numbytes; |
103 | }; | 136 | }; |
104 | 137 | ||
138 | static unsigned char *convert_utf8(unsigned char *src, unsigned char *dst, | ||
139 | int datasize, int bufsize, bool is_64) | ||
140 | { | ||
141 | int size = (datasize > bufsize)? bufsize : datasize; | ||
142 | |||
143 | if (is_64) | ||
144 | { | ||
145 | /* Note: wave64: metadata codepage is UTF-16 only */ | ||
146 | return utf16LEdecode(src, dst, size); | ||
147 | } | ||
148 | return iso_decode(src, dst, -1, size); | ||
149 | } | ||
150 | |||
105 | static void set_totalsamples(struct wave_fmt *fmt, struct mp3entry* id3) | 151 | static void set_totalsamples(struct wave_fmt *fmt, struct mp3entry* id3) |
106 | { | 152 | { |
107 | switch (fmt->formattag) | 153 | switch (fmt->formattag) |
@@ -197,6 +243,94 @@ static void parse_riff_format(unsigned char* buf, int fmtsize, struct wave_fmt * | |||
197 | } | 243 | } |
198 | } | 244 | } |
199 | 245 | ||
246 | static bool parse_list_chunk(int fd, struct mp3entry* id3, int chunksize, bool is_64) | ||
247 | { | ||
248 | unsigned char tmpbuf[ID3V2_BUF_SIZE]; | ||
249 | unsigned char *bp = tmpbuf; | ||
250 | unsigned char *endp; | ||
251 | unsigned char *data_pos; | ||
252 | unsigned char *curpos = id3->id3v2buf; | ||
253 | unsigned char *nextpos; | ||
254 | int datasize; | ||
255 | int infosize; | ||
256 | int remain = ID3V2_BUF_SIZE; | ||
257 | |||
258 | if (is_64) | ||
259 | lseek(fd, 4, SEEK_CUR); | ||
260 | else if (read(fd, bp, 4) < 4 || memcmp(bp, "INFO", 4)) | ||
261 | return false; | ||
262 | |||
263 | infosize = read(fd, bp, (ID3V2_BUF_SIZE > chunksize)? chunksize : ID3V2_BUF_SIZE); | ||
264 | if (infosize <= 8) | ||
265 | return false; | ||
266 | |||
267 | endp = bp + infosize; | ||
268 | while (bp < endp) | ||
269 | { | ||
270 | nextpos = curpos; | ||
271 | datasize = get_long_le(bp + 4); | ||
272 | data_pos = bp + 8; | ||
273 | remain = ID3V2_BUF_SIZE - (curpos - (unsigned char*)id3->id3v2buf); | ||
274 | if (remain <= 0) | ||
275 | break; | ||
276 | |||
277 | if (memcmp(bp, infochunk_list[INFO_TITLE], 4) == 0) | ||
278 | { | ||
279 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
280 | id3->title = curpos; | ||
281 | } | ||
282 | else if (memcmp(bp, infochunk_list[INFO_ARTIST], 4) == 0) | ||
283 | { | ||
284 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
285 | id3->artist = curpos; | ||
286 | } | ||
287 | else if (memcmp(bp, infochunk_list[INFO_ALBUM_ARTIST], 4) == 0) | ||
288 | { | ||
289 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
290 | id3->albumartist = curpos; | ||
291 | } | ||
292 | else if (memcmp(bp, infochunk_list[INFO_COMPOSER], 4) == 0) | ||
293 | { | ||
294 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
295 | id3->composer = curpos; | ||
296 | } | ||
297 | else if (memcmp(bp, infochunk_list[INFO_COMMENT], 4) == 0) | ||
298 | { | ||
299 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
300 | id3->comment = curpos; | ||
301 | } | ||
302 | else if (memcmp(bp, infochunk_list[INFO_GROUPING], 4) == 0) | ||
303 | { | ||
304 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
305 | id3->grouping = curpos; | ||
306 | } | ||
307 | else if (memcmp(bp, infochunk_list[INFO_GENRE], 4) == 0) | ||
308 | { | ||
309 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
310 | id3->genre_string = curpos; | ||
311 | } | ||
312 | else if (memcmp(bp, infochunk_list[INFO_DATE], 4) == 0) | ||
313 | { | ||
314 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
315 | id3->year_string = curpos; | ||
316 | } | ||
317 | else if (memcmp(bp, infochunk_list[INFO_TRACK], 4) == 0) | ||
318 | { | ||
319 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
320 | id3->track_string = curpos; | ||
321 | } | ||
322 | else if (memcmp(bp, infochunk_list[INFO_DISC], 4) == 0) | ||
323 | { | ||
324 | nextpos = convert_utf8(data_pos, curpos, datasize, remain, is_64); | ||
325 | id3->disc_string = curpos; | ||
326 | } | ||
327 | |||
328 | bp = data_pos + datasize + (datasize & 1); | ||
329 | curpos = nextpos; | ||
330 | }; | ||
331 | return true; | ||
332 | } | ||
333 | |||
200 | static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunknames, | 334 | static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunknames, |
201 | bool is_64) | 335 | bool is_64) |
202 | { | 336 | { |
@@ -213,6 +347,9 @@ static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunk | |||
213 | int read_data; | 347 | int read_data; |
214 | 348 | ||
215 | memset(&fmt, 0, sizeof(struct wave_fmt)); | 349 | memset(&fmt, 0, sizeof(struct wave_fmt)); |
350 | |||
351 | id3->vbr = false; /* All Wave/Wave64 files are CBR */ | ||
352 | id3->filesize = filesize(fd); | ||
216 | 353 | ||
217 | /* get RIFF chunk header */ | 354 | /* get RIFF chunk header */ |
218 | lseek(fd, 0, SEEK_SET); | 355 | lseek(fd, 0, SEEK_SET); |
@@ -226,34 +363,14 @@ static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunk | |||
226 | } | 363 | } |
227 | 364 | ||
228 | /* iterate over WAVE chunks until 'data' chunk */ | 365 | /* iterate over WAVE chunks until 'data' chunk */ |
229 | while (true) | 366 | while (read(fd, buf, len) > 0) |
230 | { | 367 | { |
231 | /* get chunk header */ | ||
232 | if (read(fd, buf, len) <= 0) | ||
233 | { | ||
234 | DEBUGF("metadata error: read error or missing 'data' chunk.\n"); | ||
235 | return false; | ||
236 | } | ||
237 | |||
238 | offset += len; | 368 | offset += len; |
239 | 369 | ||
240 | /* get chunk size (when the header is wave64, chunksize includes GUID and data length) */ | 370 | /* get chunk size (when the header is wave64, chunksize includes GUID and data length) */ |
241 | chunksize = (is_64) ? get_uint64_le(buf + namelen) - len : | 371 | chunksize = (is_64) ? get_uint64_le(buf + namelen) - len : |
242 | get_long_le(buf + namelen); | 372 | get_long_le(buf + namelen); |
243 | 373 | ||
244 | if (memcmp(buf, chunknames + DATA_CHUNK * namelen, namelen) == 0) | ||
245 | { | ||
246 | DEBUGF("find 'data' chunk\n"); | ||
247 | fmt.numbytes = chunksize; | ||
248 | if (fmt.formattag == WAVE_FORMAT_ATRAC3) | ||
249 | id3->first_frame_offset = offset; | ||
250 | break; | ||
251 | } | ||
252 | |||
253 | /* padded to next chunk */ | ||
254 | chunksize += ((is_64)? ((1 + ~chunksize) & 0x07) : (chunksize & 1)); | ||
255 | offset += chunksize; | ||
256 | |||
257 | read_data = 0; | 374 | read_data = 0; |
258 | if (memcmp(buf, chunknames + FMT_CHUNK * namelen, namelen) == 0) | 375 | if (memcmp(buf, chunknames + FMT_CHUNK * namelen, namelen) == 0) |
259 | { | 376 | { |
@@ -284,10 +401,36 @@ static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunk | |||
284 | fmt.totalsamples = (is_64)? get_uint64_le(buf) : get_long_le(buf); | 401 | fmt.totalsamples = (is_64)? get_uint64_le(buf) : get_long_le(buf); |
285 | } | 402 | } |
286 | } | 403 | } |
404 | else if (memcmp(buf, chunknames + DATA_CHUNK * namelen, namelen) == 0) | ||
405 | { | ||
406 | DEBUGF("find 'data' chunk\n"); | ||
407 | fmt.numbytes = chunksize; | ||
408 | if (fmt.formattag == WAVE_FORMAT_ATRAC3) | ||
409 | id3->first_frame_offset = offset; | ||
410 | } | ||
411 | else if (memcmp(buf, chunknames + LIST_CHUNK * namelen, namelen) == 0) | ||
412 | { | ||
413 | DEBUGF("find 'LIST' chunk\n"); | ||
414 | parse_list_chunk(fd, id3, chunksize, is_64); | ||
415 | lseek(fd, offset, SEEK_SET); | ||
416 | } | ||
417 | |||
418 | /* padded to next chunk */ | ||
419 | chunksize += ((is_64)? ((1 + ~chunksize) & 0x07) : (chunksize & 1)); | ||
420 | |||
421 | offset += chunksize; | ||
422 | if (offset >= id3->filesize) | ||
423 | break; | ||
287 | 424 | ||
288 | lseek(fd, chunksize - read_data, SEEK_CUR); | 425 | lseek(fd, chunksize - read_data, SEEK_CUR); |
289 | } | 426 | } |
290 | 427 | ||
428 | if (fmt.numbytes == 0) | ||
429 | { | ||
430 | DEBUGF("metadata error: read error or missing 'data' chunk.\n"); | ||
431 | return false; | ||
432 | } | ||
433 | |||
291 | if (fmt.totalsamples == 0) | 434 | if (fmt.totalsamples == 0) |
292 | set_totalsamples(&fmt, id3); | 435 | set_totalsamples(&fmt, id3); |
293 | 436 | ||
@@ -297,9 +440,6 @@ static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunk | |||
297 | return false; | 440 | return false; |
298 | } | 441 | } |
299 | 442 | ||
300 | id3->vbr = false; /* All Wave/Wave64 files are CBR */ | ||
301 | id3->filesize = filesize(fd); | ||
302 | |||
303 | /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ | 443 | /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ |
304 | id3->length = (fmt.formattag != WAVE_FORMAT_ATRAC3)? | 444 | id3->length = (fmt.formattag != WAVE_FORMAT_ATRAC3)? |
305 | (uint64_t)fmt.totalsamples * 1000 / id3->frequency : | 445 | (uint64_t)fmt.totalsamples * 1000 / id3->frequency : |