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