1//=========================================================================
2// FILENAME	: tagutils-asf.c
3// DESCRIPTION	: ASF (wma/wmv) metadata reader
4//=========================================================================
5// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
6//=========================================================================
7
8/*
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22#ifdef HAVE_MACHINE_ENDIAN_H
23#include <machine/endian.h>
24#else
25#include <endian.h>
26#endif
27
28static inline uint16_t
29le16_to_cpu(uint16_t le16)
30{
31#if __BYTE_ORDER == __LITTLE_ENDIAN
32	return le16;
33#else
34	uint16_t be16 = ((le16 << 8) & 0xff00) | ((le16 >> 8) & 0x00ff);
35	return be16;
36#endif
37}
38
39static inline uint32_t
40le32_to_cpu(uint32_t le32)
41{
42#if __BYTE_ORDER == __LITTLE_ENDIAN
43	return le32;
44#else
45	uint32_t be32 =
46		((le32 << 24) & 0xff000000) |
47		((le32 << 8) & 0x00ff0000) |
48		((le32 >> 8) & 0x0000ff00) |
49		((le32 >> 24) & 0x000000ff);
50	return be32;
51#endif
52}
53
54static inline uint64_t
55le64_to_cpu(uint64_t le64)
56{
57#if __BYTE_ORDER == __LITTLE_ENDIAN
58	return le64;
59#else
60	uint64_t be64;
61	uint8_t *le64p = (uint8_t*)&le64;
62	uint8_t *be64p = (uint8_t*)&be64;
63	be64p[0] = le64p[7];
64	be64p[1] = le64p[6];
65	be64p[2] = le64p[5];
66	be64p[3] = le64p[4];
67	be64p[4] = le64p[3];
68	be64p[5] = le64p[2];
69	be64p[6] = le64p[1];
70	be64p[7] = le64p[0];
71	return be64;
72#endif
73}
74
75static inline uint32_t
76cpu_to_be32(uint32_t cpu32)
77{
78#if __BYTE_ORDER == __LITTLE_ENDIAN
79	uint32_t be32 =
80		((cpu32 << 24) & 0xff000000) |
81		((cpu32 << 8) & 0x00ff0000) |
82		((cpu32 >> 8) & 0x0000ff00) |
83		((cpu32 >> 24) & 0x000000ff);
84	return be32;
85#else
86	return cpu32;
87#endif
88}
89
90static inline uint8_t
91fget_byte(FILE *fp)
92{
93	uint8_t d;
94
95	if (fread(&d, sizeof(d), 1, fp) != 1)
96		return 0;
97	return d;
98}
99
100static inline uint16_t
101fget_le16(FILE *fp)
102{
103	uint16_t d;
104
105	if (fread(&d, sizeof(d), 1, fp) != 1)
106		return 0;
107	d = le16_to_cpu(d);
108	return d;
109}
110
111static inline uint32_t
112fget_le32(FILE *fp)
113{
114	uint32_t d;
115
116	if (fread(&d, sizeof(d), 1, fp) != 1)
117		return 0;
118	d = le32_to_cpu(d);
119	return d;
120}
121
122// NOTE: support U+0000 ~ U+FFFF only.
123static int
124utf16le_to_utf8(char *dst, int n, uint16_t utf16le)
125{
126	uint16_t wc = le16_to_cpu(utf16le);
127	if (wc < 0x80)
128	{
129		if (n < 1)
130			return 0;
131		*dst++ = wc & 0xff;
132		return 1;
133	}
134	else if (wc < 0x800)
135	{
136		if (n < 2)
137			return 0;
138		*dst++ = 0xc0 | (wc>>6);
139		*dst++ = 0x80 | (wc & 0x3f);
140		return 2;
141	}
142	else
143	{
144		if (n < 3)
145			return 0;
146		*dst++ = 0xe0 | (wc>>12);
147		*dst++ = 0x80 | ((wc>>6) & 0x3f);
148		*dst++ = 0x80 | (wc & 0x3f);
149		return 3;
150	}
151}
152
153static int
154_asf_read_file_properties(FILE *fp, asf_file_properties_t *p, uint32_t size)
155{
156	int len;
157
158	len = sizeof(*p) - offsetof(asf_file_properties_t, FileID);
159	if(size < len)
160		return -1;
161
162	memset(p, 0, sizeof(*p));
163	p->ID = ASF_FileProperties;
164	p->Size = size;
165
166	if(len != fread(&p->FileID, 1, len, fp))
167		return -1;
168
169	return 0;
170}
171
172static void
173_pick_dlna_profile(struct song_metadata *psong, uint16_t format)
174{
175	/* DLNA Profile Name */
176	switch( le16_to_cpu(format) )
177	{
178		case WMA:
179			if( psong->max_bitrate < 193000 )
180				xasprintf(&(psong->dlna_pn), "WMABASE");
181			else if( psong->max_bitrate < 385000 )
182				xasprintf(&(psong->dlna_pn), "WMAFULL");
183			break;
184		case WMAPRO:
185			xasprintf(&(psong->dlna_pn), "WMAPRO");
186			break;
187		case WMALSL:
188			xasprintf(&(psong->dlna_pn), "WMALSL%s",
189				psong->channels > 2 ? "_MULT5" : "");
190		default:
191			break;
192	}
193}
194
195static int
196_asf_read_audio_stream(FILE *fp, struct song_metadata *psong, int size)
197{
198	asf_audio_stream_t s;
199	int len;
200
201	len = sizeof(s) - sizeof(s.Hdr);
202	if(len > size)
203		len = size;
204
205	if(len != fread(&s.wfx, 1, len, fp))
206		return -1;
207
208	psong->channels = le16_to_cpu(s.wfx.nChannels);
209	psong->bitrate = le32_to_cpu(s.wfx.nAvgBytesPerSec) * 8;
210	psong->samplerate = le32_to_cpu(s.wfx.nSamplesPerSec);
211	if (!psong->max_bitrate)
212		psong->max_bitrate = psong->bitrate;
213	_pick_dlna_profile(psong, s.wfx.wFormatTag);
214
215	return 0;
216}
217
218static int
219_asf_read_media_stream(FILE *fp, struct song_metadata *psong, uint32_t size)
220{
221	asf_media_stream_t s;
222	avi_audio_format_t wfx;
223	int len;
224
225	len = sizeof(s) - sizeof(s.Hdr);
226	if(len > size)
227		len = size;
228
229	memset(&s, 0, sizeof(s));
230
231	if(len != fread(&s.MajorType, 1, len, fp))
232		return -1;
233
234	if(IsEqualGUID(&s.MajorType, &ASF_MediaTypeAudio) &&
235	   IsEqualGUID(&s.FormatType, &ASF_FormatTypeWave) && s.FormatSize >= sizeof(wfx))
236	{
237
238		if(sizeof(wfx) != fread(&wfx, 1, sizeof(wfx), fp))
239			return -1;
240
241		psong->channels = le16_to_cpu(wfx.nChannels);
242		psong->bitrate = le32_to_cpu(wfx.nAvgBytesPerSec) * 8;
243		psong->samplerate = le32_to_cpu(wfx.nSamplesPerSec);
244		if (!psong->max_bitrate)
245			psong->max_bitrate = psong->bitrate;
246		_pick_dlna_profile(psong, wfx.wFormatTag);
247	}
248
249	return 0;
250}
251
252static int
253_asf_read_stream_object(FILE *fp, struct song_metadata *psong, uint32_t size)
254{
255	asf_stream_object_t s;
256	int len;
257
258	len = sizeof(s) - sizeof(asf_object_t);
259	if(size < len)
260		return -1;
261
262	memset(&s, 0, sizeof(s));
263
264	if(len != fread(&s.StreamType, 1, len, fp))
265		return -1;
266
267	if(IsEqualGUID(&s.StreamType, &ASF_AudioStream))
268		_asf_read_audio_stream(fp, psong, s.TypeSpecificSize);
269	else if(IsEqualGUID(&s.StreamType, &ASF_StreamBufferStream))
270		_asf_read_media_stream(fp, psong, s.TypeSpecificSize);
271	else if(!IsEqualGUID(&s.StreamType, &ASF_VideoStream))
272	{
273		DPRINTF(E_ERROR, L_SCANNER, "Unknown asf stream type.\n");
274	}
275
276	return 0;
277}
278
279static int
280_asf_read_extended_stream_object(FILE *fp, struct song_metadata *psong, uint32_t size)
281{
282	int i, len;
283	long off;
284	asf_object_t tmp;
285	asf_extended_stream_object_t xs;
286	asf_stream_name_t nm;
287	asf_payload_extension_t pe;
288
289	if(size < sizeof(asf_extended_stream_object_t))
290		return -1;
291
292	memset(&xs, 0, sizeof(xs));
293
294	len = sizeof(xs) - offsetof(asf_extended_stream_object_t, StartTime);
295	if(len != fread(&xs.StartTime, 1, len, fp))
296		return -1;
297	off = sizeof(xs);
298
299	for(i = 0; i < xs.StreamNameCount; i++)
300	{
301		if(off + sizeof(nm) > size)
302			return -1;
303		if(sizeof(nm) != fread(&nm, 1, sizeof(nm), fp))
304			return -1;
305		off += sizeof(nm);
306		if(off + nm.Length > sizeof(asf_extended_stream_object_t))
307			return -1;
308		if(nm.Length > 0)
309			fseek(fp, nm.Length, SEEK_CUR);
310		off += nm.Length;
311	}
312
313	for(i = 0; i < xs.PayloadExtensionSystemCount; i++)
314	{
315		if(off + sizeof(pe) > size)
316			return -1;
317		if(sizeof(pe) != fread(&pe, 1, sizeof(pe), fp))
318			return -1;
319		off += sizeof(pe);
320		if(pe.InfoLength > 0)
321			fseek(fp, pe.InfoLength, SEEK_CUR);
322		off += pe.InfoLength;
323	}
324
325	if(off < size)
326	{
327		if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
328			return -1;
329		if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader))
330			_asf_read_stream_object(fp, psong, tmp.Size);
331	}
332
333	return 0;
334}
335
336static int
337_asf_read_header_extension(FILE *fp, struct song_metadata *psong, uint32_t size)
338{
339	off_t pos;
340	long off;
341	asf_header_extension_t ext;
342	asf_object_t tmp;
343
344	if(size < sizeof(asf_header_extension_t))
345		return -1;
346
347	if(sizeof(ext.Reserved1) != fread(&ext.Reserved1, 1, sizeof(ext.Reserved1), fp))
348		return -1;
349	ext.Reserved2 = fget_le16(fp);
350	ext.DataSize = fget_le32(fp);
351
352	pos = ftell(fp);
353	off = 0;
354	while(off < ext.DataSize)
355	{
356		if(sizeof(asf_header_extension_t) + off > size)
357			break;
358		if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
359			break;
360		if(off + tmp.Size > ext.DataSize)
361			break;
362		if(IsEqualGUID(&tmp.ID, &ASF_ExtendedStreamPropertiesObject))
363			_asf_read_extended_stream_object(fp, psong, tmp.Size);
364
365		off += tmp.Size;
366		fseek(fp, pos + off, SEEK_SET);
367	}
368
369	return 0;
370}
371
372static int
373_asf_load_string(FILE *fp, int type, int size, char *buf, int len)
374{
375	unsigned char data[2048];
376	uint16_t wc;
377	int i, j;
378	int16_t *wd16;
379	int32_t *wd32;
380	int64_t *wd64;
381
382	i = 0;
383	if(size && (size <= sizeof(data)) && (size == fread(data, 1, size, fp)))
384	{
385
386		switch(type)
387		{
388		case ASF_VT_UNICODE:
389			for(j = 0; j < size; j += 2)
390			{
391				wd16 = (int16_t *) &data[j];
392				wc = (uint16_t)*wd16;
393				i += utf16le_to_utf8(&buf[i], len - i, wc);
394			}
395			break;
396		case ASF_VT_BYTEARRAY:
397			for(i = 0; i < size; i++)
398			{
399				if(i + 1 >= len)
400					break;
401				buf[i] = data[i];
402			}
403			break;
404		case ASF_VT_BOOL:
405		case ASF_VT_DWORD:
406			if(size >= 4)
407			{
408				wd32 = (int32_t *) &data[0];
409				i = snprintf(buf, len, "%d", le32_to_cpu(*wd32));
410			}
411			break;
412		case ASF_VT_QWORD:
413			if(size >= 8)
414			{
415				wd64 = (int64_t *) &data[0];
416				i = snprintf(buf, len, "%lld", (long long)le64_to_cpu(*wd64));
417			}
418			break;
419		case ASF_VT_WORD:
420			if(size >= 2)
421			{
422				wd16 = (int16_t *) &data[0];
423				i = snprintf(buf, len, "%d", le16_to_cpu(*wd16));
424			}
425			break;
426		}
427
428		size = 0;
429	}
430	else fseek(fp, size, SEEK_CUR);
431
432	buf[i] = 0;
433	return i;
434}
435
436static void *
437_asf_load_picture(FILE *fp, int size, void *bm, int *bm_size)
438{
439	int i;
440	char buf[256];
441#if 0
442	//
443	// Picture type       $xx
444	// Data length	  $xx $xx $xx $xx
445	// MIME type          <text string> $00
446	// Description        <text string> $00
447	// Picture data       <binary data>
448
449	char pic_type;
450	long pic_size;
451
452	pic_type = fget_byte(fp); size -= 1;
453	pic_size = fget_le32(fp); size -= 4;
454#else
455	fseek(fp, 5, SEEK_CUR);
456	size -= 5;
457#endif
458	for(i = 0; i < sizeof(buf) - 1; i++)
459	{
460		buf[i] = fget_le16(fp); size -= 2;
461		if(!buf[i])
462			break;
463	}
464	buf[i] = '\0';
465	if(i == sizeof(buf) - 1)
466	{
467		while(fget_le16(fp))
468			size -= 2;
469	}
470
471	if(!strcasecmp(buf, "image/jpeg") ||
472	   !strcasecmp(buf, "image/jpg") ||
473	   !strcasecmp(buf, "image/peg"))
474	{
475
476		while(0 != fget_le16(fp))
477			size -= 2;
478
479		if(size > 0)
480		{
481			if(!(bm = malloc(size)))
482			{
483				DPRINTF(E_ERROR, L_SCANNER, "Couldn't allocate %d bytes\n", size);
484			}
485			else
486			{
487				*bm_size = size;
488				if(size > *bm_size || fread(bm, 1, size, fp) != size)
489				{
490					DPRINTF(E_ERROR, L_SCANNER, "Overrun %d bytes required\n", size);
491					free(bm);
492					bm = NULL;
493				}
494			}
495		}
496		else
497		{
498			DPRINTF(E_ERROR, L_SCANNER, "No binary data\n");
499			size = 0;
500			bm = NULL;
501		}
502	}
503	else
504	{
505		DPRINTF(E_ERROR, L_SCANNER, "Invalid mime type %s\n", buf);
506	}
507
508	*bm_size = size;
509	return bm;
510}
511
512static int
513_get_asffileinfo(char *file, struct song_metadata *psong)
514{
515	FILE *fp;
516	asf_object_t hdr;
517	asf_object_t tmp;
518	unsigned long NumObjects;
519	unsigned short TitleLength;
520	unsigned short AuthorLength;
521	unsigned short CopyrightLength;
522	unsigned short DescriptionLength;
523	unsigned short RatingLength;
524	unsigned short NumEntries;
525	unsigned short NameLength;
526	unsigned short ValueType;
527	unsigned short ValueLength;
528	off_t pos;
529	char buf[2048];
530	asf_file_properties_t FileProperties;
531
532	psong->vbr_scale = -1;
533
534	if(!(fp = fopen(file, "rb")))
535	{
536		DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
537		return -1;
538	}
539
540	if(sizeof(hdr) != fread(&hdr, 1, sizeof(hdr), fp))
541	{
542		DPRINTF(E_ERROR, L_SCANNER, "Error reading %s\n", file);
543		fclose(fp);
544		return -1;
545	}
546	hdr.Size = le64_to_cpu(hdr.Size);
547
548	if(!IsEqualGUID(&hdr.ID, &ASF_HeaderObject))
549	{
550		DPRINTF(E_ERROR, L_SCANNER, "Not a valid header\n");
551		fclose(fp);
552		return -1;
553	}
554	NumObjects = fget_le32(fp);
555	fseek(fp, 2, SEEK_CUR); // Reserved le16
556
557	pos = ftell(fp);
558	while(NumObjects > 0)
559	{
560		if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
561			break;
562		tmp.Size = le64_to_cpu(tmp.Size);
563
564		if(pos + tmp.Size > hdr.Size)
565		{
566			DPRINTF(E_ERROR, L_SCANNER, "Size overrun reading header object %llu\n",
567				(unsigned long long)tmp.Size);
568			break;
569		}
570
571		if(IsEqualGUID(&tmp.ID, &ASF_FileProperties))
572		{
573			_asf_read_file_properties(fp, &FileProperties, tmp.Size);
574			psong->song_length = le64_to_cpu(FileProperties.PlayDuration) / 10000;
575			psong->bitrate = le64_to_cpu(FileProperties.MaxBitrate);
576			psong->max_bitrate = psong->bitrate;
577		}
578		else if(IsEqualGUID(&tmp.ID, &ASF_ContentDescription))
579		{
580			TitleLength = fget_le16(fp);
581			AuthorLength = fget_le16(fp);
582			CopyrightLength = fget_le16(fp);
583			DescriptionLength = fget_le16(fp);
584			RatingLength = fget_le16(fp);
585
586			if(_asf_load_string(fp, ASF_VT_UNICODE, TitleLength, buf, sizeof(buf)))
587			{
588				if(buf[0])
589					psong->title = strdup(buf);
590			}
591			if(_asf_load_string(fp, ASF_VT_UNICODE, AuthorLength, buf, sizeof(buf)))
592			{
593				if(buf[0])
594					psong->contributor[ROLE_TRACKARTIST] = strdup(buf);
595			}
596			if(CopyrightLength)
597				fseek(fp, CopyrightLength, SEEK_CUR);
598			if(DescriptionLength)
599				fseek(fp, DescriptionLength, SEEK_CUR);
600			if(RatingLength)
601				fseek(fp, RatingLength, SEEK_CUR);
602		}
603		else if(IsEqualGUID(&tmp.ID, &ASF_ExtendedContentDescription))
604		{
605			NumEntries = fget_le16(fp);
606			while(NumEntries > 0)
607			{
608				NameLength = fget_le16(fp);
609				_asf_load_string(fp, ASF_VT_UNICODE, NameLength, buf, sizeof(buf));
610				ValueType = fget_le16(fp);
611				ValueLength = fget_le16(fp);
612
613				if(!strcasecmp(buf, "AlbumTitle") || !strcasecmp(buf, "WM/AlbumTitle"))
614				{
615					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
616						if(buf[0])
617							psong->album = strdup(buf);
618				}
619				else if(!strcasecmp(buf, "AlbumArtist") || !strcasecmp(buf, "WM/AlbumArtist"))
620				{
621					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
622					{
623						if(buf[0])
624							psong->contributor[ROLE_ALBUMARTIST] = strdup(buf);
625					}
626				}
627				else if(!strcasecmp(buf, "Description") || !strcasecmp(buf, "WM/Track"))
628				{
629					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
630						if(buf[0])
631							psong->track = atoi(buf);
632				}
633				else if(!strcasecmp(buf, "Genre") || !strcasecmp(buf, "WM/Genre"))
634				{
635					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
636						if(buf[0])
637							psong->genre = strdup(buf);
638				}
639				else if(!strcasecmp(buf, "Year") || !strcasecmp(buf, "WM/Year"))
640				{
641					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
642						if(buf[0])
643							psong->year = atoi(buf);
644				}
645				else if(!strcasecmp(buf, "WM/Director"))
646				{
647					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
648						if(buf[0])
649							psong->contributor[ROLE_CONDUCTOR] = strdup(buf);
650				}
651				else if(!strcasecmp(buf, "WM/Composer"))
652				{
653					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
654						if(buf[0])
655							psong->contributor[ROLE_COMPOSER] = strdup(buf);
656				}
657				else if(!strcasecmp(buf, "WM/Picture") && (ValueType == ASF_VT_BYTEARRAY))
658				{
659					psong->image = _asf_load_picture(fp, ValueLength, psong->image, &psong->image_size);
660				}
661				else if(!strcasecmp(buf, "TrackNumber") || !strcasecmp(buf, "WM/TrackNumber"))
662				{
663					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
664						if(buf[0])
665							psong->track = atoi(buf);
666				}
667				else if(!strcasecmp(buf, "isVBR"))
668				{
669					fseek(fp, ValueLength, SEEK_CUR);
670					psong->vbr_scale = 0;
671				}
672				else if(ValueLength)
673				{
674					fseek(fp, ValueLength, SEEK_CUR);
675				}
676				NumEntries--;
677			}
678		}
679		else if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader))
680		{
681			_asf_read_stream_object(fp, psong, tmp.Size);
682		}
683		else if(IsEqualGUID(&tmp.ID, &ASF_HeaderExtension))
684		{
685			_asf_read_header_extension(fp, psong, tmp.Size);
686		}
687		pos += tmp.Size;
688		fseek(fp, pos, SEEK_SET);
689		NumObjects--;
690	}
691
692#if 0
693	if(sizeof(hdr) == fread(&hdr, 1, sizeof(hdr), fp) && IsEqualGUID(&hdr.ID, &ASF_DataObject))
694	{
695		if(psong->song_length)
696		{
697			psong->bitrate = (hdr.Size * 8000) / psong->song_length;
698		}
699	}
700#endif
701
702	fclose(fp);
703	return 0;
704}
705