summaryrefslogtreecommitdiff
path: root/apps/metadata/asf.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/metadata/asf.c')
-rw-r--r--apps/metadata/asf.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/apps/metadata/asf.c b/apps/metadata/asf.c
new file mode 100644
index 0000000000..85d30f50fd
--- /dev/null
+++ b/apps/metadata/asf.c
@@ -0,0 +1,418 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * $Id$
10 *
11 * Copyright (C) 2007 Dave Chapman
12 *
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20#include <stdio.h>
21#include <string.h>
22#include <stdlib.h>
23#include <ctype.h>
24#include <inttypes.h>
25
26#include "id3.h"
27#include "debug.h"
28#include "rbunicode.h"
29#include "metadata_common.h"
30#include <codecs/libwma/asf.h>
31
32static asf_waveformatex_t wfx;
33
34/* TODO: Just read the GUIDs into a 16-byte array, and use memcmp to compare */
35struct guid_s {
36 uint32_t v1;
37 uint16_t v2;
38 uint16_t v3;
39 uint8_t v4[8];
40};
41typedef struct guid_s guid_t;
42
43struct asf_object_s {
44 guid_t guid;
45 uint64_t size;
46 uint64_t datalen;
47};
48typedef struct asf_object_s asf_object_t;
49
50enum asf_error_e {
51 ASF_ERROR_INTERNAL = -1, /* incorrect input to API calls */
52 ASF_ERROR_OUTOFMEM = -2, /* some malloc inside program failed */
53 ASF_ERROR_EOF = -3, /* unexpected end of file */
54 ASF_ERROR_IO = -4, /* error reading or writing to file */
55 ASF_ERROR_INVALID_LENGTH = -5, /* length value conflict in input data */
56 ASF_ERROR_INVALID_VALUE = -6, /* other value conflict in input data */
57 ASF_ERROR_INVALID_OBJECT = -7, /* ASF object missing or in wrong place */
58 ASF_ERROR_OBJECT_SIZE = -8, /* invalid ASF object size (too small) */
59 ASF_ERROR_SEEKABLE = -9, /* file not seekable */
60 ASF_ERROR_SEEK = -10 /* file is seekable but seeking failed */
61};
62
63static const guid_t asf_guid_null =
64{0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
65
66/* top level object guids */
67
68static const guid_t asf_guid_header =
69{0x75B22630, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}};
70
71static const guid_t asf_guid_data =
72{0x75B22636, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}};
73
74static const guid_t asf_guid_index =
75{0x33000890, 0xE5B1, 0x11CF, {0x89, 0xF4, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB}};
76
77/* header level object guids */
78
79static const guid_t asf_guid_file_properties =
80{0x8cabdca1, 0xa947, 0x11cf, {0x8E, 0xe4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}};
81
82static const guid_t asf_guid_stream_properties =
83{0xB7DC0791, 0xA9B7, 0x11CF, {0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}};
84
85static const guid_t asf_guid_content_description =
86{0x75B22633, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}};
87
88static const guid_t asf_guid_extended_content_description =
89{0xD2D0A440, 0xE307, 0x11D2, {0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50}};
90
91/* stream type guids */
92
93static const guid_t asf_guid_stream_type_audio =
94{0xF8699E40, 0x5B4D, 0x11CF, {0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}};
95
96static int asf_guid_match(const guid_t *guid1, const guid_t *guid2)
97{
98 if((guid1->v1 != guid2->v1) ||
99 (guid1->v2 != guid2->v2) ||
100 (guid1->v3 != guid2->v3) ||
101 (memcmp(guid1->v4, guid2->v4, 8))) {
102 return 0;
103 }
104
105 return 1;
106}
107
108/* Read the 16 byte GUID from a file */
109static void asf_readGUID(int fd, guid_t* guid)
110{
111 read_uint32le(fd, &guid->v1);
112 read_uint16le(fd, &guid->v2);
113 read_uint16le(fd, &guid->v3);
114 read(fd, guid->v4, 8);
115}
116
117static void asf_read_object_header(asf_object_t *obj, int fd)
118{
119 asf_readGUID(fd, &obj->guid);
120 read_uint64le(fd, &obj->size);
121 obj->datalen = 0;
122}
123
124static int asf_parse_header(int fd, struct mp3entry* id3)
125{
126 asf_object_t current;
127 asf_object_t header;
128 uint64_t datalen;
129 int i;
130 int fileprop = 0;
131 uint64_t play_duration;
132 uint64_t tmp64;
133 uint32_t tmp32;
134 uint16_t tmp16;
135 uint8_t tmp8;
136 uint16_t flags;
137 uint32_t subobjects;
138 uint8_t utf16buf[512];
139 uint8_t utf8buf[512];
140
141 asf_read_object_header((asf_object_t *) &header, fd);
142
143 DEBUGF("header.size=%d\n",(int)header.size);
144 if (header.size < 30) {
145 /* invalid size for header object */
146 return ASF_ERROR_OBJECT_SIZE;
147 }
148
149 read_uint32le(fd, &subobjects);
150
151 /* Two reserved bytes - do we need to read them? */
152 lseek(fd, 2, SEEK_CUR);
153
154 DEBUGF("Read header - size=%d, subobjects=%lu\n",(int)header.size, subobjects);
155
156 if (subobjects > 0) {
157 header.datalen = header.size - 30;
158
159 /* TODO: Check that we have datalen bytes left in the file */
160 datalen = header.datalen;
161
162 for (i=0; i<(int)subobjects; i++) {
163 DEBUGF("Parsing header object %d - datalen=%d\n",i,(int)datalen);
164 if (datalen < 24) {
165 DEBUGF("not enough data for reading object\n");
166 break;
167 }
168
169 asf_read_object_header(&current, fd);
170
171 if (current.size > datalen || current.size < 24) {
172 DEBUGF("invalid object size - current.size=%d, datalen=%d\n",(int)current.size,(int)datalen);
173 break;
174 }
175
176 if (asf_guid_match(&current.guid, &asf_guid_file_properties)) {
177 if (current.size < 104)
178 return ASF_ERROR_OBJECT_SIZE;
179
180 if (fileprop) {
181 /* multiple file properties objects not allowed */
182 return ASF_ERROR_INVALID_OBJECT;
183 }
184
185 fileprop = 1;
186 /* All we want is the play duration - uint64_t at offset 40 */
187 lseek(fd, 40, SEEK_CUR);
188
189 read_uint64le(fd, &play_duration);
190 id3->length = play_duration / 10000;
191
192 DEBUGF("****** length = %lums\n", id3->length);
193
194 /* Read the packet size - uint32_t at offset 68 */
195 lseek(fd, 20, SEEK_CUR);
196 read_uint32le(fd, &wfx.packet_size);
197
198 /* Skip bytes remaining in object */
199 lseek(fd, current.size - 24 - 72, SEEK_CUR);
200 } else if (asf_guid_match(&current.guid, &asf_guid_stream_properties)) {
201 guid_t guid;
202 uint32_t propdatalen;
203
204 if (current.size < 78)
205 return ASF_ERROR_OBJECT_SIZE;
206
207#if 0
208 asf_byteio_getGUID(&guid, current->data);
209 datalen = asf_byteio_getDWLE(current->data + 40);
210 flags = asf_byteio_getWLE(current->data + 48);
211#endif
212
213 asf_readGUID(fd, &guid);
214
215 lseek(fd, 24, SEEK_CUR);
216 read_uint32le(fd, &propdatalen);
217 lseek(fd, 4, SEEK_CUR);
218 read_uint16le(fd, &flags);
219
220 if (!asf_guid_match(&guid, &asf_guid_stream_type_audio)) {
221 DEBUGF("Found stream properties for non audio stream, skipping\n");
222 lseek(fd,current.size - 24 - 50,SEEK_CUR);
223 } else {
224 lseek(fd, 4, SEEK_CUR);
225 DEBUGF("Found stream properties for audio stream %d\n",flags&0x7f);
226
227 /* TODO: Check codec_id and find the lowest numbered audio stream in the file */
228 wfx.audiostream = flags&0x7f;
229
230 if (propdatalen < 18) {
231 return ASF_ERROR_INVALID_LENGTH;
232 }
233
234#if 0
235 if (asf_byteio_getWLE(data + 16) > datalen - 16) {
236 return ASF_ERROR_INVALID_LENGTH;
237 }
238#endif
239 read_uint16le(fd, &wfx.codec_id);
240 read_uint16le(fd, &wfx.channels);
241 read_uint32le(fd, &wfx.rate);
242 read_uint32le(fd, &wfx.bitrate);
243 wfx.bitrate *= 8;
244 read_uint16le(fd, &wfx.blockalign);
245 read_uint16le(fd, &wfx.bitspersample);
246 read_uint16le(fd, &wfx.datalen);
247
248 /* Round bitrate to the nearest kbit */
249 id3->bitrate = (wfx.bitrate + 500) / 1000;
250 id3->frequency = wfx.rate;
251
252 if (wfx.codec_id == ASF_CODEC_ID_WMAV1) {
253 read(fd, wfx.data, 4);
254 lseek(fd,current.size - 24 - 72 - 4,SEEK_CUR);
255 /* A hack - copy the wfx struct to the MP3 TOC field in the id3 struct */
256 memcpy(id3->toc, &wfx, sizeof(wfx));
257 } else if (wfx.codec_id == ASF_CODEC_ID_WMAV2) {
258 read(fd, wfx.data, 6);
259 lseek(fd,current.size - 24 - 72 - 6,SEEK_CUR);
260 /* A hack - copy the wfx struct to the MP3 TOC field in the id3 struct */
261 memcpy(id3->toc, &wfx, sizeof(wfx));
262 } else {
263 lseek(fd,current.size - 24 - 72,SEEK_CUR);
264 }
265
266 }
267 } else if (asf_guid_match(&current.guid, &asf_guid_content_description)) {
268 /* Object contains five 16-bit string lengths, followed by the five strings:
269 title, artist, copyright, description, rating
270 */
271 uint16_t strlength[5];
272 int i;
273
274 DEBUGF("Found GUID_CONTENT_DESCRIPTION - size=%d\n",(int)(current.size - 24));
275
276 /* Read the 5 string lengths - number of bytes included trailing zero */
277 for (i=0; i<5; i++) {
278 read_uint16le(fd, &strlength[i]);
279 DEBUGF("strlength = %u\n",strlength[i]);
280 }
281
282 for (i=0; i<5 ; i++) {
283 if (strlength[i] > 0) {
284 read(fd, utf16buf, strlength[i]);
285 utf16LEdecode(utf16buf, utf8buf, strlength[i]);
286 DEBUGF("TAG %d = %s\n",i,utf8buf);
287 }
288 }
289 } else if (asf_guid_match(&current.guid, &asf_guid_extended_content_description)) {
290 uint16_t count;
291 int i;
292 int bytesleft = current.size - 24;
293 DEBUGF("Found GUID_EXTENDED_CONTENT_DESCRIPTION\n");
294
295 read_uint16le(fd, &count);
296 bytesleft -= 2;
297 DEBUGF("extended metadata count = %u\n",count);
298
299 for (i=0; i < count; i++) {
300 uint16_t length, type;
301
302 read_uint16le(fd, &length);
303 read(fd, utf16buf, length);
304 utf16LEdecode(utf16buf, utf8buf, length);
305 DEBUGF("Key=\"%s\" ",utf8buf);
306 bytesleft -= 2 + length;
307
308 read_uint16le(fd, &type);
309 read_uint16le(fd, &length);
310 switch(type)
311 {
312 case 0: /* String */
313 read(fd, utf16buf, length);
314 utf16LEdecode(utf16buf, utf8buf, length);
315 DEBUGF("Value=\"%s\"\n",utf8buf);
316 break;
317
318 case 1: /* Hex string */
319 DEBUGF("Value=NOT YET IMPLEMENTED (HEX STRING)\n");
320 lseek(fd,length,SEEK_CUR);
321 break;
322
323 case 2: /* Bool */
324 read(fd, &tmp8, 1);
325 DEBUGF("Value=%s\n",(tmp8 ? "TRUE" : "FALSE"));
326 lseek(fd,length - 1,SEEK_CUR);
327 break;
328
329 case 3: /* 32-bit int */
330 read_uint32le(fd, &tmp32);
331 DEBUGF("Value=%lu\n",tmp32);
332 lseek(fd,length - 4,SEEK_CUR);
333 break;
334
335 case 4: /* 64-bit int */
336 read_uint64le(fd, &tmp64);
337 DEBUGF("Value=%llu\n",tmp64);
338 lseek(fd,length - 8,SEEK_CUR);
339 break;
340
341 case 5: /* 16-bit int */
342 read_uint16le(fd, &tmp16);
343 DEBUGF("Value=%u\n",tmp16);
344 lseek(fd,length - 2,SEEK_CUR);
345 break;
346
347 default:
348 lseek(fd,length,SEEK_CUR);
349 break;
350 }
351 bytesleft -= 4 + length;
352 }
353
354 lseek(fd, bytesleft, SEEK_CUR);
355 } else {
356 DEBUGF("Skipping %d bytes of object\n",(int)(current.size - 24));
357 lseek(fd,current.size - 24,SEEK_CUR);
358 }
359
360 DEBUGF("Parsed object - size = %d\n",(int)current.size);
361 datalen -= current.size;
362 }
363
364 if (i != (int)subobjects || datalen != 0) {
365 DEBUGF("header data doesn't match given subobject count\n");
366 return ASF_ERROR_INVALID_VALUE;
367 }
368
369 DEBUGF("%d subobjects read successfully\n", i);
370 }
371
372#if 0
373 tmp = asf_parse_header_validate(file, &header);
374 if (tmp < 0) {
375 /* header read ok but doesn't validate correctly */
376 return tmp;
377 }
378#endif
379
380 DEBUGF("header validated correctly\n");
381
382 return 0;
383}
384
385bool get_asf_metadata(int fd, struct mp3entry* id3)
386{
387 int res;
388 asf_object_t obj;
389
390 wfx.audiostream = -1;
391
392 res = asf_parse_header(fd, id3);
393
394 if (res < 0) {
395 DEBUGF("ASF: parsing error - %d\n",res);
396 return false;
397 }
398
399 if (wfx.audiostream == -1) {
400 DEBUGF("ASF: No WMA streams found\n");
401 return false;
402 }
403
404 asf_read_object_header(&obj, fd);
405
406 if (!asf_guid_match(&obj.guid, &asf_guid_data)) {
407 DEBUGF("ASF: No data object found\n");
408 return false;
409 }
410
411 /* Store the current file position - no need to parse the header
412 again in the codec. The +26 skips the rest of the data object
413 header.
414 */
415 id3->first_frame_offset = lseek(fd, 0, SEEK_CUR) + 26;
416
417 return true;
418}