1 /* This program is licensed under the GNU Library General Public License, version 2,
2 * a copy of which is included with this program (LICENCE.LGPL).
3 *
4 * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au>
5 *
6 *
7 * Comment editing backend, suitable for use by nice frontend interfaces.
8 *
9 * last modified: $Id: vcedit.c,v 1.1 2002/10/14 22:31:13 paol Exp $
10 */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <ogg/ogg.h>
17 #include <vorbis/codec.h>
18
19 #include "vcedit.h"
20 //#include "i18n.h"
21 #define _(s) s
22
23
24 #define CHUNKSIZE 4096
25
vcedit_new_state(void)26 vcedit_state *vcedit_new_state(void)
27 {
28 vcedit_state *state = malloc(sizeof(vcedit_state));
29 memset(state, 0, sizeof(vcedit_state));
30
31 return state;
32 }
33
vcedit_error(vcedit_state * state)34 char *vcedit_error(vcedit_state *state)
35 {
36 return state->lasterror;
37 }
38
vcedit_comments(vcedit_state * state)39 vorbis_comment *vcedit_comments(vcedit_state *state)
40 {
41 return state->vc;
42 }
43
vcedit_clear_internals(vcedit_state * state)44 static void vcedit_clear_internals(vcedit_state *state)
45 {
46 if(state->vc)
47 {
48 vorbis_comment_clear(state->vc);
49 free(state->vc);
50 }
51 if(state->os)
52 {
53 ogg_stream_clear(state->os);
54 free(state->os);
55 }
56 if(state->oy)
57 {
58 ogg_sync_clear(state->oy);
59 free(state->oy);
60 }
61 if(state->vendor)
62 free(state->vendor);
63 if(state->mainbuf)
64 free(state->mainbuf);
65 if(state->bookbuf)
66 free(state->bookbuf);
67 if(state->vi) {
68 vorbis_info_clear(state->vi);
69 free(state->vi);
70 }
71
72 memset(state, 0, sizeof(*state));
73 }
74
vcedit_clear(vcedit_state * state)75 void vcedit_clear(vcedit_state *state)
76 {
77 if(state)
78 {
79 vcedit_clear_internals(state);
80 free(state);
81 }
82 }
83
84 /* Next two functions pulled straight from libvorbis, apart from one change
85 * - we don't want to overwrite the vendor string.
86 */
_v_writestring(oggpack_buffer * o,char * s,int len)87 static void _v_writestring(oggpack_buffer *o,char *s, int len)
88 {
89 while(len--)
90 {
91 oggpack_write(o,*s++,8);
92 }
93 }
94
_commentheader_out(vorbis_comment * vc,char * vendor,ogg_packet * op)95 static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
96 {
97 oggpack_buffer opb;
98
99 oggpack_writeinit(&opb);
100
101 /* preamble */
102 oggpack_write(&opb,0x03,8);
103 _v_writestring(&opb,"vorbis", 6);
104
105 /* vendor */
106 oggpack_write(&opb,strlen(vendor),32);
107 _v_writestring(&opb,vendor, strlen(vendor));
108
109 /* comments */
110 oggpack_write(&opb,vc->comments,32);
111 if(vc->comments){
112 int i;
113 for(i=0;i<vc->comments;i++){
114 if(vc->user_comments[i]){
115 oggpack_write(&opb,vc->comment_lengths[i],32);
116 _v_writestring(&opb,vc->user_comments[i],
117 vc->comment_lengths[i]);
118 }else{
119 oggpack_write(&opb,0,32);
120 }
121 }
122 }
123 oggpack_write(&opb,1,1);
124
125 op->packet = _ogg_malloc(oggpack_bytes(&opb));
126 memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
127
128 op->bytes=oggpack_bytes(&opb);
129 op->b_o_s=0;
130 op->e_o_s=0;
131 op->granulepos=0;
132
133 oggpack_writeclear(&opb);
134 return 0;
135 }
136
_blocksize(vcedit_state * s,ogg_packet * p)137 static int _blocksize(vcedit_state *s, ogg_packet *p)
138 {
139 int this = vorbis_packet_blocksize(s->vi, p);
140 int ret = (this + s->prevW)/4;
141
142 if(!s->prevW)
143 {
144 s->prevW = this;
145 return 0;
146 }
147
148 s->prevW = this;
149 return ret;
150 }
151
_fetch_next_packet(vcedit_state * s,ogg_packet * p,ogg_page * page)152 static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
153 {
154 int result;
155 char *buffer;
156 int bytes;
157
158 result = ogg_stream_packetout(s->os, p);
159
160 if(result > 0)
161 return 1;
162 else
163 {
164 if(s->eosin)
165 return 0;
166 while(ogg_sync_pageout(s->oy, page) <= 0)
167 {
168 buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
169 bytes = s->read(buffer,1, CHUNKSIZE, s->in);
170 ogg_sync_wrote(s->oy, bytes);
171 if(bytes == 0)
172 return 0;
173 }
174 if(ogg_page_eos(page))
175 s->eosin = 1;
176 else if(ogg_page_serialno(page) != s->serial)
177 {
178 s->eosin = 1;
179 s->extrapage = 1;
180 return 0;
181 }
182
183 ogg_stream_pagein(s->os, page);
184 return _fetch_next_packet(s, p, page);
185 }
186 }
187
vcedit_open(vcedit_state * state,FILE * in)188 int vcedit_open(vcedit_state *state, FILE *in)
189 {
190 return vcedit_open_callbacks(state, (void *)in,
191 (vcedit_read_func)fread, (vcedit_write_func)fwrite);
192 }
193
vcedit_open_callbacks(vcedit_state * state,void * in,vcedit_read_func read_func,vcedit_write_func write_func)194 int vcedit_open_callbacks(vcedit_state *state, void *in,
195 vcedit_read_func read_func, vcedit_write_func write_func)
196 {
197
198 char *buffer;
199 int bytes,i;
200 ogg_packet *header;
201 ogg_packet header_main;
202 ogg_packet header_comments;
203 ogg_packet header_codebooks;
204 ogg_page og;
205
206 state->in = in;
207 state->read = read_func;
208 state->write = write_func;
209
210 state->oy = malloc(sizeof(ogg_sync_state));
211 ogg_sync_init(state->oy);
212
213 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
214 bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
215
216 ogg_sync_wrote(state->oy, bytes);
217
218 if(ogg_sync_pageout(state->oy, &og) != 1)
219 {
220 if(bytes<CHUNKSIZE)
221 state->lasterror = _("Input truncated or empty.");
222 else
223 state->lasterror = _("Input is not an Ogg bitstream.");
224 goto err;
225 }
226
227 state->serial = ogg_page_serialno(&og);
228
229 state->os = malloc(sizeof(ogg_stream_state));
230 ogg_stream_init(state->os, state->serial);
231
232 state->vi = malloc(sizeof(vorbis_info));
233 vorbis_info_init(state->vi);
234
235 state->vc = malloc(sizeof(vorbis_comment));
236 vorbis_comment_init(state->vc);
237
238 if(ogg_stream_pagein(state->os, &og) < 0)
239 {
240 state->lasterror = _("Error reading first page of Ogg bitstream.");
241 goto err;
242 }
243
244 if(ogg_stream_packetout(state->os, &header_main) != 1)
245 {
246 state->lasterror = _("Error reading initial header packet.");
247 goto err;
248 }
249
250 if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) < 0)
251 {
252 state->lasterror = _("Ogg bitstream does not contain vorbis data.");
253 goto err;
254 }
255
256 state->mainlen = header_main.bytes;
257 state->mainbuf = malloc(state->mainlen);
258 memcpy(state->mainbuf, header_main.packet, header_main.bytes);
259
260 i = 0;
261 header = &header_comments;
262 while(i<2) {
263 while(i<2) {
264 int result = ogg_sync_pageout(state->oy, &og);
265 if(result == 0) break; /* Too little data so far */
266 else if(result == 1)
267 {
268 ogg_stream_pagein(state->os, &og);
269 while(i<2)
270 {
271 result = ogg_stream_packetout(state->os, header);
272 if(result == 0) break;
273 if(result == -1)
274 {
275 state->lasterror = _("Corrupt secondary header.");
276 goto err;
277 }
278 vorbis_synthesis_headerin(state->vi, state->vc, header);
279 if(i==1)
280 {
281 state->booklen = header->bytes;
282 state->bookbuf = malloc(state->booklen);
283 memcpy(state->bookbuf, header->packet,
284 header->bytes);
285 }
286 i++;
287 header = &header_codebooks;
288 }
289 }
290 }
291
292 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
293 bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
294 if(bytes == 0 && i < 2)
295 {
296 state->lasterror = _("EOF before end of vorbis headers.");
297 goto err;
298 }
299 ogg_sync_wrote(state->oy, bytes);
300 }
301
302 /* Copy the vendor tag */
303 state->vendor = malloc(strlen(state->vc->vendor) +1);
304 strcpy(state->vendor, state->vc->vendor);
305
306 /* Headers are done! */
307 return 0;
308
309 err:
310 vcedit_clear_internals(state);
311 return -1;
312 }
313
vcedit_write(vcedit_state * state,void * out)314 int vcedit_write(vcedit_state *state, void *out)
315 {
316 ogg_stream_state streamout;
317 ogg_packet header_main;
318 ogg_packet header_comments;
319 ogg_packet header_codebooks;
320
321 ogg_page ogout, ogin;
322 ogg_packet op;
323 ogg_int64_t granpos = 0;
324 int result;
325 char *buffer;
326 int bytes;
327 int needflush=0, needout=0;
328
329 state->eosin = 0;
330 state->extrapage = 0;
331
332 header_main.bytes = state->mainlen;
333 header_main.packet = state->mainbuf;
334 header_main.b_o_s = 1;
335 header_main.e_o_s = 0;
336 header_main.granulepos = 0;
337
338 header_codebooks.bytes = state->booklen;
339 header_codebooks.packet = state->bookbuf;
340 header_codebooks.b_o_s = 0;
341 header_codebooks.e_o_s = 0;
342 header_codebooks.granulepos = 0;
343
344 ogg_stream_init(&streamout, state->serial);
345
346 _commentheader_out(state->vc, state->vendor, &header_comments);
347
348 ogg_stream_packetin(&streamout, &header_main);
349 ogg_stream_packetin(&streamout, &header_comments);
350 ogg_stream_packetin(&streamout, &header_codebooks);
351
352 while((result = ogg_stream_flush(&streamout, &ogout)))
353 {
354 if(state->write(ogout.header,1,ogout.header_len, out) !=
355 (size_t) ogout.header_len)
356 goto cleanup;
357 if(state->write(ogout.body,1,ogout.body_len, out) !=
358 (size_t) ogout.body_len)
359 goto cleanup;
360 }
361
362 while(_fetch_next_packet(state, &op, &ogin))
363 {
364 int size;
365 size = _blocksize(state, &op);
366 granpos += size;
367
368 if(needflush)
369 {
370 if(ogg_stream_flush(&streamout, &ogout))
371 {
372 if(state->write(ogout.header,1,ogout.header_len,
373 out) != (size_t) ogout.header_len)
374 goto cleanup;
375 if(state->write(ogout.body,1,ogout.body_len,
376 out) != (size_t) ogout.body_len)
377 goto cleanup;
378 }
379 }
380 else if(needout)
381 {
382 if(ogg_stream_pageout(&streamout, &ogout))
383 {
384 if(state->write(ogout.header,1,ogout.header_len,
385 out) != (size_t) ogout.header_len)
386 goto cleanup;
387 if(state->write(ogout.body,1,ogout.body_len,
388 out) != (size_t) ogout.body_len)
389 goto cleanup;
390 }
391 }
392
393 needflush=needout=0;
394
395 if(op.granulepos == -1)
396 {
397 op.granulepos = granpos;
398 ogg_stream_packetin(&streamout, &op);
399 }
400 else /* granulepos is set, validly. Use it, and force a flush to
401 account for shortened blocks (vcut) when appropriate */
402 {
403 if(granpos > op.granulepos)
404 {
405 granpos = op.granulepos;
406 ogg_stream_packetin(&streamout, &op);
407 needflush=1;
408 }
409 else
410 {
411 ogg_stream_packetin(&streamout, &op);
412 needout=1;
413 }
414 }
415 }
416
417 streamout.e_o_s = 1;
418 while(ogg_stream_flush(&streamout, &ogout))
419 {
420 if(state->write(ogout.header,1,ogout.header_len,
421 out) != (size_t) ogout.header_len)
422 goto cleanup;
423 if(state->write(ogout.body,1,ogout.body_len,
424 out) != (size_t) ogout.body_len)
425 goto cleanup;
426 }
427
428 if (state->extrapage)
429 {
430 if(state->write(ogin.header,1,ogin.header_len,
431 out) != (size_t) ogin.header_len)
432 goto cleanup;
433 if (state->write(ogin.body,1,ogin.body_len, out) !=
434 (size_t) ogin.body_len)
435 goto cleanup;
436 }
437
438 state->eosin=0; /* clear it, because not all paths to here do */
439 while(!state->eosin) /* We reached eos, not eof */
440 {
441 /* We copy the rest of the stream (other logical streams)
442 * through, a page at a time. */
443 while(1)
444 {
445 result = ogg_sync_pageout(state->oy, &ogout);
446 if(result==0)
447 break;
448 if(result<0)
449 state->lasterror = _("Corrupt or missing data, continuing...");
450 else
451 {
452 /* Don't bother going through the rest, we can just
453 * write the page out now */
454 if(state->write(ogout.header,1,ogout.header_len, out) !=
455 (size_t) ogout.header_len) {
456 fprintf(stderr, "Bumming out\n");
457 goto cleanup;
458 }
459 if(state->write(ogout.body,1,ogout.body_len, out) !=
460 (size_t) ogout.body_len) {
461 fprintf(stderr, "Bumming out 2\n");
462 goto cleanup;
463 }
464 }
465 }
466 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
467 bytes = state->read(buffer,1, CHUNKSIZE, state->in);
468 ogg_sync_wrote(state->oy, bytes);
469 if(bytes == 0)
470 {
471 state->eosin = 1;
472 break;
473 }
474 }
475
476
477 cleanup:
478 ogg_stream_clear(&streamout);
479 ogg_packet_clear(&header_comments);
480
481 free(state->mainbuf);
482 free(state->bookbuf);
483 state->mainbuf = state->bookbuf = NULL;
484
485 if(!state->eosin)
486 {
487 state->lasterror =
488 _("Error writing stream to output. "
489 "Output stream may be corrupted or truncated.");
490 return -1;
491 }
492
493 return 0;
494 }
495
496