diff options
author | Magnus Holmgren <magnushol@gmail.com> | 2009-05-30 14:24:16 +0000 |
---|---|---|
committer | Magnus Holmgren <magnushol@gmail.com> | 2009-05-30 14:24:16 +0000 |
commit | 0b495f0a189db12131bd049e26d6695564495806 (patch) | |
tree | ed4da79f4ce98db0b3649772e80d2cc1bab66fd9 /apps | |
parent | 31c279e8802938cde7b1fda619701aaf25db7fbf (diff) | |
download | rockbox-0b495f0a189db12131bd049e26d6695564495806.tar.gz rockbox-0b495f0a189db12131bd049e26d6695564495806.zip |
Improved Vorbis comment reader. The tags no longer need to fit in the first Ogg page to be fully read (album art can make the tags not fit).
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21134 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r-- | apps/metadata/flac.c | 2 | ||||
-rw-r--r-- | apps/metadata/metadata_common.h | 2 | ||||
-rw-r--r-- | apps/metadata/ogg.c | 59 | ||||
-rw-r--r-- | apps/metadata/vorbis.c | 321 |
4 files changed, 286 insertions, 98 deletions
diff --git a/apps/metadata/flac.c b/apps/metadata/flac.c index a50649e54a..21ecdd61ca 100644 --- a/apps/metadata/flac.c +++ b/apps/metadata/flac.c | |||
@@ -101,7 +101,7 @@ bool get_flac_metadata(int fd, struct mp3entry* id3) | |||
101 | else if (type == 4) /* 4 is the VORBIS_COMMENT block */ | 101 | else if (type == 4) /* 4 is the VORBIS_COMMENT block */ |
102 | { | 102 | { |
103 | /* The next i bytes of the file contain the VORBIS COMMENTS. */ | 103 | /* The next i bytes of the file contain the VORBIS COMMENTS. */ |
104 | if (!read_vorbis_tags(fd, id3, i)) | 104 | if (read_vorbis_tags(fd, id3, i) == 0) |
105 | { | 105 | { |
106 | return rc; | 106 | return rc; |
107 | } | 107 | } |
diff --git a/apps/metadata/metadata_common.h b/apps/metadata/metadata_common.h index c8c0dc463f..1bfa6b09e4 100644 --- a/apps/metadata/metadata_common.h +++ b/apps/metadata/metadata_common.h | |||
@@ -32,7 +32,7 @@ | |||
32 | enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS }; | 32 | enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS }; |
33 | 33 | ||
34 | bool read_ape_tags(int fd, struct mp3entry* id3); | 34 | bool read_ape_tags(int fd, struct mp3entry* id3); |
35 | bool read_vorbis_tags(int fd, struct mp3entry *id3, | 35 | long read_vorbis_tags(int fd, struct mp3entry *id3, |
36 | long tag_remaining); | 36 | long tag_remaining); |
37 | 37 | ||
38 | bool skip_id3v2(int fd, struct mp3entry *id3); | 38 | bool skip_id3v2(int fd, struct mp3entry *id3); |
diff --git a/apps/metadata/ogg.c b/apps/metadata/ogg.c index cd4c85f46e..3a3cb29998 100644 --- a/apps/metadata/ogg.c +++ b/apps/metadata/ogg.c | |||
@@ -115,64 +115,7 @@ bool get_ogg_metadata(int fd, struct mp3entry* id3) | |||
115 | * one from the last page (since we only support a single bitstream). | 115 | * one from the last page (since we only support a single bitstream). |
116 | */ | 116 | */ |
117 | serial = get_long_le(&buf[14]); | 117 | serial = get_long_le(&buf[14]); |
118 | 118 | comment_size = read_vorbis_tags(fd, id3, remaining); | |
119 | /* Minimum header length for Ogg pages is 27. */ | ||
120 | if (read(fd, buf, 27) < 27) | ||
121 | { | ||
122 | return false; | ||
123 | } | ||
124 | |||
125 | if (memcmp(buf, "OggS", 4) !=0 ) | ||
126 | { | ||
127 | return false; | ||
128 | } | ||
129 | |||
130 | segments = buf[26]; | ||
131 | |||
132 | /* read in segment table */ | ||
133 | if (read(fd, buf, segments) < segments) | ||
134 | { | ||
135 | return false; | ||
136 | } | ||
137 | |||
138 | /* The second packet in a vorbis stream is the comment packet. It *may* | ||
139 | * extend beyond the second page, but usually does not. Here we find the | ||
140 | * length of the comment packet (or the rest of the page if the comment | ||
141 | * packet extends to the third page). | ||
142 | */ | ||
143 | for (i = 0; i < segments; i++) | ||
144 | { | ||
145 | remaining += buf[i]; | ||
146 | |||
147 | /* The last segment of a packet is always < 255 bytes */ | ||
148 | if (buf[i] < 255) | ||
149 | { | ||
150 | break; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | comment_size = remaining; | ||
155 | |||
156 | if (id3->codectype == AFMT_OGG_VORBIS) { | ||
157 | /* Now read in packet header (type and id string) */ | ||
158 | if (read(fd, buf, 7) < 7) | ||
159 | { | ||
160 | return false; | ||
161 | } | ||
162 | |||
163 | remaining -= 7; | ||
164 | |||
165 | /* The first byte of a packet is the packet type; comment packets are | ||
166 | * type 3. | ||
167 | */ | ||
168 | if (buf[0] != 3) | ||
169 | { | ||
170 | return false; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | /* Failure to read the tags isn't fatal. */ | ||
175 | read_vorbis_tags(fd, id3, remaining); | ||
176 | 119 | ||
177 | /* We now need to search for the last page in the file - identified by | 120 | /* We now need to search for the last page in the file - identified by |
178 | * by ('O','g','g','S',0) and retrieve totalsamples. | 121 | * by ('O','g','g','S',0) and retrieve totalsamples. |
diff --git a/apps/metadata/vorbis.c b/apps/metadata/vorbis.c index cfaa7158f1..c9fcd471cc 100644 --- a/apps/metadata/vorbis.c +++ b/apps/metadata/vorbis.c | |||
@@ -31,90 +31,335 @@ | |||
31 | #include "structec.h" | 31 | #include "structec.h" |
32 | #include "logf.h" | 32 | #include "logf.h" |
33 | 33 | ||
34 | /* Read the items in a Vorbis comment packet. Returns true the items were | 34 | |
35 | * fully read, false otherwise. | 35 | struct file |
36 | { | ||
37 | int fd; | ||
38 | bool packet_ended; | ||
39 | long packet_remaining; | ||
40 | }; | ||
41 | |||
42 | |||
43 | /* Read an Ogg page header. file->packet_remaining is set to the size of the | ||
44 | * first packet on the page; file->packet_ended is set to true if the packet | ||
45 | * ended on the current page. Returns true if the page header was | ||
46 | * successfully read. | ||
36 | */ | 47 | */ |
37 | bool read_vorbis_tags(int fd, struct mp3entry *id3, | 48 | static bool file_read_page_header(struct file* file) |
38 | long tag_remaining) | ||
39 | { | 49 | { |
40 | char *buf = id3->id3v2buf; | 50 | unsigned char buffer[64]; |
41 | int32_t comment_count; | 51 | ssize_t table_left; |
42 | int32_t len; | ||
43 | int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); | ||
44 | int i; | ||
45 | 52 | ||
46 | if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len)) | 53 | /* Size of page header without segment table */ |
54 | if (read(file->fd, buffer, 27) != 27) | ||
47 | { | 55 | { |
48 | return false; | 56 | return false; |
49 | } | 57 | } |
50 | 58 | ||
51 | if ((lseek(fd, len, SEEK_CUR) < 0) | 59 | if (memcmp("OggS", buffer, 4)) |
52 | || (ecread(fd, &comment_count, 1, "l", IS_BIG_ENDIAN) | ||
53 | < (long) sizeof(comment_count))) | ||
54 | { | 60 | { |
55 | return false; | 61 | return false; |
56 | } | 62 | } |
63 | |||
64 | /* Skip pattern (4), version (1), flags (1), granule position (8), | ||
65 | * serial (4), pageno (4), checksum (4) | ||
66 | */ | ||
67 | table_left = buffer[26]; | ||
68 | file->packet_remaining = 0; | ||
69 | |||
70 | /* Read segment table for the first packet */ | ||
71 | do | ||
72 | { | ||
73 | ssize_t count = MIN(sizeof(buffer), (size_t) table_left); | ||
74 | int i; | ||
75 | |||
76 | if (read(file->fd, buffer, count) < count) | ||
77 | { | ||
78 | return false; | ||
79 | } | ||
80 | |||
81 | table_left -= count; | ||
82 | |||
83 | for (i = 0; i < count; i++) | ||
84 | { | ||
85 | file->packet_remaining += buffer[i]; | ||
86 | |||
87 | if (buffer[i] < 255) | ||
88 | { | ||
89 | file->packet_ended = true; | ||
90 | |||
91 | /* Skip remainder of the table */ | ||
92 | if (lseek(file->fd, table_left, SEEK_CUR) < 0) | ||
93 | { | ||
94 | return false; | ||
95 | } | ||
96 | |||
97 | table_left = 0; | ||
98 | break; | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | while (table_left > 0); | ||
57 | 103 | ||
58 | tag_remaining -= len + sizeof(len) + sizeof(comment_count); | 104 | return true; |
105 | } | ||
106 | |||
59 | 107 | ||
60 | if (tag_remaining <= 0) | 108 | /* Read (up to) buffer_size of data from the file. If buffer is NULL, just |
109 | * skip ahead buffer_size bytes (like lseek). Returns number of bytes read, | ||
110 | * 0 if there is no more data to read (in the packet or the file), < 0 if a | ||
111 | * read error occurred. | ||
112 | */ | ||
113 | static ssize_t file_read(struct file* file, void* buffer, size_t buffer_size) | ||
114 | { | ||
115 | ssize_t done = 0; | ||
116 | ssize_t count = -1; | ||
117 | |||
118 | do | ||
61 | { | 119 | { |
62 | return true; | 120 | if (file->packet_remaining <= 0) |
121 | { | ||
122 | if (file->packet_ended) | ||
123 | { | ||
124 | break; | ||
125 | } | ||
126 | |||
127 | if (!file_read_page_header(file)) | ||
128 | { | ||
129 | count = -1; | ||
130 | break; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | count = MIN(buffer_size, (size_t) file->packet_remaining); | ||
135 | |||
136 | if (buffer) | ||
137 | { | ||
138 | count = read(file->fd, buffer, count); | ||
139 | } | ||
140 | else | ||
141 | { | ||
142 | if (lseek(file->fd, count, SEEK_CUR) < 0) | ||
143 | { | ||
144 | count = -1; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | if (count <= 0) | ||
149 | { | ||
150 | break; | ||
151 | } | ||
152 | |||
153 | if (buffer) | ||
154 | { | ||
155 | buffer += count; | ||
156 | } | ||
157 | |||
158 | buffer_size -= count; | ||
159 | done += count; | ||
160 | file->packet_remaining -= count; | ||
63 | } | 161 | } |
162 | while (buffer_size > 0); | ||
163 | |||
164 | return (count < 0 ? count : done); | ||
165 | } | ||
166 | |||
64 | 167 | ||
65 | for (i = 0; i < comment_count; i++) | 168 | /* Read an int32 from file. Returns false if a read error occurred. |
169 | */ | ||
170 | static bool file_read_int32(struct file* file, int32_t* value) | ||
171 | { | ||
172 | char buf[sizeof(int32_t)]; | ||
173 | |||
174 | if (file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf)) | ||
66 | { | 175 | { |
67 | char name[TAG_NAME_LENGTH]; | 176 | return false; |
68 | char value[TAG_VALUE_LENGTH]; | 177 | } |
69 | int32_t read_len; | 178 | |
179 | *value = get_long_le(buf); | ||
180 | return true; | ||
181 | } | ||
182 | |||
183 | |||
184 | /* Read a string from the file. Read up to buffer_size bytes, or, if eos | ||
185 | * != -1, until the eos character is found (eos is not stored in buf, | ||
186 | * unless it is nil). Writes up to buffer_size chars to buf, always | ||
187 | * terminating with a nil. Returns number of chars read or < 0 if a read | ||
188 | * error occurred. | ||
189 | * | ||
190 | * Unfortunately this is a slightly modified copy of read_string() in | ||
191 | * metadata_common.c... | ||
192 | */ | ||
193 | static long file_read_string(struct file* file, char* buffer, | ||
194 | long buffer_size, int eos, long size) | ||
195 | { | ||
196 | long read_bytes = 0; | ||
197 | |||
198 | while (size > 0) | ||
199 | { | ||
200 | char c; | ||
70 | 201 | ||
71 | if (tag_remaining < 4) | 202 | if (file_read(file, &c, 1) != 1) |
72 | { | 203 | { |
204 | read_bytes = -1; | ||
73 | break; | 205 | break; |
74 | } | 206 | } |
207 | |||
208 | read_bytes++; | ||
209 | size--; | ||
210 | |||
211 | if ((eos != -1) && (eos == (unsigned char) c)) | ||
212 | { | ||
213 | break; | ||
214 | } | ||
215 | |||
216 | if (buffer_size > 1) | ||
217 | { | ||
218 | *buffer++ = c; | ||
219 | buffer_size--; | ||
220 | } | ||
221 | else if (eos == -1) | ||
222 | { | ||
223 | /* No point in reading any more, skip remaining data */ | ||
224 | if (file_read(file, NULL, size) < 0) | ||
225 | { | ||
226 | read_bytes = -1; | ||
227 | } | ||
228 | else | ||
229 | { | ||
230 | read_bytes += size; | ||
231 | } | ||
232 | |||
233 | break; | ||
234 | } | ||
235 | } | ||
236 | |||
237 | *buffer = 0; | ||
238 | return read_bytes; | ||
239 | } | ||
75 | 240 | ||
76 | if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len)) | 241 | |
242 | /* Init struct file for reading from fd. type is the AFMT_* codec type of | ||
243 | * the file, and determines if Ogg pages are to be read. remaining is the | ||
244 | * max amount to read if codec type is FLAC; it is ignored otherwise. | ||
245 | * Returns true if the file was successfully initialized. | ||
246 | */ | ||
247 | static bool file_init(struct file* file, int fd, int type, int remaining) | ||
248 | { | ||
249 | memset(file, 0, sizeof(*file)); | ||
250 | file->fd = fd; | ||
251 | |||
252 | if (type == AFMT_OGG_VORBIS || type == AFMT_SPEEX) | ||
253 | { | ||
254 | if (!file_read_page_header(file)) | ||
77 | { | 255 | { |
78 | return false; | 256 | return false; |
79 | } | 257 | } |
258 | } | ||
80 | 259 | ||
81 | tag_remaining -= 4; | 260 | if (type == AFMT_OGG_VORBIS) |
261 | { | ||
262 | char buffer[7]; | ||
82 | 263 | ||
83 | /* Quit if we've passed the end of the page */ | 264 | /* Read packet header (type and id string) */ |
84 | if (tag_remaining < len) | 265 | if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) |
85 | { | 266 | { |
86 | break; | 267 | return false; |
268 | } | ||
269 | |||
270 | /* The first byte of a packet is the packet type; comment packets | ||
271 | * are type 3. | ||
272 | */ | ||
273 | if (buffer[0] != 3) | ||
274 | { | ||
275 | return false; | ||
87 | } | 276 | } |
277 | } | ||
278 | else if (type == AFMT_FLAC) | ||
279 | { | ||
280 | file->packet_remaining = remaining; | ||
281 | file->packet_ended = true; | ||
282 | } | ||
283 | |||
284 | return true; | ||
285 | } | ||
286 | |||
88 | 287 | ||
89 | tag_remaining -= len; | 288 | /* Read the items in a Vorbis comment packet. For Ogg files, the file must |
90 | read_len = read_string(fd, name, sizeof(name), '=', len); | 289 | * be located on a page start, for other files, the beginning of the comment |
290 | * data (i.e., the vendor string length). Returns total size of the | ||
291 | * comments, or 0 if there was a read error. | ||
292 | */ | ||
293 | long read_vorbis_tags(int fd, struct mp3entry *id3, | ||
294 | long tag_remaining) | ||
295 | { | ||
296 | struct file file; | ||
297 | char *buf = id3->id3v2buf; | ||
298 | int32_t comment_count; | ||
299 | int32_t len; | ||
300 | long comment_size = 0; | ||
301 | int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); | ||
302 | int i; | ||
303 | |||
304 | if (!file_init(&file, fd, id3->codectype, tag_remaining)) | ||
305 | { | ||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | /* Skip vendor string */ | ||
310 | |||
311 | if (!file_read_int32(&file, &len) || (file_read(&file, NULL, len) < 0)) | ||
312 | { | ||
313 | return 0; | ||
314 | } | ||
315 | |||
316 | if (!file_read_int32(&file, &comment_count)) | ||
317 | { | ||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | comment_size += 4 + len + 4; | ||
322 | |||
323 | for (i = 0; i < comment_count && file.packet_remaining > 0; i++) | ||
324 | { | ||
325 | char name[TAG_NAME_LENGTH]; | ||
326 | int32_t read_len; | ||
327 | |||
328 | if (!file_read_int32(&file, &len)) | ||
329 | { | ||
330 | return 0; | ||
331 | } | ||
332 | |||
333 | comment_size += 4 + len; | ||
334 | read_len = file_read_string(&file, name, sizeof(name), '=', len); | ||
91 | 335 | ||
92 | if (read_len < 0) | 336 | if (read_len < 0) |
93 | { | 337 | { |
94 | return false; | 338 | return 0; |
95 | } | 339 | } |
96 | 340 | ||
97 | len -= read_len; | 341 | len -= read_len; |
98 | 342 | ||
99 | if (read_string(fd, value, sizeof(value), -1, len) < 0) | 343 | if (file_read_string(&file, id3->path, sizeof(id3->path), -1, len) < 0) |
100 | { | 344 | { |
101 | return false; | 345 | return 0; |
102 | } | 346 | } |
103 | 347 | ||
104 | len = parse_tag(name, value, id3, buf, buf_remaining, | 348 | DEBUGF("Vorbis comment %d: %s=%s\n", i, name, id3->path); |
349 | len = parse_tag(name, id3->path, id3, buf, buf_remaining, | ||
105 | TAGTYPE_VORBIS); | 350 | TAGTYPE_VORBIS); |
106 | buf += len; | 351 | buf += len; |
107 | buf_remaining -= len; | 352 | buf_remaining -= len; |
108 | } | 353 | } |
109 | 354 | ||
110 | /* Skip to the end of the block */ | 355 | /* Skip to the end of the block (needed by FLAC) */ |
111 | if (tag_remaining) | 356 | if (file.packet_remaining) |
112 | { | 357 | { |
113 | if (lseek(fd, tag_remaining, SEEK_CUR) < 0) | 358 | if (file_read(&file, NULL, file.packet_remaining) < 0) |
114 | { | 359 | { |
115 | return false; | 360 | return 0; |
116 | } | 361 | } |
117 | } | 362 | } |
118 | 363 | ||
119 | return true; | 364 | return comment_size; |
120 | } | 365 | } |