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
23static int
24_asf_read_file_properties(FILE *fp, asf_file_properties_t *p, __u32 size)
25{
26	int len;
27
28	len = sizeof(*p) - offsetof(asf_file_properties_t, FileID);
29	if(size < len)
30		return -1;
31
32	memset(p, 0, sizeof(*p));
33	p->ID = ASF_FileProperties;
34	p->Size = size;
35
36	if(len != fread(&p->FileID, 1, len, fp))
37		return -1;
38
39	return 0;
40}
41
42static void
43_pick_dlna_profile(struct song_metadata *psong, uint16_t format)
44{
45	/* DLNA Profile Name */
46	switch( le16_to_cpu(format) )
47	{
48		case WMA:
49			if( psong->max_bitrate < 193000 )
50				asprintf(&(psong->dlna_pn), "WMABASE");
51			else if( psong->max_bitrate < 385000 )
52				asprintf(&(psong->dlna_pn), "WMAFULL");
53			break;
54		case WMAPRO:
55			asprintf(&(psong->dlna_pn), "WMAPRO");
56			break;
57		case WMALSL:
58			asprintf(&(psong->dlna_pn), "WMALSL%s",
59				psong->channels > 2 ? "_MULT5" : "");
60		default:
61			break;
62	}
63}
64
65static int
66_asf_read_audio_stream(FILE *fp, struct song_metadata *psong, int size)
67{
68	asf_audio_stream_t s;
69	int len;
70
71	len = sizeof(s) - sizeof(s.Hdr);
72	if(len > size)
73		len = size;
74
75	if(len != fread(&s.wfx, 1, len, fp))
76		return -1;
77
78	psong->channels = le16_to_cpu(s.wfx.nChannels);
79	psong->bitrate = le32_to_cpu(s.wfx.nAvgBytesPerSec) * 8;
80	psong->samplerate = le32_to_cpu(s.wfx.nSamplesPerSec);
81	if (!psong->max_bitrate)
82		psong->max_bitrate = psong->bitrate;
83	_pick_dlna_profile(psong, s.wfx.wFormatTag);
84
85	return 0;
86}
87
88static int
89_asf_read_media_stream(FILE *fp, struct song_metadata *psong, __u32 size)
90{
91	asf_media_stream_t s;
92	avi_audio_format_t wfx;
93	int len;
94
95	len = sizeof(s) - sizeof(s.Hdr);
96	if(len > size)
97		len = size;
98
99	if(len != fread(&s.MajorType, 1, len, fp))
100		return -1;
101
102	if(IsEqualGUID(&s.MajorType, &ASF_MediaTypeAudio) &&
103	   IsEqualGUID(&s.FormatType, &ASF_FormatTypeWave) && s.FormatSize >= sizeof(wfx))
104	{
105
106		if(sizeof(wfx) != fread(&wfx, 1, sizeof(wfx), fp))
107			return -1;
108
109		psong->channels = le16_to_cpu(wfx.nChannels);
110		psong->bitrate = le32_to_cpu(wfx.nAvgBytesPerSec) * 8;
111		psong->samplerate = le32_to_cpu(wfx.nSamplesPerSec);
112		if (!psong->max_bitrate)
113			psong->max_bitrate = psong->bitrate;
114		_pick_dlna_profile(psong, wfx.wFormatTag);
115	}
116
117	return 0;
118}
119
120static int
121_asf_read_stream_object(FILE *fp, struct song_metadata *psong, __u32 size)
122{
123	asf_stream_object_t s;
124	int len;
125
126	len = sizeof(s) - sizeof(asf_object_t);
127	if(size < len)
128		return -1;
129
130	if(len != fread(&s.StreamType, 1, len, fp))
131		return -1;
132
133	if(IsEqualGUID(&s.StreamType, &ASF_AudioStream))
134		_asf_read_audio_stream(fp, psong, s.TypeSpecificSize);
135	else if(IsEqualGUID(&s.StreamType, &ASF_StreamBufferStream))
136		_asf_read_media_stream(fp, psong, s.TypeSpecificSize);
137	else if(!IsEqualGUID(&s.StreamType, &ASF_VideoStream))
138	{
139		DPRINTF(E_ERROR, L_SCANNER, "Unknown asf stream type.\n");
140	}
141
142	return 0;
143}
144
145static int
146_asf_read_extended_stream_object(FILE *fp, struct song_metadata *psong, __u32 size)
147{
148	int i, len;
149	long off;
150	asf_object_t tmp;
151	asf_extended_stream_object_t xs;
152	asf_stream_name_t nm;
153	asf_payload_extension_t pe;
154
155	if(size < sizeof(asf_extended_stream_object_t))
156		return -1;
157
158	len = sizeof(xs) - offsetof(asf_extended_stream_object_t, StartTime);
159	if(len != fread(&xs.StartTime, 1, len, fp))
160		return -1;
161	off = sizeof(xs);
162
163	for(i = 0; i < xs.StreamNameCount; i++)
164	{
165		if(off + sizeof(nm) > size)
166			return -1;
167		if(sizeof(nm) != fread(&nm, 1, sizeof(nm), fp))
168			return -1;
169		off += sizeof(nm);
170		if(off + nm.Length > sizeof(asf_extended_stream_object_t))
171			return -1;
172		if(nm.Length > 0)
173			fseek(fp, nm.Length, SEEK_CUR);
174		off += nm.Length;
175	}
176
177	for(i = 0; i < xs.PayloadExtensionSystemCount; i++)
178	{
179		if(off + sizeof(pe) > size)
180			return -1;
181		if(sizeof(pe) != fread(&pe, 1, sizeof(pe), fp))
182			return -1;
183		off += sizeof(pe);
184		if(pe.InfoLength > 0)
185			fseek(fp, pe.InfoLength, SEEK_CUR);
186		off += pe.InfoLength;
187	}
188
189	if(off < size)
190	{
191		if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
192			return -1;
193		if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader))
194			_asf_read_stream_object(fp, psong, tmp.Size);
195	}
196
197	return 0;
198}
199
200static int
201_asf_read_header_extension(FILE *fp, struct song_metadata *psong, __u32 size)
202{
203	off_t pos;
204	long off;
205	asf_header_extension_t ext;
206	asf_object_t tmp;
207
208	if(size < sizeof(asf_header_extension_t))
209		return -1;
210
211	fread(&ext.Reserved1, 1, sizeof(ext.Reserved1), fp);
212	ext.Reserved2 = fget_le16(fp);
213	ext.DataSize = fget_le32(fp);
214
215	pos = ftell(fp);
216	off = 0;
217	while(off < ext.DataSize)
218	{
219		if(sizeof(asf_header_extension_t) + off > size)
220			break;
221		if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
222			break;
223		if(off + tmp.Size > ext.DataSize)
224			break;
225		if(IsEqualGUID(&tmp.ID, &ASF_ExtendedStreamPropertiesObject))
226			_asf_read_extended_stream_object(fp, psong, tmp.Size);
227
228		off += tmp.Size;
229		fseek(fp, pos + off, SEEK_SET);
230	}
231
232	return 0;
233}
234
235static int
236_asf_load_string(FILE *fp, int type, int size, char *buf, int len)
237{
238	unsigned char data[2048];
239	__u16 wc;
240	int i, j;
241	__s32 *wd32;
242	__s64 *wd64;
243	__s16 *wd16;
244
245	i = 0;
246	if(size && (size <= sizeof(data)) && (size == fread(data, 1, size, fp)))
247	{
248
249		switch(type)
250		{
251		case ASF_VT_UNICODE:
252			for(j = 0; j < size; j += 2)
253			{
254				wc = *(__s16*)&data[j];
255				i += utf16le_to_utf8(&buf[i], len - i, wc);
256			}
257			break;
258		case ASF_VT_BYTEARRAY:
259			for(i = 0; i < size; i++)
260			{
261				if(i + 1 >= len)
262					break;
263				buf[i] = data[i];
264			}
265			break;
266		case ASF_VT_BOOL:
267		case ASF_VT_DWORD:
268			if(size >= 4)
269			{
270				wd32 = (__s32 *) &data[0];
271				i = snprintf(buf, len, "%d", le32_to_cpu(*wd32));
272			}
273			break;
274		case ASF_VT_QWORD:
275			if(size >= 8)
276			{
277				wd64 = (__s64 *) &data[0];
278#if __WORDSIZE == 64
279				i = snprintf(buf, len, "%ld", le64_to_cpu(*wd64));
280#else
281				i = snprintf(buf, len, "%lld", le64_to_cpu(*wd64));
282#endif
283			}
284			break;
285		case ASF_VT_WORD:
286			if(size >= 2)
287			{
288				wd16 = (__s16 *) &data[0];
289				i = snprintf(buf, len, "%d", le16_to_cpu(*wd16));
290			}
291			break;
292		}
293
294		size = 0;
295	}
296	else fseek(fp, size, SEEK_CUR);
297
298	buf[i] = 0;
299	return i;
300}
301
302static void *
303_asf_load_picture(FILE *fp, int size, void *bm, int *bm_size)
304{
305	int i;
306	char buf[256];
307#if 0
308	//
309	// Picture type       $xx
310	// Data length	  $xx $xx $xx $xx
311	// MIME type          <text string> $00
312	// Description        <text string> $00
313	// Picture data       <binary data>
314
315	char pic_type;
316	long pic_size;
317
318	pic_type = fget_byte(fp); size -= 1;
319	pic_size = fget_le32(fp); size -= 4;
320#else
321	fseek(fp, 5, SEEK_CUR);
322	size -= 5;
323#endif
324	for(i = 0; i < sizeof(buf) - 1; i++)
325	{
326		buf[i] = fget_le16(fp); size -= 2;
327		if(!buf[i])
328			break;
329	}
330	buf[i] = '\0';
331	if(i == sizeof(buf) - 1)
332	{
333		while(fget_le16(fp))
334			size -= 2;
335	}
336
337	if(!strcasecmp(buf, "image/jpeg") ||
338	   !strcasecmp(buf, "image/jpg") ||
339	   !strcasecmp(buf, "image/peg"))
340	{
341
342		while(0 != fget_le16(fp))
343			size -= 2;
344
345		if(size > 0)
346		{
347			if(!(bm = malloc(size)))
348			{
349				DPRINTF(E_ERROR, L_SCANNER, "Couldn't allocate %d bytes\n", size);
350			}
351			else
352			{
353				*bm_size = size;
354				if(size <= *bm_size)
355				{
356					fread(bm, 1, size, fp);
357				}
358				else
359				{
360					DPRINTF(E_ERROR, L_SCANNER, "Overrun %d bytes required\n", size);
361					free(bm);
362					bm = NULL;
363				}
364			}
365		}
366		else
367		{
368			DPRINTF(E_ERROR, L_SCANNER, "No binary data\n");
369			size = 0;
370			bm = NULL;
371		}
372	}
373	else
374	{
375		DPRINTF(E_ERROR, L_SCANNER, "Invalid mime type %s\n", buf);
376	}
377
378	*bm_size = size;
379	return bm;
380}
381
382static int
383_get_asffileinfo(char *file, struct song_metadata *psong)
384{
385	FILE *fp;
386	asf_object_t hdr;
387	asf_object_t tmp;
388	unsigned long NumObjects;
389	unsigned short TitleLength;
390	unsigned short AuthorLength;
391	unsigned short CopyrightLength;
392	unsigned short DescriptionLength;
393	unsigned short RatingLength;
394	unsigned short NumEntries;
395	unsigned short NameLength;
396	unsigned short ValueType;
397	unsigned short ValueLength;
398	off_t pos;
399	char buf[2048];
400	asf_file_properties_t FileProperties;
401
402	psong->vbr_scale = -1;
403
404	if(!(fp = fopen(file, "rb")))
405	{
406		DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
407		return -1;
408	}
409
410	if(sizeof(hdr) != fread(&hdr, 1, sizeof(hdr), fp))
411	{
412		DPRINTF(E_ERROR, L_SCANNER, "Error reading %s\n", file);
413		fclose(fp);
414		return -1;
415	}
416	hdr.Size = le64_to_cpu(hdr.Size);
417
418	if(!IsEqualGUID(&hdr.ID, &ASF_HeaderObject))
419	{
420		DPRINTF(E_ERROR, L_SCANNER, "Not a valid header\n");
421		fclose(fp);
422		return -1;
423	}
424	NumObjects = fget_le32(fp);
425	fseek(fp, 2, SEEK_CUR); // Reserved le16
426
427	pos = ftell(fp);
428	while(NumObjects > 0)
429	{
430		if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
431			break;
432		tmp.Size = le64_to_cpu(tmp.Size);
433
434		if(pos + tmp.Size > hdr.Size)
435		{
436			DPRINTF(E_ERROR, L_SCANNER, "Size overrun reading header object %I64x\n", tmp.Size);
437			break;
438		}
439
440		if(IsEqualGUID(&tmp.ID, &ASF_FileProperties))
441		{
442			_asf_read_file_properties(fp, &FileProperties, tmp.Size);
443			psong->song_length = le64_to_cpu(FileProperties.PlayDuration) / 10000;
444			psong->bitrate = le64_to_cpu(FileProperties.MaxBitrate);
445			psong->max_bitrate = psong->bitrate;
446		}
447		else if(IsEqualGUID(&tmp.ID, &ASF_ContentDescription))
448		{
449			TitleLength = fget_le16(fp);
450			AuthorLength = fget_le16(fp);
451			CopyrightLength = fget_le16(fp);
452			DescriptionLength = fget_le16(fp);
453			RatingLength = fget_le16(fp);
454
455			if(_asf_load_string(fp, ASF_VT_UNICODE, TitleLength, buf, sizeof(buf)))
456			{
457				if(buf[0])
458					psong->title = strdup(buf);
459			}
460			if(_asf_load_string(fp, ASF_VT_UNICODE, AuthorLength, buf, sizeof(buf)))
461			{
462				if(buf[0])
463					psong->contributor[ROLE_TRACKARTIST] = strdup(buf);
464			}
465			if(CopyrightLength)
466				fseek(fp, CopyrightLength, SEEK_CUR);
467			if(DescriptionLength)
468				fseek(fp, DescriptionLength, SEEK_CUR);
469			if(RatingLength)
470				fseek(fp, RatingLength, SEEK_CUR);
471		}
472		else if(IsEqualGUID(&tmp.ID, &ASF_ExtendedContentDescription))
473		{
474			NumEntries = fget_le16(fp);
475			while(NumEntries > 0)
476			{
477				NameLength = fget_le16(fp);
478				_asf_load_string(fp, ASF_VT_UNICODE, NameLength, buf, sizeof(buf));
479				ValueType = fget_le16(fp);
480				ValueLength = fget_le16(fp);
481
482				if(!strcasecmp(buf, "AlbumTitle") || !strcasecmp(buf, "WM/AlbumTitle"))
483				{
484					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
485						if(buf[0])
486							psong->album = strdup(buf);
487				}
488				else if(!strcasecmp(buf, "AlbumArtist") || !strcasecmp(buf, "WM/AlbumArtist"))
489				{
490					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
491					{
492						if(buf[0])
493							psong->contributor[ROLE_ALBUMARTIST] = strdup(buf);
494					}
495				}
496				else if(!strcasecmp(buf, "Description") || !strcasecmp(buf, "WM/Track"))
497				{
498					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
499						if(buf[0])
500							psong->track = atoi(buf);
501				}
502				else if(!strcasecmp(buf, "Genre") || !strcasecmp(buf, "WM/Genre"))
503				{
504					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
505						if(buf[0])
506							psong->genre = strdup(buf);
507				}
508				else if(!strcasecmp(buf, "Year") || !strcasecmp(buf, "WM/Year"))
509				{
510					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
511						if(buf[0])
512							psong->year = atoi(buf);
513				}
514				else if(!strcasecmp(buf, "WM/Director"))
515				{
516					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
517						if(buf[0])
518							psong->contributor[ROLE_CONDUCTOR] = strdup(buf);
519				}
520				else if(!strcasecmp(buf, "WM/Composer"))
521				{
522					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
523						if(buf[0])
524							psong->contributor[ROLE_COMPOSER] = strdup(buf);
525				}
526				else if(!strcasecmp(buf, "WM/Picture") && (ValueType == ASF_VT_BYTEARRAY))
527				{
528					psong->image = _asf_load_picture(fp, ValueLength, psong->image, &psong->image_size);
529				}
530				else if(!strcasecmp(buf, "TrackNumber") || !strcasecmp(buf, "WM/TrackNumber"))
531				{
532					if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
533						if(buf[0])
534							psong->track = atoi(buf);
535				}
536				else if(!strcasecmp(buf, "isVBR"))
537				{
538					fseek(fp, ValueLength, SEEK_CUR);
539					psong->vbr_scale = 0;
540				}
541				else if(ValueLength)
542				{
543					fseek(fp, ValueLength, SEEK_CUR);
544				}
545				NumEntries--;
546			}
547		}
548		else if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader))
549		{
550			_asf_read_stream_object(fp, psong, tmp.Size);
551		}
552		else if(IsEqualGUID(&tmp.ID, &ASF_HeaderExtension))
553		{
554			_asf_read_header_extension(fp, psong, tmp.Size);
555		}
556		pos += tmp.Size;
557		fseek(fp, pos, SEEK_SET);
558		NumObjects--;
559	}
560
561#if 0
562	if(sizeof(hdr) == fread(&hdr, 1, sizeof(hdr), fp) && IsEqualGUID(&hdr.ID, &ASF_DataObject))
563	{
564		if(psong->song_length)
565		{
566			psong->bitrate = (hdr.Size * 8000) / psong->song_length;
567		}
568	}
569#endif
570
571	fclose(fp);
572	return 0;
573}
574