1 | //========================================================================= |
---|
2 | // FILENAME : tagutils-wav.c |
---|
3 | // DESCRIPTION : WAV metadata reader |
---|
4 | //========================================================================= |
---|
5 | // Copyright (c) 2009 NETGEAR, Inc. All Rights Reserved. |
---|
6 | // based on software from Ron Pedde's FireFly Media Server project |
---|
7 | //========================================================================= |
---|
8 | |
---|
9 | /* |
---|
10 | * This program is free software; you can redistribute it and/or modify |
---|
11 | * it under the terms of the GNU General Public License as published by |
---|
12 | * the Free Software Foundation; either version 2 of the License, or |
---|
13 | * (at your option) any later version. |
---|
14 | * |
---|
15 | * This program is distributed in the hope that it will be useful, |
---|
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
18 | * GNU General Public License for more details. |
---|
19 | * |
---|
20 | * You should have received a copy of the GNU General Public License |
---|
21 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
---|
22 | */ |
---|
23 | |
---|
24 | #define GET_WAV_INT32(p) ((((uint32_t)((p)[3])) << 24) | \ |
---|
25 | (((uint32_t)((p)[2])) << 16) | \ |
---|
26 | (((uint32_t)((p)[1])) << 8) | \ |
---|
27 | (((uint32_t)((p)[0])))) |
---|
28 | |
---|
29 | #define GET_WAV_INT16(p) ((((uint32_t)((p)[1])) << 8) | \ |
---|
30 | (((uint32_t)((p)[0])))) |
---|
31 | |
---|
32 | static int |
---|
33 | _get_wavtags(char *filename, struct song_metadata *psong) |
---|
34 | { |
---|
35 | int fd; |
---|
36 | uint32_t len; |
---|
37 | unsigned char hdr[12]; |
---|
38 | unsigned char fmt[16]; |
---|
39 | //uint32_t chunk_data_length; |
---|
40 | uint32_t format_data_length = 0; |
---|
41 | uint32_t compression_code = 0; |
---|
42 | uint32_t channel_count = 0; |
---|
43 | uint32_t sample_rate = 0; |
---|
44 | uint32_t sample_bit_length = 0; |
---|
45 | uint32_t bit_rate; |
---|
46 | uint32_t data_length = 0; |
---|
47 | uint32_t sec, ms; |
---|
48 | |
---|
49 | uint32_t current_offset; |
---|
50 | uint32_t block_len; |
---|
51 | |
---|
52 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Getting WAV file info\n"); |
---|
53 | |
---|
54 | if(!(fd = open(filename, O_RDONLY))) |
---|
55 | { |
---|
56 | DPRINTF(E_WARN, L_SCANNER, "Could not create file handle\n"); |
---|
57 | return -1; |
---|
58 | } |
---|
59 | |
---|
60 | len = 12; |
---|
61 | if(!(len = read(fd, hdr, len)) || (len != 12)) |
---|
62 | { |
---|
63 | DPRINTF(E_WARN, L_SCANNER, "Could not read wav header from %s\n", filename); |
---|
64 | close(fd); |
---|
65 | return -1; |
---|
66 | } |
---|
67 | |
---|
68 | /* I've found some wav files that have INFO tags |
---|
69 | * in them... */ |
---|
70 | if(strncmp((char*)hdr + 0, "RIFF", 4) || |
---|
71 | strncmp((char*)hdr + 8, "WAVE", 4)) |
---|
72 | { |
---|
73 | DPRINTF(E_WARN, L_SCANNER, "Invalid wav header in %s\n", filename); |
---|
74 | close(fd); |
---|
75 | return -1; |
---|
76 | } |
---|
77 | |
---|
78 | //chunk_data_length = GET_WAV_INT32(hdr + 4); |
---|
79 | |
---|
80 | /* now, walk through the chunks */ |
---|
81 | current_offset = 12; |
---|
82 | while(current_offset < psong->file_size) |
---|
83 | { |
---|
84 | len = 8; |
---|
85 | if(!(len = read(fd, hdr, len)) || (len != 8)) |
---|
86 | { |
---|
87 | close(fd); |
---|
88 | DPRINTF(E_WARN, L_SCANNER, "Error reading block: %s\n", filename); |
---|
89 | return -1; |
---|
90 | } |
---|
91 | |
---|
92 | current_offset += 8; |
---|
93 | block_len = GET_WAV_INT32(hdr + 4); |
---|
94 | |
---|
95 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Read block %02x%02x%02x%02x (%c%c%c%c) of " |
---|
96 | // "size 0x%08x\n",hdr[0],hdr[1],hdr[2],hdr[3], |
---|
97 | // isprint(hdr[0]) ? hdr[0] : '?', |
---|
98 | // isprint(hdr[1]) ? hdr[1] : '?', |
---|
99 | // isprint(hdr[2]) ? hdr[2] : '?', |
---|
100 | // isprint(hdr[3]) ? hdr[3] : '?', |
---|
101 | // block_len); |
---|
102 | |
---|
103 | if(block_len < 0) |
---|
104 | { |
---|
105 | close(fd); |
---|
106 | DPRINTF(E_WARN, L_SCANNER, "Bad block len: %s\n", filename); |
---|
107 | return -1; |
---|
108 | } |
---|
109 | |
---|
110 | if(strncmp((char*)&hdr, "fmt ", 4) == 0) |
---|
111 | { |
---|
112 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Found 'fmt ' header\n"); |
---|
113 | len = 16; |
---|
114 | if(!read(fd, fmt, len) || (len != 16)) |
---|
115 | { |
---|
116 | close(fd); |
---|
117 | DPRINTF(E_WARN, L_SCANNER, "Bad .wav file: can't read fmt: %s\n", |
---|
118 | filename); |
---|
119 | return -1; |
---|
120 | } |
---|
121 | |
---|
122 | format_data_length = block_len; |
---|
123 | compression_code = GET_WAV_INT16(fmt); |
---|
124 | channel_count = GET_WAV_INT16(fmt + 2); |
---|
125 | sample_rate = GET_WAV_INT32(fmt + 4); |
---|
126 | sample_bit_length = GET_WAV_INT16(fmt + 14); |
---|
127 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Compression code: %d\n",compression_code); |
---|
128 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Channel count: %d\n",channel_count); |
---|
129 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Sample Rate: %d\n",sample_rate); |
---|
130 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Sample bit length %d\n",sample_bit_length); |
---|
131 | |
---|
132 | } |
---|
133 | else if(strncmp((char*)&hdr, "data", 4) == 0) |
---|
134 | { |
---|
135 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Found 'data' header\n"); |
---|
136 | data_length = block_len; |
---|
137 | goto next_block; |
---|
138 | } |
---|
139 | else if(strncmp((char*)&hdr, "LIST", 4) == 0) |
---|
140 | { |
---|
141 | char *tags; |
---|
142 | char *p; |
---|
143 | int off; |
---|
144 | int taglen; |
---|
145 | char **m; |
---|
146 | |
---|
147 | len = GET_WAV_INT32(hdr + 4); |
---|
148 | if(len > 65536) |
---|
149 | goto next_block; |
---|
150 | |
---|
151 | tags = malloc(len); |
---|
152 | if(!tags) |
---|
153 | goto next_block; |
---|
154 | |
---|
155 | if(read(fd, tags, len) < len || |
---|
156 | strncmp(tags, "INFO", 4) != 0) |
---|
157 | { |
---|
158 | free(tags); |
---|
159 | goto next_block; |
---|
160 | } |
---|
161 | |
---|
162 | off = 4; |
---|
163 | p = tags + off; |
---|
164 | while(off < len - 8) |
---|
165 | { |
---|
166 | taglen = GET_WAV_INT32(p + 4); |
---|
167 | |
---|
168 | //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "%.*s: %.*s\n", 4, p, taglen, p + 8); |
---|
169 | m = NULL; |
---|
170 | if (taglen > 2048) { |
---|
171 | DPRINTF(E_WARN, L_SCANNER, "Ignoring long tag [%.*s] in %s\n", |
---|
172 | 4, p+8, filename); |
---|
173 | } |
---|
174 | else if(strncmp(p, "INAM", 4) == 0) |
---|
175 | m = &(psong->title); |
---|
176 | else if(strncmp(p, "IALB", 4) == 0 || |
---|
177 | strncmp(p, "IPRD", 4) == 0) |
---|
178 | m = &(psong->album); |
---|
179 | else if(strncmp(p, "IGRE", 4) == 0 || |
---|
180 | strncmp(p, "IGNR", 4) == 0) |
---|
181 | m = &(psong->genre); |
---|
182 | else if(strncmp(p, "ICMT", 4) == 0) |
---|
183 | m = &(psong->comment); |
---|
184 | else if(strncmp(p, "IART", 4) == 0) |
---|
185 | m = &(psong->contributor[ROLE_TRACKARTIST]); |
---|
186 | else if(strncmp(p, "IAAR", 4) == 0) |
---|
187 | m = &(psong->contributor[ROLE_ALBUMARTIST]); |
---|
188 | else if(strncmp(p, "ICOM", 4) == 0 || |
---|
189 | strncmp(p, "IMUS", 4) == 0) |
---|
190 | m = &(psong->contributor[ROLE_COMPOSER]); |
---|
191 | else if(strncasecmp(p, "ITRK", 4) == 0) |
---|
192 | psong->track = atoi(p + 8); |
---|
193 | else if(strncmp(p, "ICRD", 4) == 0 || |
---|
194 | strncmp(p, "IYER", 4) == 0) |
---|
195 | psong->year = atoi(p + 8); |
---|
196 | if(m) |
---|
197 | { |
---|
198 | *m = malloc(taglen + 1); |
---|
199 | strncpy(*m, p + 8, taglen); |
---|
200 | (*m)[taglen] = '\0'; |
---|
201 | } |
---|
202 | |
---|
203 | p += taglen + 8; |
---|
204 | off += taglen + 8; |
---|
205 | } |
---|
206 | free(tags); |
---|
207 | } |
---|
208 | next_block: |
---|
209 | lseek(fd, current_offset + block_len, SEEK_SET); |
---|
210 | current_offset += block_len; |
---|
211 | } |
---|
212 | close(fd); |
---|
213 | |
---|
214 | if(((format_data_length != 16) && (format_data_length != 18)) || |
---|
215 | (compression_code != 1) || |
---|
216 | (channel_count < 1)) |
---|
217 | { |
---|
218 | DPRINTF(E_WARN, L_SCANNER, "Invalid wav header in %s\n", filename); |
---|
219 | return -1; |
---|
220 | } |
---|
221 | |
---|
222 | if( !data_length ) |
---|
223 | data_length = psong->file_size - 44; |
---|
224 | |
---|
225 | bit_rate = sample_rate * channel_count * ((sample_bit_length + 7) / 8) * 8; |
---|
226 | psong->bitrate = bit_rate; |
---|
227 | psong->samplerate = sample_rate; |
---|
228 | psong->channels = channel_count; |
---|
229 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Data length: %d\n", data_length); |
---|
230 | sec = data_length / (bit_rate / 8); |
---|
231 | ms = ((data_length % (bit_rate / 8)) * 1000) / (bit_rate / 8); |
---|
232 | psong->song_length = (sec * 1000) + ms; |
---|
233 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Song length: %d\n", psong->song_length); |
---|
234 | //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Bit rate: %d\n", psong->bitrate); |
---|
235 | |
---|
236 | /* Upon further review, WAV files should be little-endian, and DLNA requires the LPCM profile to be big-endian. |
---|
237 | asprintf(&(psong->mime), "audio/L16;rate=%d;channels=%d", psong->samplerate, psong->channels); |
---|
238 | */ |
---|
239 | |
---|
240 | return 0; |
---|
241 | } |
---|
242 | |
---|
243 | static int |
---|
244 | _get_wavfileinfo(char *filename, struct song_metadata *psong) |
---|
245 | { |
---|
246 | psong->lossless = 1; |
---|
247 | /* Upon further review, WAV files should be little-endian, and DLNA requires the LPCM profile to be big-endian. |
---|
248 | asprintf(&(psong->dlna_pn), "LPCM"); |
---|
249 | */ |
---|
250 | |
---|
251 | return 0; |
---|
252 | } |
---|