diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/cuesheet.c | 127 | ||||
-rw-r--r-- | apps/cuesheet.h | 15 | ||||
-rw-r--r-- | apps/metadata.c | 4 | ||||
-rw-r--r-- | apps/metadata.h | 16 | ||||
-rw-r--r-- | apps/metadata/id3tags.c | 34 | ||||
-rw-r--r-- | apps/metadata/vorbis.c | 20 | ||||
-rw-r--r-- | apps/mpeg.c | 6 | ||||
-rw-r--r-- | apps/playback.c | 6 |
8 files changed, 186 insertions, 42 deletions
diff --git a/apps/cuesheet.c b/apps/cuesheet.c index 935af60898..ab4063a66a 100644 --- a/apps/cuesheet.c +++ b/apps/cuesheet.c | |||
@@ -42,21 +42,29 @@ | |||
42 | 42 | ||
43 | #define CUE_DIR ROCKBOX_DIR "/cue" | 43 | #define CUE_DIR ROCKBOX_DIR "/cue" |
44 | 44 | ||
45 | bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path) | 45 | bool look_for_cuesheet_file(struct mp3entry *track_id3, struct cuesheet_file *cue_file) |
46 | { | 46 | { |
47 | /* DEBUGF("look for cue file\n"); */ | 47 | /* DEBUGF("look for cue file\n"); */ |
48 | 48 | ||
49 | char cuepath[MAX_PATH]; | 49 | char cuepath[MAX_PATH]; |
50 | char *dot, *slash; | 50 | char *dot, *slash; |
51 | 51 | ||
52 | slash = strrchr(trackpath, '/'); | 52 | if (track_id3->embed_cuesheet.present) |
53 | if (!slash) | ||
54 | { | 53 | { |
55 | found_cue_path = NULL; | 54 | cue_file->pos = track_id3->embed_cuesheet.pos; |
56 | return false; | 55 | cue_file->size = track_id3->embed_cuesheet.size; |
56 | cue_file->encoding = track_id3->embed_cuesheet.encoding; | ||
57 | strlcpy(cue_file->path, track_id3->path, MAX_PATH); | ||
58 | return true; | ||
57 | } | 59 | } |
58 | 60 | ||
59 | strlcpy(cuepath, trackpath, MAX_PATH); | 61 | cue_file->pos = 0; |
62 | cue_file->size = 0; | ||
63 | cue_file->path[0] = '\0'; | ||
64 | slash = strrchr(track_id3->path, '/'); | ||
65 | if (!slash) | ||
66 | return false; | ||
67 | strlcpy(cuepath, track_id3->path, MAX_PATH); | ||
60 | dot = strrchr(cuepath, '.'); | 68 | dot = strrchr(cuepath, '.'); |
61 | strcpy(dot, ".cue"); | 69 | strcpy(dot, ".cue"); |
62 | 70 | ||
@@ -67,15 +75,10 @@ bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path) | |||
67 | char *dot = strrchr(cuepath, '.'); | 75 | char *dot = strrchr(cuepath, '.'); |
68 | strcpy(dot, ".cue"); | 76 | strcpy(dot, ".cue"); |
69 | if (!file_exists(cuepath)) | 77 | if (!file_exists(cuepath)) |
70 | { | ||
71 | if (found_cue_path) | ||
72 | found_cue_path = NULL; | ||
73 | return false; | 78 | return false; |
74 | } | ||
75 | } | 79 | } |
76 | 80 | ||
77 | if (found_cue_path) | 81 | strlcpy(cue_file->path, cuepath, MAX_PATH); |
78 | strlcpy(found_cue_path, cuepath, MAX_PATH); | ||
79 | return true; | 82 | return true; |
80 | } | 83 | } |
81 | 84 | ||
@@ -99,29 +102,81 @@ static char *get_string(const char *line) | |||
99 | return start; | 102 | return start; |
100 | } | 103 | } |
101 | 104 | ||
102 | /* parse cuesheet "file" and store the information in "cue" */ | 105 | /* parse cuesheet "cue_file" and store the information in "cue" */ |
103 | bool parse_cuesheet(char *file, struct cuesheet *cue) | 106 | bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) |
104 | { | 107 | { |
105 | char line[MAX_PATH]; | 108 | char line[MAX_PATH]; |
106 | char *s; | 109 | char *s; |
107 | bool utf8 = false; | 110 | unsigned char char_enc = CHAR_ENC_ISO_8859_1; |
111 | bool is_embedded = false; | ||
112 | int line_len; | ||
113 | int bytes_left = 0; | ||
114 | int read_bytes = MAX_PATH; | ||
115 | unsigned char utf16_buf[MAX_PATH]; | ||
116 | |||
117 | int fd = open(cue_file->path, O_RDONLY, 0644); | ||
118 | if(fd < 0) | ||
119 | return false; | ||
120 | if (cue_file->pos > 0) | ||
121 | { | ||
122 | is_embedded = true; | ||
123 | lseek(fd, cue_file->pos, SEEK_SET); | ||
124 | bytes_left = cue_file->size; | ||
125 | char_enc = cue_file->encoding; | ||
126 | } | ||
108 | 127 | ||
109 | int fd = open_utf8(file,O_RDONLY); | 128 | /* Look for a Unicode BOM */ |
110 | if (fd < 0) | 129 | unsigned char bom_read = 0; |
130 | read(fd, line, 3); | ||
131 | if(!memcmp(line, "\xef\xbb\xbf", 3)) | ||
111 | { | 132 | { |
112 | /* couln't open the file */ | 133 | char_enc = CHAR_ENC_UTF_8; |
113 | return false; | 134 | bom_read = 3; |
135 | } | ||
136 | else if(!memcmp(line, "\xff\xfe", 2)) | ||
137 | { | ||
138 | char_enc = CHAR_ENC_UTF_16_LE; | ||
139 | bom_read = 2; | ||
140 | } | ||
141 | else if(!memcmp(line, "\xfe\xff", 2)) | ||
142 | { | ||
143 | char_enc = CHAR_ENC_UTF_16_BE; | ||
144 | bom_read = 2; | ||
145 | } | ||
146 | if (bom_read < 3 ) | ||
147 | lseek(fd, cue_file->pos + bom_read, SEEK_SET); | ||
148 | if (is_embedded) | ||
149 | { | ||
150 | if (bom_read > 0) | ||
151 | bytes_left -= bom_read; | ||
152 | if (read_bytes > bytes_left) | ||
153 | read_bytes = bytes_left; | ||
114 | } | 154 | } |
115 | if(lseek(fd, 0, SEEK_CUR) > 0) | ||
116 | utf8 = true; | ||
117 | 155 | ||
118 | /* Initialization */ | 156 | /* Initialization */ |
119 | memset(cue, 0, sizeof(struct cuesheet)); | 157 | memset(cue, 0, sizeof(struct cuesheet)); |
120 | strcpy(cue->path, file); | 158 | strcpy(cue->path, cue_file->path); |
121 | cue->curr_track = cue->tracks; | 159 | cue->curr_track = cue->tracks; |
122 | 160 | ||
123 | while ( read_line(fd,line,MAX_PATH) && cue->track_count < MAX_TRACKS ) | 161 | while ((line_len = read_line(fd, line, read_bytes)) > 0 |
162 | && cue->track_count < MAX_TRACKS ) | ||
124 | { | 163 | { |
164 | if (char_enc == CHAR_ENC_UTF_16_LE) | ||
165 | { | ||
166 | s = utf16LEdecode(line, utf16_buf, line_len); | ||
167 | /* terminate the string at the newline */ | ||
168 | *s = '\0'; | ||
169 | strcpy(line, utf16_buf); | ||
170 | /* chomp the trailing 0 after the newline */ | ||
171 | lseek(fd, 1, SEEK_CUR); | ||
172 | line_len++; | ||
173 | } | ||
174 | else if (char_enc == CHAR_ENC_UTF_16_BE) | ||
175 | { | ||
176 | s = utf16BEdecode(line, utf16_buf, line_len); | ||
177 | *s = '\0'; | ||
178 | strcpy(line, utf16_buf); | ||
179 | } | ||
125 | s = skip_whitespace(line); | 180 | s = skip_whitespace(line); |
126 | 181 | ||
127 | if (!strncmp(s, "TRACK", 5)) | 182 | if (!strncmp(s, "TRACK", 5)) |
@@ -169,9 +224,10 @@ bool parse_cuesheet(char *file, struct cuesheet *cue) | |||
169 | 224 | ||
170 | if (dest) | 225 | if (dest) |
171 | { | 226 | { |
172 | if (!utf8) | 227 | if (char_enc == CHAR_ENC_ISO_8859_1) |
173 | { | 228 | { |
174 | dest = iso_decode(string, dest, -1, MIN(strlen(string), MAX_NAME)); | 229 | dest = iso_decode(string, dest, -1, |
230 | MIN(strlen(string), MAX_NAME)); | ||
175 | *dest = '\0'; | 231 | *dest = '\0'; |
176 | } | 232 | } |
177 | else | 233 | else |
@@ -180,6 +236,14 @@ bool parse_cuesheet(char *file, struct cuesheet *cue) | |||
180 | } | 236 | } |
181 | } | 237 | } |
182 | } | 238 | } |
239 | if (is_embedded) | ||
240 | { | ||
241 | bytes_left -= line_len; | ||
242 | if (bytes_left <= 0) | ||
243 | break; | ||
244 | if (bytes_left < read_bytes) | ||
245 | read_bytes = bytes_left; | ||
246 | } | ||
183 | } | 247 | } |
184 | close(fd); | 248 | close(fd); |
185 | 249 | ||
@@ -256,7 +320,7 @@ void browse_cuesheet(struct cuesheet *cue) | |||
256 | bool done = false; | 320 | bool done = false; |
257 | int sel; | 321 | int sel; |
258 | char title[MAX_PATH]; | 322 | char title[MAX_PATH]; |
259 | char cuepath[MAX_PATH]; | 323 | struct cuesheet_file cue_file; |
260 | struct mp3entry *id3 = audio_current_track(); | 324 | struct mp3entry *id3 = audio_current_track(); |
261 | 325 | ||
262 | snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title); | 326 | snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title); |
@@ -283,8 +347,8 @@ void browse_cuesheet(struct cuesheet *cue) | |||
283 | id3 = audio_current_track(); | 347 | id3 = audio_current_track(); |
284 | if (id3 && *id3->path && strcmp(id3->path, "No file!")) | 348 | if (id3 && *id3->path && strcmp(id3->path, "No file!")) |
285 | { | 349 | { |
286 | look_for_cuesheet_file(id3->path, cuepath); | 350 | look_for_cuesheet_file(id3, &cue_file); |
287 | if (id3->cuesheet && !strcmp(cue->path, cuepath)) | 351 | if (id3->cuesheet && !strcmp(cue->path, cue_file.path)) |
288 | { | 352 | { |
289 | sel = gui_synclist_get_sel_pos(&lists); | 353 | sel = gui_synclist_get_sel_pos(&lists); |
290 | seek(cue->tracks[sel/2].offset); | 354 | seek(cue->tracks[sel/2].offset); |
@@ -300,11 +364,16 @@ void browse_cuesheet(struct cuesheet *cue) | |||
300 | bool display_cuesheet_content(char* filename) | 364 | bool display_cuesheet_content(char* filename) |
301 | { | 365 | { |
302 | size_t bufsize = 0; | 366 | size_t bufsize = 0; |
367 | struct cuesheet_file cue_file; | ||
303 | struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize); | 368 | struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize); |
304 | if (!cue || bufsize < sizeof(struct cuesheet)) | 369 | if (!cue || bufsize < sizeof(struct cuesheet)) |
305 | return false; | 370 | return false; |
306 | 371 | ||
307 | if (!parse_cuesheet(filename, cue)) | 372 | strlcpy(cue_file.path, filename, MAX_PATH); |
373 | cue_file.pos = 0; | ||
374 | cue_file.size = 0; | ||
375 | |||
376 | if (!parse_cuesheet(&cue_file, cue)) | ||
308 | return false; | 377 | return false; |
309 | 378 | ||
310 | browse_cuesheet(cue); | 379 | browse_cuesheet(cue); |
diff --git a/apps/cuesheet.h b/apps/cuesheet.h index e8d77ca3a9..31841dacf6 100644 --- a/apps/cuesheet.h +++ b/apps/cuesheet.h | |||
@@ -51,11 +51,18 @@ struct cuesheet { | |||
51 | struct cue_track_info *curr_track; | 51 | struct cue_track_info *curr_track; |
52 | }; | 52 | }; |
53 | 53 | ||
54 | /* looks if there is a cuesheet file that has a name matching "trackpath" */ | 54 | struct cuesheet_file { |
55 | bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path); | 55 | char path[MAX_PATH]; |
56 | int size; | ||
57 | off_t pos; | ||
58 | enum character_encoding encoding; | ||
59 | }; | ||
60 | |||
61 | /* looks if there is a cuesheet file with a name matching path of "track_id3" */ | ||
62 | bool look_for_cuesheet_file(struct mp3entry *track_id3, struct cuesheet_file *cue_file); | ||
56 | 63 | ||
57 | /* parse cuesheet "file" and store the information in "cue" */ | 64 | /* parse cuesheet_file "cue_file" and store the information in "cue" */ |
58 | bool parse_cuesheet(char *file, struct cuesheet *cue); | 65 | bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue); |
59 | 66 | ||
60 | /* reads a cuesheet to find the audio track associated to it */ | 67 | /* reads a cuesheet to find the audio track associated to it */ |
61 | bool get_trackname_from_cuesheet(char *filename, char *buf); | 68 | bool get_trackname_from_cuesheet(char *filename, char *buf); |
diff --git a/apps/metadata.c b/apps/metadata.c index 7479de105f..898436781b 100644 --- a/apps/metadata.c +++ b/apps/metadata.c | |||
@@ -438,6 +438,10 @@ bool get_metadata(struct mp3entry* id3, int fd, const char* trackname) | |||
438 | /* Take our best guess at the codec type based on file extension */ | 438 | /* Take our best guess at the codec type based on file extension */ |
439 | id3->codectype = probe_file_format(trackname); | 439 | id3->codectype = probe_file_format(trackname); |
440 | 440 | ||
441 | /* default values for embedded cuesheets */ | ||
442 | id3->embed_cuesheet.present = false; | ||
443 | id3->embed_cuesheet.pos = 0; | ||
444 | |||
441 | entry = &audio_formats[id3->codectype]; | 445 | entry = &audio_formats[id3->codectype]; |
442 | 446 | ||
443 | /* Load codec specific track tag information and confirm the codec type. */ | 447 | /* Load codec specific track tag information and confirm the codec type. */ |
diff --git a/apps/metadata.h b/apps/metadata.h index 3676bd8e24..0c6768d3d9 100644 --- a/apps/metadata.h +++ b/apps/metadata.h | |||
@@ -217,6 +217,21 @@ struct mp3_albumart { | |||
217 | }; | 217 | }; |
218 | #endif | 218 | #endif |
219 | 219 | ||
220 | enum character_encoding { | ||
221 | CHAR_ENC_ISO_8859_1 = 1, | ||
222 | CHAR_ENC_UTF_8, | ||
223 | CHAR_ENC_UTF_16_LE, | ||
224 | CHAR_ENC_UTF_16_BE, | ||
225 | }; | ||
226 | |||
227 | /* cache embedded cuesheet details */ | ||
228 | struct embed_cuesheet { | ||
229 | bool present; | ||
230 | int size; | ||
231 | off_t pos; | ||
232 | enum character_encoding encoding; | ||
233 | }; | ||
234 | |||
220 | struct mp3entry { | 235 | struct mp3entry { |
221 | char path[MAX_PATH]; | 236 | char path[MAX_PATH]; |
222 | char* title; | 237 | char* title; |
@@ -307,6 +322,7 @@ struct mp3entry { | |||
307 | #endif | 322 | #endif |
308 | 323 | ||
309 | /* Cuesheet support */ | 324 | /* Cuesheet support */ |
325 | struct embed_cuesheet embed_cuesheet; | ||
310 | struct cuesheet *cuesheet; | 326 | struct cuesheet *cuesheet; |
311 | 327 | ||
312 | /* Musicbrainz Track ID */ | 328 | /* Musicbrainz Track ID */ |
diff --git a/apps/metadata/id3tags.c b/apps/metadata/id3tags.c index dcf71f71bf..e9b59e012a 100644 --- a/apps/metadata/id3tags.c +++ b/apps/metadata/id3tags.c | |||
@@ -995,6 +995,40 @@ void setid3v2title(int fd, struct mp3entry *entry) | |||
995 | if(bytesread >= buffersize - bufferpos) | 995 | if(bytesread >= buffersize - bufferpos) |
996 | bytesread = buffersize - bufferpos - 1; | 996 | bytesread = buffersize - bufferpos - 1; |
997 | 997 | ||
998 | if ( /* Is it an embedded cuesheet? */ | ||
999 | (tr->tag_length == 4 && !memcmp(header, "TXXX", 4)) && | ||
1000 | (bytesread >= 14 && !strncmp(utf8buf, "CUESHEET", 8)) | ||
1001 | ) { | ||
1002 | unsigned char char_enc = 0; | ||
1003 | /* 0CUESHEET0 = 10 bytes */ | ||
1004 | unsigned char cuesheet_offset = 10; | ||
1005 | switch (tag[0]) { | ||
1006 | case 0x00: | ||
1007 | char_enc = CHAR_ENC_ISO_8859_1; | ||
1008 | break; | ||
1009 | case 0x01: | ||
1010 | char_enc = CHAR_ENC_UTF_16_LE; | ||
1011 | cuesheet_offset += cuesheet_offset+1; | ||
1012 | break; | ||
1013 | case 0x02: | ||
1014 | char_enc = CHAR_ENC_UTF_16_BE; | ||
1015 | cuesheet_offset += cuesheet_offset+1; | ||
1016 | break; | ||
1017 | case 0x03: | ||
1018 | char_enc = CHAR_ENC_UTF_8; | ||
1019 | break; | ||
1020 | } | ||
1021 | if (char_enc > 0) { | ||
1022 | entry->embed_cuesheet.present = true; | ||
1023 | entry->embed_cuesheet.pos = lseek(fd, 0, SEEK_CUR) | ||
1024 | - framelen + cuesheet_offset; | ||
1025 | entry->embed_cuesheet.size = totframelen | ||
1026 | - cuesheet_offset; | ||
1027 | entry->embed_cuesheet.encoding = char_enc; | ||
1028 | } | ||
1029 | break; | ||
1030 | } | ||
1031 | |||
998 | for (j = 0; j < bytesread; j++) | 1032 | for (j = 0; j < bytesread; j++) |
999 | tag[j] = utf8buf[j]; | 1033 | tag[j] = utf8buf[j]; |
1000 | 1034 | ||
diff --git a/apps/metadata/vorbis.c b/apps/metadata/vorbis.c index f6d3af1cef..29848daa19 100644 --- a/apps/metadata/vorbis.c +++ b/apps/metadata/vorbis.c | |||
@@ -341,15 +341,29 @@ long read_vorbis_tags(int fd, struct mp3entry *id3, | |||
341 | } | 341 | } |
342 | 342 | ||
343 | len -= read_len; | 343 | len -= read_len; |
344 | read_len = file_read_string(&file, id3->path, sizeof(id3->path), -1, len); | ||
344 | 345 | ||
345 | if (file_read_string(&file, id3->path, sizeof(id3->path), -1, len) < 0) | 346 | if (read_len < 0) |
346 | { | 347 | { |
347 | return 0; | 348 | return 0; |
348 | } | 349 | } |
349 | 350 | ||
350 | logf("Vorbis comment %d: %s=%s", i, name, id3->path); | 351 | logf("Vorbis comment %d: %s=%s", i, name, id3->path); |
351 | len = parse_tag(name, id3->path, id3, buf, buf_remaining, | 352 | |
352 | TAGTYPE_VORBIS); | 353 | /* Is it an embedded cuesheet? */ |
354 | if (!strcasecmp(name, "CUESHEET")) | ||
355 | { | ||
356 | id3->embed_cuesheet.present = true; | ||
357 | id3->embed_cuesheet.pos = lseek(file.fd, 0, SEEK_CUR) - read_len; | ||
358 | id3->embed_cuesheet.size = len; | ||
359 | id3->embed_cuesheet.encoding = CHAR_ENC_UTF_8; | ||
360 | } | ||
361 | else | ||
362 | { | ||
363 | len = parse_tag(name, id3->path, id3, buf, buf_remaining, | ||
364 | TAGTYPE_VORBIS); | ||
365 | } | ||
366 | |||
353 | buf += len; | 367 | buf += len; |
354 | buf_remaining -= len; | 368 | buf_remaining -= len; |
355 | } | 369 | } |
diff --git a/apps/mpeg.c b/apps/mpeg.c index 698695b72d..4fbc40ff1e 100644 --- a/apps/mpeg.c +++ b/apps/mpeg.c | |||
@@ -2170,10 +2170,10 @@ struct mp3entry* audio_current_track(void) | |||
2170 | if (!checked_for_cuesheet && curr_cuesheet && id3->cuesheet == NULL) | 2170 | if (!checked_for_cuesheet && curr_cuesheet && id3->cuesheet == NULL) |
2171 | { | 2171 | { |
2172 | checked_for_cuesheet = true; /* only check once per track */ | 2172 | checked_for_cuesheet = true; /* only check once per track */ |
2173 | char cuepath[MAX_PATH]; | 2173 | struct cuesheet_file cue_file; |
2174 | 2174 | ||
2175 | if (look_for_cuesheet_file(id3->path, cuepath) && | 2175 | if (look_for_cuesheet_file(id3, &cue_file)) && |
2176 | parse_cuesheet(cuepath, curr_cuesheet)) | 2176 | parse_cuesheet(&cue_file, curr_cuesheet)) |
2177 | { | 2177 | { |
2178 | id3->cuesheet = curr_cuesheet; | 2178 | id3->cuesheet = curr_cuesheet; |
2179 | } | 2179 | } |
diff --git a/apps/playback.c b/apps/playback.c index 2739118aeb..36fbd88832 100644 --- a/apps/playback.c +++ b/apps/playback.c | |||
@@ -1485,12 +1485,12 @@ static bool audio_load_cuesheet(struct track_info *info, | |||
1485 | /* If error other than a full buffer, then mark it "unsupported" to | 1485 | /* If error other than a full buffer, then mark it "unsupported" to |
1486 | avoid reloading attempt */ | 1486 | avoid reloading attempt */ |
1487 | int hid = ERR_UNSUPPORTED_TYPE; | 1487 | int hid = ERR_UNSUPPORTED_TYPE; |
1488 | char cuepath[MAX_PATH]; | 1488 | struct cuesheet_file cue_file; |
1489 | 1489 | ||
1490 | #ifdef HAVE_IO_PRIORITY | 1490 | #ifdef HAVE_IO_PRIORITY |
1491 | buf_back_off_storage(true); | 1491 | buf_back_off_storage(true); |
1492 | #endif | 1492 | #endif |
1493 | if (look_for_cuesheet_file(track_id3->path, cuepath)) | 1493 | if (look_for_cuesheet_file(track_id3, &cue_file)) |
1494 | { | 1494 | { |
1495 | hid = bufalloc(NULL, sizeof (struct cuesheet), TYPE_CUESHEET); | 1495 | hid = bufalloc(NULL, sizeof (struct cuesheet), TYPE_CUESHEET); |
1496 | 1496 | ||
@@ -1499,7 +1499,7 @@ static bool audio_load_cuesheet(struct track_info *info, | |||
1499 | void *cuesheet = NULL; | 1499 | void *cuesheet = NULL; |
1500 | bufgetdata(hid, sizeof (struct cuesheet), &cuesheet); | 1500 | bufgetdata(hid, sizeof (struct cuesheet), &cuesheet); |
1501 | 1501 | ||
1502 | if (parse_cuesheet(cuepath, (struct cuesheet *)cuesheet)) | 1502 | if (parse_cuesheet(&cue_file, (struct cuesheet *)cuesheet)) |
1503 | { | 1503 | { |
1504 | /* Indicate cuesheet is present (while track remains | 1504 | /* Indicate cuesheet is present (while track remains |
1505 | buffered) */ | 1505 | buffered) */ |