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