1/*
2 * WMA metatag parsing
3 *
4 * Copyright (C) 2005 Ron Pedde (ron@pedde.com)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21#ifdef HAVE_CONFIG_H
22# include <config.h>
23#endif
24
25#include <errno.h>
26#include <fcntl.h>
27#ifdef HAVE_STDINT_H
28#include <stdint.h>
29#endif
30#include <inttypes.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <sys/types.h>
35#include <unistd.h>
36
37#include "logger.h"
38#include "db.h"
39
40#define TRUE   ((1 == 1))
41#define FALSE  (!TRUE)
42
43typedef struct media_file_info MP3FILE;
44
45typedef struct tag_wma_guidlist {
46    char *name;
47    char *guid;
48    char value[16];
49} WMA_GUID;
50
51WMA_GUID wma_guidlist[] = {
52    { "ASF_Index_Object",
53      "D6E229D3-35DA-11D1-9034-00A0C90349BE",
54      "\xD3\x29\xE2\xD6\xDA\x35\xD1\x11\x90\x34\x00\xA0\xC9\x03\x49\xBE" },
55    { "ASF_Extended_Stream_Properties_Object",
56      "14E6A5CB-C672-4332-8399-A96952065B5A",
57      "\xCB\xA5\xE6\x14\x72\xC6\x32\x43\x83\x99\xA9\x69\x52\x06\x5B\x5A" },
58    { "ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio",
59      "1B1EE554-F9EA-4BC8-821A-376B74E4C4B8",
60      "\x54\xE5\x1E\x1B\xEA\xF9\xC8\x4B\x82\x1A\x37\x6B\x74\xE4\xC4\xB8" },
61    { "ASF_Bandwidth_Sharing_Object",
62      "A69609E6-517B-11D2-B6AF-00C04FD908E9",
63      "\xE6\x09\x96\xA6\x7B\x51\xD2\x11\xB6\xAF\x00\xC0\x4F\xD9\x08\xE9" },
64    { "ASF_Payload_Extension_System_Timecode",
65      "399595EC-8667-4E2D-8FDB-98814CE76C1E",
66      "\xEC\x95\x95\x39\x67\x86\x2D\x4E\x8F\xDB\x98\x81\x4C\xE7\x6C\x1E" },
67    { "ASF_Marker_Object",
68      "F487CD01-A951-11CF-8EE6-00C00C205365",
69      "\x01\xCD\x87\xF4\x51\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65" },
70    { "ASF_Data_Object",
71      "75B22636-668E-11CF-A6D9-00AA0062CE6C",
72      "\x36\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C" },
73    { "ASF_Content_Description_Object",
74      "75B22633-668E-11CF-A6D9-00AA0062CE6C",
75      "\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C" },
76    { "ASF_Reserved_1",
77      "ABD3D211-A9BA-11cf-8EE6-00C00C205365",
78      "\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65" },
79    { "ASF_Timecode_Index_Object",
80      "3CB73FD0-0C4A-4803-953D-EDF7B6228F0C",
81      "\xD0\x3F\xB7\x3C\x4A\x0C\x03\x48\x95\x3D\xED\xF7\xB6\x22\x8F\x0C" },
82    { "ASF_Language_List_Object",
83      "7C4346A9-EFE0-4BFC-B229-393EDE415C85",
84      "\xA9\x46\x43\x7C\xE0\xEF\xFC\x4B\xB2\x29\x39\x3E\xDE\x41\x5C\x85" },
85    { "ASF_No_Error_Correction",
86      "20FB5700-5B55-11CF-A8FD-00805F5C442B",
87      "\x00\x57\xFB\x20\x55\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B" },
88    { "ASF_Extended_Content_Description_Object",
89      "D2D0A440-E307-11D2-97F0-00A0C95EA850",
90      "\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50" },
91    { "ASF_Media_Object_Index_Parameters_Obj",
92      "6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7",
93      "\xAD\x3B\x20\x6B\x11\x3F\x84\x4E\xAC\xA8\xD7\x61\x3D\xE2\xCF\xA7" },
94    { "ASF_Codec_List_Object",
95      "86D15240-311D-11D0-A3A4-00A0C90348F6",
96      "\x40\x52\xD1\x86\x1D\x31\xD0\x11\xA3\xA4\x00\xA0\xC9\x03\x48\xF6" },
97    { "ASF_Stream_Bitrate_Properties_Object",
98      "7BF875CE-468D-11D1-8D82-006097C9A2B2",
99      "\xCE\x75\xF8\x7B\x8D\x46\xD1\x11\x8D\x82\x00\x60\x97\xC9\xA2\xB2" },
100    { "ASF_Script_Command_Object",
101      "1EFB1A30-0B62-11D0-A39B-00A0C90348F6",
102      "\x30\x1A\xFB\x1E\x62\x0B\xD0\x11\xA3\x9B\x00\xA0\xC9\x03\x48\xF6" },
103    { "ASF_Degradable_JPEG_Media",
104      "35907DE0-E415-11CF-A917-00805F5C442B",
105      "\xE0\x7D\x90\x35\x15\xE4\xCF\x11\xA9\x17\x00\x80\x5F\x5C\x44\x2B" },
106    { "ASF_Header_Object",
107      "75B22630-668E-11CF-A6D9-00AA0062CE6C",
108      "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C" },
109    { "ASF_Padding_Object",
110      "1806D474-CADF-4509-A4BA-9AABCB96AAE8",
111      "\x74\xD4\x06\x18\xDF\xCA\x09\x45\xA4\xBA\x9A\xAB\xCB\x96\xAA\xE8" },
112    { "ASF_JFIF_Media",
113      "B61BE100-5B4E-11CF-A8FD-00805F5C442B",
114      "\x00\xE1\x1B\xB6\x4E\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B" },
115    { "ASF_Digital_Signature_Object",
116      "2211B3FC-BD23-11D2-B4B7-00A0C955FC6E",
117      "\xFC\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E" },
118    { "ASF_Metadata_Library_Object",
119      "44231C94-9498-49D1-A141-1D134E457054",
120      "\x94\x1C\x23\x44\x98\x94\xD1\x49\xA1\x41\x1D\x13\x4E\x45\x70\x54" },
121    { "ASF_Payload_Ext_System_File_Name",
122      "E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B",
123      "\x0E\xEC\x65\xE1\xED\x19\xD7\x45\xB4\xA7\x25\xCB\xD1\xE2\x8E\x9B" },
124    { "ASF_Stream_Prioritization_Object",
125      "D4FED15B-88D3-454F-81F0-ED5C45999E24",
126      "\x5B\xD1\xFE\xD4\xD3\x88\x4F\x45\x81\xF0\xED\x5C\x45\x99\x9E\x24" },
127    { "ASF_Bandwidth_Sharing_Exclusive",
128      "AF6060AA-5197-11D2-B6AF-00C04FD908E9",
129      "\xAA\x60\x60\xAF\x97\x51\xD2\x11\xB6\xAF\x00\xC0\x4F\xD9\x08\xE9" },
130    { "ASF_Group_Mutual_Exclusion_Object",
131      "D1465A40-5A79-4338-B71B-E36B8FD6C249",
132      "\x40\x5A\x46\xD1\x79\x5A\x38\x43\xB7\x1B\xE3\x6B\x8F\xD6\xC2\x49" },
133    { "ASF_Audio_Spread",
134      "BFC3CD50-618F-11CF-8BB2-00AA00B4E220",
135      "\x50\xCD\xC3\xBF\x8F\x61\xCF\x11\x8B\xB2\x00\xAA\x00\xB4\xE2\x20" },
136    { "ASF_Advanced_Mutual_Exclusion_Object",
137      "A08649CF-4775-4670-8A16-6E35357566CD",
138      "\xCF\x49\x86\xA0\x75\x47\x70\x46\x8A\x16\x6E\x35\x35\x75\x66\xCD" },
139    { "ASF_Payload_Ext_Syst_Sample_Duration",
140      "C6BD9450-867F-4907-83A3-C77921B733AD",
141      "\x50\x94\xBD\xC6\x7F\x86\x07\x49\x83\xA3\xC7\x79\x21\xB7\x33\xAD" },
142    { "ASF_Stream_Properties_Object",
143      "B7DC0791-A9B7-11CF-8EE6-00C00C205365",
144      "\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65" },
145    { "ASF_Metadata_Object",
146      "C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA",
147      "\xEA\xCB\xF8\xC5\xAF\x5B\x77\x48\x84\x67\xAA\x8C\x44\xFA\x4C\xCA" },
148    { "ASF_Mutex_Unknown",
149      "D6E22A02-35DA-11D1-9034-00A0C90349BE",
150      "\x02\x2A\xE2\xD6\xDA\x35\xD1\x11\x90\x34\x00\xA0\xC9\x03\x49\xBE" },
151    { "ASF_Content_Branding_Object",
152      "2211B3FA-BD23-11D2-B4B7-00A0C955FC6E",
153      "\xFA\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E" },
154    { "ASF_Content_Encryption_Object",
155      "2211B3FB-BD23-11D2-B4B7-00A0C955FC6E",
156      "\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E" },
157    { "ASF_Index_Parameters_Object",
158      "D6E229DF-35DA-11D1-9034-00A0C90349BE",
159      "\xDF\x29\xE2\xD6\xDA\x35\xD1\x11\x90\x34\x00\xA0\xC9\x03\x49\xBE" },
160    { "ASF_Payload_Ext_System_Content_Type",
161      "D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC",
162      "\x20\xDC\x90\xD5\xBC\x07\x6C\x43\x9C\xF7\xF3\xBB\xFB\xF1\xA4\xDC" },
163    { "ASF_Web_Stream_Media_Subtype",
164      "776257D4-C627-41CB-8F81-7AC7FF1C40CC",
165      "\xD4\x57\x62\x77\x27\xC6\xCB\x41\x8F\x81\x7A\xC7\xFF\x1C\x40\xCC" },
166    { "ASF_Web_Stream_Format",
167      "DA1E6B13-8359-4050-B398-388E965BF00C",
168      "\x13\x6B\x1E\xDA\x59\x83\x50\x40\xB3\x98\x38\x8E\x96\x5B\xF0\x0C" },
169    { "ASF_Simple_Index_Object",
170      "33000890-E5B1-11CF-89F4-00A0C90349CB",
171      "\x90\x08\x00\x33\xB1\xE5\xCF\x11\x89\xF4\x00\xA0\xC9\x03\x49\xCB" },
172    { "ASF_Error_Correction_Object",
173      "75B22635-668E-11CF-A6D9-00AA0062CE6C",
174      "\x35\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C" },
175    { "ASF_Media_Object_Index_Object",
176      "FEB103F8-12AD-4C64-840F-2A1D2F7AD48C",
177      "\xF8\x03\xB1\xFE\xAD\x12\x64\x4C\x84\x0F\x2A\x1D\x2F\x7A\xD4\x8C" },
178    { "ASF_Mutex_Language",
179      "D6E22A00-35DA-11D1-9034-00A0C90349BE",
180      "\x00\x2A\xE2\xD6\xDA\x35\xD1\x11\x90\x34\x00\xA0\xC9\x03\x49\xBE" },
181    { "ASF_File_Transfer_Media",
182      "91BD222C-F21C-497A-8B6D-5AA86BFC0185",
183      "\x2C\x22\xBD\x91\x1C\xF2\x7A\x49\x8B\x6D\x5A\xA8\x6B\xFC\x01\x85" },
184    { "ASF_Reserved_3",
185      "4B1ACBE3-100B-11D0-A39B-00A0C90348F6",
186      "\xE3\xCB\x1A\x4B\x0B\x10\xD0\x11\xA3\x9B\x00\xA0\xC9\x03\x48\xF6" },
187    { "ASF_Bitrate_Mutual_Exclusion_Object",
188      "D6E229DC-35DA-11D1-9034-00A0C90349BE",
189      "\xDC\x29\xE2\xD6\xDA\x35\xD1\x11\x90\x34\x00\xA0\xC9\x03\x49\xBE" },
190    { "ASF_Bandwidth_Sharing_Partial",
191      "AF6060AB-5197-11D2-B6AF-00C04FD908E9",
192      "\xAB\x60\x60\xAF\x97\x51\xD2\x11\xB6\xAF\x00\xC0\x4F\xD9\x08\xE9" },
193    { "ASF_Command_Media",
194      "59DACFC0-59E6-11D0-A3AC-00A0C90348F6",
195      "\xC0\xCF\xDA\x59\xE6\x59\xD0\x11\xA3\xAC\x00\xA0\xC9\x03\x48\xF6" },
196    { "ASF_Audio_Media",
197      "F8699E40-5B4D-11CF-A8FD-00805F5C442B",
198      "\x40\x9E\x69\xF8\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B" },
199    { "ASF_Reserved_2",
200      "86D15241-311D-11D0-A3A4-00A0C90348F6",
201      "\x41\x52\xD1\x86\x1D\x31\xD0\x11\xA3\xA4\x00\xA0\xC9\x03\x48\xF6" },
202    { "ASF_Binary_Media",
203      "3AFB65E2-47EF-40F2-AC2C-70A90D71D343",
204      "\xE2\x65\xFB\x3A\xEF\x47\xF2\x40\xAC\x2C\x70\xA9\x0D\x71\xD3\x43" },
205    { "ASF_Mutex_Bitrate",
206      "D6E22A01-35DA-11D1-9034-00A0C90349BE",
207      "\x01\x2A\xE2\xD6\xDA\x35\xD1\x11\x90\x34\x00\xA0\xC9\x03\x49\xBE" },
208    { "ASF_Reserved_4",
209      "4CFEDB20-75F6-11CF-9C0F-00A0C90349CB",
210      "\x20\xDB\xFE\x4C\xF6\x75\xCF\x11\x9C\x0F\x00\xA0\xC9\x03\x49\xCB" },
211    { "ASF_Alt_Extended_Content_Encryption_Obj",
212      "FF889EF1-ADEE-40DA-9E71-98704BB928CE",
213      "\xF1\x9E\x88\xFF\xEE\xAD\xDA\x40\x9E\x71\x98\x70\x4B\xB9\x28\xCE" },
214    { "ASF_Timecode_Index_Parameters_Object",
215      "F55E496D-9797-4B5D-8C8B-604DFE9BFB24",
216      "\x6D\x49\x5E\xF5\x97\x97\x5D\x4B\x8C\x8B\x60\x4D\xFE\x9B\xFB\x24" },
217    { "ASF_Header_Extension_Object",
218      "5FBF03B5-A92E-11CF-8EE3-00C00C205365",
219      "\xB5\x03\xBF\x5F\x2E\xA9\xCF\x11\x8E\xE3\x00\xC0\x0C\x20\x53\x65" },
220    { "ASF_Video_Media",
221      "BC19EFC0-5B4D-11CF-A8FD-00805F5C442B",
222      "\xC0\xEF\x19\xBC\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B" },
223    { "ASF_Extended_Content_Encryption_Object",
224      "298AE614-2622-4C17-B935-DAE07EE9289C",
225      "\x14\xE6\x8A\x29\x22\x26\x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C" },
226    { "ASF_File_Properties_Object",
227      "8CABDCA1-A947-11CF-8EE4-00C00C205365",
228      "\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65" },
229    { NULL, NULL, "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" }
230};
231
232#define MAYBEFREE(x) { free((x)); }
233
234typedef struct tag_wma_header {
235    unsigned char objectid[16];
236    unsigned long long size;
237    unsigned int objects;
238    char reserved1;
239    char reserved2;
240} __attribute__((packed)) WMA_HEADER;
241
242typedef struct tag_wma_subheader {
243    unsigned char objectid[16];
244    long long size;
245} __attribute__((packed)) WMA_SUBHEADER;
246
247typedef struct tag_wma_stream_properties {
248    unsigned char stream_type[16];
249    unsigned char codec_type[16];
250    char time_offset[8];
251    unsigned int tsdl;
252    unsigned int ecdl;
253    unsigned short int flags;
254    unsigned int reserved;
255} __attribute__((packed)) WMA_STREAM_PROP;
256
257typedef struct tag_wma_header_extension {
258    unsigned char reserved_1[16];
259    unsigned short int reserved_2;
260    unsigned int data_size;
261} __attribute__((packed)) WMA_HEADER_EXT;
262
263/*
264 * Forwards
265 */
266WMA_GUID *wma_find_guid(unsigned char *guid);
267unsigned short int wma_convert_short(unsigned char *src);
268unsigned int wma_convert_int(unsigned char *src);
269unsigned long long wma_convert_ll(unsigned char *src);
270char *wma_utf16toutf8(unsigned char *utf16, int len);
271int wma_parse_content_description(int fd,int size, MP3FILE *pmp3);
272int wma_parse_extended_content_description(int fd,int size, MP3FILE *pmp3, int extended);
273int wma_parse_file_properties(int fd,int size, MP3FILE *pmp3);
274int wma_parse_audio_media(int fd, int size, MP3FILE *pmp3);
275int wma_parse_stream_properties(int fd, int size, MP3FILE *pmp3);
276int wma_parse_header_extension(int fd, int size, MP3FILE *pmp3);
277
278/**
279 * read an unsigned short int from the fd
280 */
281int wma_file_read_short(int fd, unsigned short int *psi) {
282    uint32_t len;
283    int ret;
284
285    len = sizeof(unsigned short int);
286    ret = read(fd, psi, len);
287    if ((ret < 0) || (ret != len)) {
288        return 0;
289    }
290
291    *psi = wma_convert_short((unsigned char *)psi);
292    return 1;
293}
294
295/**
296 * read an unsigned int from the fd
297 */
298int wma_file_read_int(int fd, unsigned int *pi) {
299    uint32_t len;
300    int ret;
301
302    len = sizeof(unsigned int);
303    ret = read(fd, pi, len);
304    if((ret < 0) || (ret != len)) {
305        return 0;
306    }
307
308    *pi = wma_convert_int((unsigned char *)pi);
309    return 1;
310}
311
312/**
313 * read an ll from the fd
314 */
315int wma_file_read_ll(int fd, unsigned long long *pll) {
316    uint32_t len;
317    int ret;
318
319    len = sizeof(unsigned long long);
320    ret = read(fd, pll, len);
321    if((ret < 0) || (ret != len)) {
322        return 0;
323    }
324
325    *pll = wma_convert_ll((unsigned char *)pll);
326    return 1;
327}
328
329/**
330 * read a utf-16le string as a utf8
331 */
332int wma_file_read_utf16(int fd, int len, char **utf8) {
333    char *out;
334    unsigned char *utf16;
335    int ret;
336
337    utf16=(unsigned char*)malloc(len);
338    if(!utf16)
339        return 0;
340
341    ret = read(fd, utf16, len);
342    if((ret < 0) || (ret != len))
343        return 0;
344
345    out = wma_utf16toutf8(utf16,len);
346    *utf8 = out;
347    free(utf16);
348
349    return 1;
350}
351
352int wma_file_read_bytes(int fd,int len, unsigned char **data) {
353    int ret;
354
355    *data = (unsigned char *)malloc(len);
356    if(!*data)
357        return 0;
358
359    ret = read(fd, *data, len);
360    if((ret < 0) || (ret != len))
361        return 0;
362
363    return 1;
364}
365
366int wma_parse_header_extension(int fd, int size, MP3FILE *pmp3) {
367    WMA_HEADER_EXT he;
368    WMA_SUBHEADER sh;
369    WMA_GUID *pguid;
370    int bytes_left; /* FIXME: uint32_t? */
371    uint64_t current;
372    uint32_t len;
373    int ret;
374
375    len = sizeof(he);
376    ret = read(fd, &he, len);
377    if((ret < 0) || (ret != len))
378        return FALSE;
379
380    he.data_size  = wma_convert_int((unsigned char *)&he.data_size);
381    bytes_left = he.data_size;
382    DPRINTF(E_DBG,L_SCAN,"Found header ext of %d (%d) bytes\n",he.data_size,size);
383
384    while(bytes_left) {
385        /* read in a subheader */
386        current = lseek(fd, 0, SEEK_CUR);
387
388        len = sizeof(sh);
389	ret = read(fd, &sh, len);
390        if((ret < 0) || (ret != len))
391            return FALSE;
392
393        sh.size = wma_convert_ll((unsigned char *)&sh.size);
394        pguid = wma_find_guid(sh.objectid);
395        if(!pguid) {
396            DPRINTF(E_DBG,L_SCAN,"  Unknown ext subheader: %02hhx%02hhx"
397                    "%02hhx%02hhx-"
398                    "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-"
399                    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx\n",
400                    sh.objectid[3],sh.objectid[2],
401                    sh.objectid[1],sh.objectid[0],
402                    sh.objectid[5],sh.objectid[4],
403                    sh.objectid[7],sh.objectid[6],
404                    sh.objectid[8],sh.objectid[9],
405                    sh.objectid[10],sh.objectid[11],
406                    sh.objectid[12],sh.objectid[13],
407                    sh.objectid[14],sh.objectid[15]);
408        } else {
409            DPRINTF(E_DBG,L_SCAN,"  Found ext subheader: %s\n", pguid->name);
410            if(strcmp(pguid->name,"ASF_Metadata_Library_Object")==0) {
411                if(!wma_parse_extended_content_description(fd,size,pmp3,1))
412                    return FALSE;
413            }
414        }
415
416        DPRINTF(E_DBG,L_SCAN,"  Size: %lld\n",sh.size);
417        if(sh.size <= sizeof(sh))
418            return TRUE; /* guess we're done! */
419
420        bytes_left -= (long)sh.size;
421        lseek(fd,current + (uint64_t)sh.size,SEEK_SET);
422    }
423
424    return TRUE;
425}
426
427
428/**
429 * another try to get stream codec type
430 *
431 * @param fd fd of the file we are reading from -- positioned at start
432 * @param size size of the content description block
433 * @param pmp3 the mp3 struct we are filling with gleaned data
434 */
435int wma_parse_stream_properties(int fd, int size, MP3FILE *pmp3) {
436    WMA_STREAM_PROP sp;
437    WMA_GUID *pguid;
438    uint32_t len;
439    int ret;
440
441    len = sizeof(sp);
442    ret = read(fd, &sp, len);
443    if((ret < 0) || (ret != len))
444        return FALSE;
445
446    pguid = wma_find_guid(sp.stream_type);
447    if(!pguid)
448        return TRUE;
449
450    if(strcmp(pguid->name,"ASF_Audio_Media") != 0)
451        return TRUE;
452
453    /* it is an audio stream... find codec.  The Type-Specific
454     * data should be a WAVEFORMATEX... so we'll leverage
455     * wma_parse_audio_media
456     */
457    return wma_parse_audio_media(fd,size - sizeof(WMA_STREAM_PROP),pmp3);
458}
459
460/**
461 * parse the audio media section... This is essentially a
462 * WAVFORMATEX structure.  Generally we only care about the
463 * codec type.
464 *
465 * @param fd fd of the file we are reading from -- positioned at start
466 * @param size size of the content description block
467 * @param pmp3 the mp3 struct we are filling with gleaned data
468 */
469int wma_parse_audio_media(int fd, int size, MP3FILE *pmp3) {
470    unsigned short int codec;
471
472    if(size < 18)
473        return TRUE; /* we'll leave it wma.  will work or not! */
474
475    if(!wma_file_read_short(fd,&codec)) {
476        return FALSE;
477    }
478
479    DPRINTF(E_DBG,L_SCAN,"WMA Codec Type: %02X\n",codec);
480
481    switch(codec) {
482    case 0x0A:
483        MAYBEFREE(pmp3->codectype);
484        pmp3->codectype = strdup("wmav"); /* voice */
485        break;
486    case 0x162:
487        MAYBEFREE(pmp3->codectype);
488        MAYBEFREE(pmp3->type);
489        pmp3->codectype = strdup("wma"); /* pro */
490        pmp3->type = strdup("wmap");
491        break;
492    case 0x163:
493        MAYBEFREE(pmp3->codectype);
494        pmp3->codectype = strdup("wmal"); /* lossless */
495        break;
496    }
497
498    /* might as well get the sample rate while we are at it */
499    lseek(fd,2,SEEK_CUR);
500    if(!wma_file_read_int(fd,(unsigned int *)&pmp3->samplerate))
501        return FALSE;
502
503    return TRUE;
504}
505
506/**
507 * parse the extended content description object.  this is an object that
508 * has ad-hoc tags, basically.
509 *
510 * @param fd fd of the file we are reading from -- positioned at start
511 * @param size size of the content description block
512 * @param pmp3 the mp3 struct we are filling with gleaned data
513 */
514int wma_parse_extended_content_description(int fd,int size, MP3FILE *pmp3, int extended) {
515    unsigned short descriptor_count;
516    int index;
517    unsigned short descriptor_name_len;
518    char *descriptor_name;
519    unsigned short descriptor_value_type;
520    unsigned int descriptor_value_int;
521    unsigned short descriptor_value_len;
522    unsigned short language_list_index;
523    unsigned short stream_number;
524
525    char *descriptor_byte_value=NULL;
526    unsigned int descriptor_int_value; /* bool and dword */
527    unsigned long long descriptor_ll_value;
528    unsigned short int descriptor_short_value;
529    int fail=0;
530    int track, tracknumber;
531    char numbuff[40];
532    char *tmp;
533    unsigned char *ptr;
534
535
536    track = tracknumber = 0;
537
538    DPRINTF(E_DBG,L_SCAN,"Reading extended content description object\n");
539
540    if(!wma_file_read_short(fd, &descriptor_count))
541        return FALSE;
542
543    for(index = 0; index < descriptor_count; index++) {
544        DPRINTF(E_DBG,L_SCAN,"Reading descr %d of %d\n",index,descriptor_count);
545        if(!extended) {
546            if(!wma_file_read_short(fd,&descriptor_name_len)) return FALSE;
547            if(!wma_file_read_utf16(fd,descriptor_name_len,&descriptor_name))
548                return FALSE;
549            if(!wma_file_read_short(fd,&descriptor_value_type)) {
550                free(descriptor_name);
551                return FALSE;
552            }
553            if(!wma_file_read_short(fd,&descriptor_value_len)) {
554                free(descriptor_name);
555                return FALSE;
556            }
557            descriptor_value_int = descriptor_value_len;
558        } else {
559            if(!wma_file_read_short(fd,&language_list_index)) return FALSE;
560            if(!wma_file_read_short(fd,&stream_number)) return FALSE;
561            if(!wma_file_read_short(fd,&descriptor_name_len)) return FALSE;
562            if(!wma_file_read_short(fd,&descriptor_value_type)) return FALSE;
563            if(!wma_file_read_int(fd,&descriptor_value_int)) return FALSE;
564            if(!wma_file_read_utf16(fd,descriptor_name_len,&descriptor_name))
565                return FALSE;
566        }
567
568        DPRINTF(E_DBG,L_SCAN,"Found descriptor: %s\n", descriptor_name);
569
570        /* see what kind it is */
571        switch(descriptor_value_type) {
572        case 0x0000: /* string */
573            if(!wma_file_read_utf16(fd,descriptor_value_int,
574                                    &descriptor_byte_value)) {
575                fail=1;
576            }
577            descriptor_int_value=atoi(descriptor_byte_value);
578            DPRINTF(E_DBG,L_SCAN,"Type: string, value: %s\n",descriptor_byte_value);
579            break;
580        case 0x0001: /* byte array */
581            if(descriptor_value_int > 4096) {
582                lseek(fd,(uint64_t)descriptor_value_int,SEEK_CUR);
583                descriptor_byte_value = NULL;
584            } else {
585                ptr = (unsigned char *)descriptor_byte_value;
586                if(!wma_file_read_bytes(fd,descriptor_value_int,
587                                        &ptr)){
588                    fail=1;
589                }
590            }
591            DPRINTF(E_DBG,L_SCAN,"Type: bytes\n");
592            break;
593        case 0x0002: /* bool - dropthru */
594        case 0x0003: /* dword */
595            if(!wma_file_read_int(fd,&descriptor_int_value)) fail=1;
596            DPRINTF(E_DBG,L_SCAN,"Type: int, value: %d\n",descriptor_int_value);
597            snprintf(numbuff,sizeof(numbuff)-1,"%d",descriptor_int_value);
598            descriptor_byte_value = strdup(numbuff);
599            break;
600        case 0x0004: /* qword */
601            if(!wma_file_read_ll(fd,&descriptor_ll_value)) fail=1;
602            DPRINTF(E_DBG,L_SCAN,"Type: ll, value: %lld\n",descriptor_ll_value);
603            snprintf(numbuff,sizeof(numbuff)-1,"%lld",descriptor_ll_value);
604            descriptor_byte_value = strdup(numbuff);
605            break;
606        case 0x0005: /* word */
607            if(!wma_file_read_short(fd,&descriptor_short_value)) fail=1;
608            DPRINTF(E_DBG,L_SCAN,"type: short, value %d\n",descriptor_short_value);
609            snprintf(numbuff,sizeof(numbuff)-1,"%d",descriptor_short_value);
610            descriptor_byte_value = strdup(numbuff);
611            break;
612        case 0x0006: /* guid */
613	    lseek(fd,16,SEEK_CUR); /* skip it */
614            if(descriptor_name)
615                free(descriptor_name);
616            descriptor_name = strdup("");
617            descriptor_byte_value = NULL;
618            break;
619        default:
620            DPRINTF(E_LOG,L_SCAN,"Badly formatted wma file\n");
621            if(descriptor_name)
622                free(descriptor_name);
623            if(descriptor_byte_value)
624                free(descriptor_byte_value);
625            return FALSE;
626        }
627
628        if(fail) {
629            DPRINTF(E_DBG,L_SCAN,"Read fail on file\n");
630            free(descriptor_name);
631            return FALSE;
632        }
633
634        /* do stuff with what we found */
635        if(strcasecmp(descriptor_name,"wm/genre")==0) {
636            MAYBEFREE(pmp3->genre);
637            pmp3->genre = descriptor_byte_value;
638            descriptor_byte_value = NULL; /* don't free it! */
639        } else if(strcasecmp(descriptor_name,"wm/albumtitle")==0) {
640            MAYBEFREE(pmp3->album);
641            pmp3->album = descriptor_byte_value;
642            descriptor_byte_value = NULL;
643        } else if(strcasecmp(descriptor_name,"wm/track")==0) {
644            track = descriptor_int_value + 1;
645        } else if(strcasecmp(descriptor_name,"wm/shareduserrating")==0) {
646            /* what a strange rating strategy */
647            pmp3->rating = descriptor_int_value;
648            if(pmp3->rating == 99) {
649                pmp3->rating = 100;
650            } else {
651                if(pmp3->rating) {
652                    pmp3->rating = ((pmp3->rating / 25) + 1) * 20;
653                }
654            }
655        } else if(strcasecmp(descriptor_name,"wm/tracknumber")==0) {
656            tracknumber = descriptor_int_value;
657        } else if(strcasecmp(descriptor_name,"wm/year")==0) {
658            pmp3->year = atoi(descriptor_byte_value);
659        } else if(strcasecmp(descriptor_name,"wm/composer")==0) {
660            /* get first one only */
661            if(!pmp3->composer) {
662                pmp3->composer = descriptor_byte_value;
663                descriptor_byte_value = NULL;
664            } else {
665                size = (int)strlen(pmp3->composer) + 1 +
666                    (int)strlen(descriptor_byte_value) + 1;
667                tmp = malloc(size);
668                if(!tmp)
669                    DPRINTF(E_FATAL,L_SCAN,"malloc: wma_ext_content_descr\n");
670                sprintf(tmp,"%s/%s",pmp3->composer,descriptor_byte_value);
671                free(pmp3->composer);
672                pmp3->composer=tmp;
673            }
674        } else if(strcasecmp(descriptor_name,"wm/albumartist")==0) {
675            /* get first one only */
676            if(!pmp3->album_artist) {
677                pmp3->album_artist = descriptor_byte_value;
678                descriptor_byte_value = NULL;
679            }
680        } else if(strcasecmp(descriptor_name,"author") == 0) {
681            /* get first one only */
682            if(!pmp3->artist) {
683                pmp3->artist = descriptor_byte_value;
684                descriptor_byte_value = NULL;
685            }
686        } else if(strcasecmp(descriptor_name,"wm/contengroupdescription")==0) {
687            MAYBEFREE(pmp3->grouping);
688            pmp3->grouping = descriptor_byte_value;
689            descriptor_byte_value = NULL;
690        } else if(strcasecmp(descriptor_name,"comment")==0) {
691            MAYBEFREE(pmp3->comment);
692            pmp3->comment = descriptor_byte_value;
693            descriptor_byte_value = NULL;
694        }
695
696        /* cleanup - done with this round */
697        if(descriptor_byte_value) {
698            free(descriptor_byte_value);
699            descriptor_byte_value = NULL;
700        }
701
702        free(descriptor_name);
703    }
704
705    if(tracknumber) {
706        pmp3->track = tracknumber;
707    } else if(track) {
708        pmp3->track = track;
709    }
710
711    if((!pmp3->artist) && (pmp3->orchestra)) {
712        pmp3->artist = strdup(pmp3->orchestra);
713    }
714
715    if((pmp3->artist) && (!pmp3->orchestra)) {
716        pmp3->orchestra = strdup(pmp3->artist);
717    }
718
719    return TRUE;
720}
721
722/**
723 * parse the content description object.  this is an object that
724 * contains lengths of title, author, copyright, descr, and rating
725 * then the utf-16le strings for each.
726 *
727 * @param fd fd of the file we are reading from -- positioned at start
728 * @param size size of the content description block
729 * @param pmp3 the mp3 struct we are filling with gleaned data
730 */
731int wma_parse_content_description(int fd,int size, MP3FILE *pmp3) {
732    unsigned short sizes[5];
733    int index;
734    char *utf8;
735
736    if(size < 10) /* must be at least enough room for the size block */
737        return FALSE;
738
739    for(index=0; index < 5; index++) {
740        if(!wma_file_read_short(fd,&sizes[index]))
741            return FALSE;
742    }
743
744    for(index=0;index<5;index++) {
745        if(sizes[index]) {
746            if(!wma_file_read_utf16(fd,sizes[index],&utf8))
747                return FALSE;
748
749            DPRINTF(E_DBG,L_SCAN,"Got item of length %d: %s\n",sizes[index],utf8);
750
751            switch(index) {
752            case 0: /* title */
753                if(pmp3->title)
754                    free(pmp3->title);
755                pmp3->title = utf8;
756                break;
757            case 1: /* author */
758                if(pmp3->artist)
759                    free(pmp3->artist);
760                pmp3->artist = utf8;
761                break;
762            case 2: /* copyright - dontcare */
763                free(utf8);
764            break;
765            case 3: /* description */
766                if(pmp3->comment)
767                    free(pmp3->comment);
768                pmp3->comment = utf8;
769                break;
770            case 4: /* rating - dontcare */
771                free(utf8);
772                break;
773            default: /* can't get here */
774                DPRINTF(E_FATAL,L_SCAN,"This is not my beautiful wife.\n");
775                break;
776            }
777        }
778    }
779
780    return TRUE;
781}
782
783/**
784 * parse the file properties object.  this is an object that
785 * contains playtime and bitrate, primarily.
786 *
787 * @param fd fd of the file we are reading from -- positioned at start
788 * @param size size of the content description block
789 * @param pmp3 the mp3 struct we are filling with gleaned data
790 */
791int wma_parse_file_properties(int fd,int size, MP3FILE *pmp3) {
792    unsigned long long play_duration;
793    unsigned long long send_duration;
794    unsigned long long preroll;
795
796    unsigned int max_bitrate;
797
798    /* skip guid (16 bytes), filesize (8), creation time (8),
799     * data packets (8)
800     */
801    lseek(fd,40,SEEK_CUR);
802
803    if(!wma_file_read_ll(fd, &play_duration))
804        return FALSE;
805
806    if(!wma_file_read_ll(fd, &send_duration))
807        return FALSE;
808
809    if(!wma_file_read_ll(fd, &preroll))
810        return FALSE;
811
812    DPRINTF(E_DBG,L_SCAN,"play_duration: %lld, "
813            "send_duration: %lld, preroll: %lld\n",
814            play_duration, send_duration, preroll);
815
816    /* I'm not entirely certain what preroll is, but it seems
817     * to make it match up with what windows thinks is the song
818     * length.
819     */
820    pmp3->song_length = (int)((play_duration / 10000) - preroll);
821
822    /* skip flags(4),
823     * min_packet_size (4), max_packet_size(4)
824     */
825
826    lseek(fd,12,SEEK_CUR);
827    if(!wma_file_read_int(fd,&max_bitrate))
828        return FALSE;
829
830    pmp3->bitrate = max_bitrate/1000;
831
832    return TRUE;
833}
834
835/**
836 * convert utf16 string to utf8.  This is a bit naive, but...
837 * Since utf-8 can't expand past 4 bytes per code point, and
838 * we're converting utf-16, we can't be more than 2n+1 bytes, so
839 * we'll just allocate that much.
840 *
841 * Probably it could be more efficiently calculated, but this will
842 * always work.  Besides, these are small strings, and will be freed
843 * after the db insert.
844 *
845 * We assume this is utf-16LE, as it comes from windows
846 *
847 * @param utf16 utf-16 to convert
848 * @param len length of utf-16 string
849 */
850char *wma_utf16toutf8(unsigned char *utf16, int len) {
851    char *utf8;
852    unsigned char *src=utf16;
853    char *dst;
854    unsigned int w1, w2;
855    int bytes;
856
857    if(!len)
858        return NULL;
859
860    utf8=(char *)malloc(len*2 + 1);
861    if(!utf8)
862        return NULL;
863
864    memset(utf8,0x0,len*2 + 1);
865    dst=utf8;
866
867    while((src+2) <= utf16+len) {
868        w1=src[1] << 8 | src[0];
869        src += 2;
870        if((w1 & 0xFC00) == 0xD800) { /* could be surrogate pair */
871            if(src+2 > utf16+len) {
872                DPRINTF(E_INFO,L_SCAN,"Invalid utf-16 in file\n");
873                free(utf8);
874                return NULL;
875            }
876            w2 = src[3] << 8 | src[2];
877            if((w2 & 0xFC00) != 0xDC00) {
878                DPRINTF(E_INFO,L_SCAN,"Invalid utf-16 in file\n");
879                free(utf8);
880                return NULL;
881            }
882
883            /* get bottom 10 of each */
884            w1 = w1 & 0x03FF;
885            w1 = w1 << 10;
886            w1 = w1 | (w2 & 0x03FF);
887
888            /* add back the 0x10000 */
889            w1 += 0x10000;
890        }
891
892        /* now encode the original code point in utf-8 */
893        if (w1 < 0x80) {
894            *dst++ = w1;
895            bytes=0;
896        } else if (w1 < 0x800) {
897            *dst++ = 0xC0 | (w1 >> 6);
898            bytes=1;
899        } else if (w1 < 0x10000) {
900            *dst++ = 0xE0 | (w1 >> 12);
901            bytes=2;
902        } else {
903            *dst++ = 0xF0 | (w1 >> 18);
904            bytes=3;
905        }
906
907        while(bytes) {
908            *dst++ = 0x80 | ((w1 >> (6*(bytes-1))) & 0x3f);
909            bytes--;
910        }
911    }
912
913    return utf8;
914}
915
916/**
917 * lookup a guid by character
918 *
919 * @param guid 16 byte guid to look up
920 */
921WMA_GUID *wma_find_guid(unsigned char *guid) {
922    WMA_GUID *pguid = wma_guidlist;
923
924    while((pguid->name) && (memcmp(guid,pguid->value,16) != 0)) {
925        pguid++;
926    }
927
928    if(!pguid->name)
929        return NULL;
930
931    return pguid;
932}
933
934/**
935 * convert a short int in wrong-endian format to host-endian
936 *
937 * @param src pointer to 16-bit wrong-endian int
938 */
939unsigned short wma_convert_short(unsigned char *src) {
940    return src[1] << 8 |
941        src[0];
942}
943
944/**
945 * convert an int in wrong-endian format to host-endian
946 *
947 * @param src pointer to 32-bit wrong-endian int
948 */
949unsigned int wma_convert_int(unsigned char *src) {
950    return src[3] << 24 |
951        src[2] << 16 |
952        src[1] << 8 |
953        src[0];
954}
955
956/**
957 * convert a long long wrong-endian format to host-endian
958 *
959 * @param src pointer to 64-bit wrong-endian int
960 */
961
962unsigned long long wma_convert_ll(unsigned char *src) {
963    unsigned int tmp_hi, tmp_lo;
964    unsigned long long retval;
965
966    tmp_hi = src[7] << 24 |
967        src[6] << 16 |
968        src[5] << 8 |
969        src[4];
970
971    tmp_lo = src[3] << 24 |
972        src[2] << 16 |
973        src[1] << 8 |
974        src[0];
975
976    retval = tmp_hi;
977    retval = (retval << 32) | tmp_lo;
978
979    return retval;
980}
981
982/**
983 * get metainfo about a wma file
984 *
985 * @param filename full path to file to scan
986 * @param pmp3 MP3FILE struct to be filled with with metainfo
987 */
988int scan_get_wmainfo(char *filename, MP3FILE *pmp3) {
989    WMA_HEADER hdr;
990    WMA_SUBHEADER subhdr;
991    WMA_GUID *pguid;
992    uint64_t offset=0;
993    uint32_t len;
994    int item;
995    int res=TRUE;
996    int encrypted = 0;
997    int fd;
998    int ret;
999
1000    fd = open(filename, O_RDONLY);
1001    if (fd < 0) {
1002        DPRINTF(E_INFO,L_SCAN,"Error opening WMA file (%s): %s\n",filename,
1003		strerror(errno));
1004        return FALSE;
1005    }
1006
1007    len = sizeof(hdr);
1008    ret = read(fd, &hdr, len);
1009    if((ret < 0) || (ret != len)) {
1010        DPRINTF(E_INFO,L_SCAN,"Error reading from %s: %s\n",filename,
1011            strerror(errno));
1012	close(fd);
1013        return FALSE;
1014    }
1015
1016    pguid = wma_find_guid(hdr.objectid);
1017    if(!pguid) {
1018        DPRINTF(E_INFO,L_SCAN,"Could not find header in %s\n",filename);
1019	close(fd);
1020        return FALSE;
1021    }
1022
1023    hdr.objects=wma_convert_int((unsigned char *)&hdr.objects);
1024    hdr.size=wma_convert_ll((unsigned char *)&hdr.size);
1025
1026    DPRINTF(E_DBG,L_SCAN,"Found WMA header: %s\n",pguid->name);
1027    DPRINTF(E_DBG,L_SCAN,"Header size:      %lld\n",hdr.size);
1028    DPRINTF(E_DBG,L_SCAN,"Header objects:   %d\n",hdr.objects);
1029
1030    offset = sizeof(hdr); //hdr.size;
1031
1032    /* Now we just walk through all the headers and see if we
1033     * find anything interesting
1034     */
1035
1036    for(item=0; item < (int) hdr.objects; item++) {
1037        if(!lseek(fd,offset,SEEK_SET)) {
1038            DPRINTF(E_INFO,L_SCAN,"Error seeking in %s\n",filename);
1039	    close(fd);
1040            return FALSE;
1041        }
1042
1043        len = sizeof(subhdr);
1044	ret = read(fd, &subhdr, len);
1045        if((ret < 0) || (ret != len)) {
1046            DPRINTF(E_INFO,L_SCAN,"Error reading from %s: %s\n",filename,
1047		    strerror(errno));
1048	    close(fd);
1049            return FALSE;
1050        }
1051
1052        subhdr.size=wma_convert_ll((unsigned char *)&subhdr.size);
1053
1054        pguid = wma_find_guid(subhdr.objectid);
1055        if(pguid) {
1056            DPRINTF(E_DBG,L_SCAN,"%" PRIu64 ": Found subheader: %s\n",
1057                    offset,pguid->name);
1058            if(strcmp(pguid->name,"ASF_Content_Description_Object")==0) {
1059                res &= wma_parse_content_description(fd,(int)subhdr.size,pmp3);
1060            } else if (strcmp(pguid->name,"ASF_Extended_Content_Description_Object")==0) {
1061                res &= wma_parse_extended_content_description(fd,(int)subhdr.size,pmp3,0);
1062            } else if (strcmp(pguid->name,"ASF_File_Properties_Object")==0) {
1063                res &= wma_parse_file_properties(fd,(int)subhdr.size,pmp3);
1064            } else if (strcmp(pguid->name,"ASF_Audio_Media")==0) {
1065                res &= wma_parse_audio_media(fd,(int)subhdr.size,pmp3);
1066            } else if (strcmp(pguid->name,"ASF_Stream_Properties_Object")==0) {
1067                res &= wma_parse_stream_properties(fd,(int)subhdr.size,pmp3);
1068            } else if(strcmp(pguid->name,"ASF_Header_Extension_Object")==0) {
1069                res &= wma_parse_header_extension(fd,(int)subhdr.size,pmp3);
1070            } else if(strstr(pguid->name,"Content_Encryption_Object")) {
1071                encrypted=1;
1072            }
1073        } else {
1074            DPRINTF(E_DBG,L_SCAN,"Unknown subheader: %02hhx%02hhx%02hhx%02hhx-"
1075            "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-"
1076            "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx\n",
1077            subhdr.objectid[3],subhdr.objectid[2],
1078            subhdr.objectid[1],subhdr.objectid[0],
1079            subhdr.objectid[5],subhdr.objectid[4],
1080            subhdr.objectid[7],subhdr.objectid[6],
1081            subhdr.objectid[8],subhdr.objectid[9],
1082            subhdr.objectid[10],subhdr.objectid[11],
1083            subhdr.objectid[12],subhdr.objectid[13],
1084            subhdr.objectid[14],subhdr.objectid[15]);
1085
1086        }
1087        offset += (uint64_t) subhdr.size;
1088    }
1089
1090
1091    if(!res) {
1092        DPRINTF(E_INFO,L_SCAN,"Error reading meta info for file %s\n",
1093            filename);
1094    } else {
1095        DPRINTF(E_DBG,L_SCAN,"Successfully parsed file\n");
1096    }
1097
1098    close(fd);
1099
1100    if(encrypted) {
1101        if(pmp3->codectype)
1102            free(pmp3->codectype);
1103
1104        pmp3->codectype=strdup("wmap");
1105    }
1106
1107    return res;
1108}
1109