2 * Copyright (C) 2000-2001 Michael Smith (msmith at xiph org)
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation, version 2.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 #include <vorbis/codec.h>
32 #define CHUNKSIZE 4096
34 struct vcedit_state_St {
46 unsigned char *mainbuf;
47 unsigned char *bookbuf;
59 vcedit_state_free (vcedit_state *state)
61 free (state->mainbuf);
62 free (state->bookbuf);
74 vcedit_state_init (vcedit_state *state, const char *filename)
78 strcpy (state->filename, filename);
84 vcedit_state_new (const char *filename)
89 len = strlen (filename);
93 state = malloc (sizeof (vcedit_state) + len + 1);
97 memset (state, 0, sizeof (vcedit_state));
99 if (!vcedit_state_init (state, filename)) {
100 vcedit_state_free (state);
108 vcedit_comments (vcedit_state *state)
110 return state->opened ? &state->vc : NULL;
114 vcedit_clear_internals (vcedit_state *state)
116 ogg_stream_clear (&state->os);
117 ogg_sync_clear (&state->oy);
119 vorbis_info_clear (&state->vi);
120 vorbis_comment_clear (&state->vc);
122 free (state->vendor);
123 state->vendor = NULL;
125 free (state->mainbuf);
126 state->mainbuf = NULL;
129 free (state->bookbuf);
130 state->bookbuf = NULL;
134 state->opened = false;
138 vcedit_state_ref (vcedit_state *state)
144 vcedit_state_unref (vcedit_state *state)
146 if (--state->refcount)
150 vcedit_clear_internals (state);
152 vcedit_state_free (state);
155 /* Next two functions pulled straight from libvorbis, apart from one change
156 * - we don't want to overwrite the vendor string.
159 _v_writestring (oggpack_buffer *o, char *s, int len)
162 oggpack_write (o, *s++, 8);
167 _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
173 oggpack_writeinit (&opb);
176 oggpack_write (&opb, 0x03, 8);
177 _v_writestring (&opb, "vorbis", 6);
180 oggpack_write (&opb, strlen (vendor), 32);
181 _v_writestring (&opb, vendor, strlen (vendor));
184 oggpack_write (&opb, vc->comments, 32);
186 for (i = 0; i < vc->comments; i++) {
187 if (!vc->user_comments[i])
188 oggpack_write (&opb, 0, 32);
190 oggpack_write (&opb, vc->comment_lengths[i], 32);
191 _v_writestring (&opb, vc->user_comments[i],
192 vc->comment_lengths[i]);
196 oggpack_write (&opb, 1, 1);
198 op->packet = _ogg_malloc (oggpack_bytes (&opb));
199 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
201 op->bytes = oggpack_bytes (&opb);
206 oggpack_writeclear (&opb);
212 _blocksize (vcedit_state *s, ogg_packet *p)
216 this = vorbis_packet_blocksize (&s->vi, p);
219 ret = (this + s->prevW) / 4;
227 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
232 result = ogg_stream_packetout (&s->os, p);
240 while (ogg_sync_pageout (&s->oy, page) <= 0) {
241 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
242 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
243 ogg_sync_wrote (&s->oy, bytes);
249 if (ogg_page_eos (page))
251 else if (ogg_page_serialno (page) != s->serial) {
257 ogg_stream_pagein (&s->os, page);
259 return _fetch_next_packet (s, p, page);
263 vcedit_open (vcedit_state *state)
267 size_t bytes, total = 0;
270 ogg_packet header_main, header_comments, header_codebooks;
273 state->in = fopen (state->filename, "rb");
275 return VCEDIT_ERR_OPEN;
277 ogg_sync_init (&state->oy);
280 /* Bail if we don't find data in the first 40 kB */
281 if (feof (state->in) || total >= (CHUNKSIZE * 10)) {
282 ogg_sync_clear (&state->oy);
284 return VCEDIT_ERR_INVAL;
287 buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE);
289 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
292 ogg_sync_wrote (&state->oy, bytes);
293 } while (ogg_sync_pageout (&state->oy, &og) != 1);
295 state->serial = ogg_page_serialno (&og);
297 ogg_stream_init (&state->os, state->serial);
298 vorbis_info_init (&state->vi);
299 vorbis_comment_init (&state->vc);
301 if (ogg_stream_pagein (&state->os, &og) < 0) {
302 ret = VCEDIT_ERR_INVAL;
306 if (ogg_stream_packetout (&state->os, &header_main) != 1) {
307 ret = VCEDIT_ERR_INVAL;
311 if (vorbis_synthesis_headerin (&state->vi, &state->vc, &header_main) < 0) {
312 ret = VCEDIT_ERR_INVAL;
316 state->mainlen = header_main.bytes;
317 state->mainbuf = malloc (state->mainlen);
318 memcpy (state->mainbuf, header_main.packet, header_main.bytes);
321 header = &header_comments;
325 int result = ogg_sync_pageout (&state->oy, &og);
328 break; /* Too little data so far */
331 ogg_stream_pagein (&state->os, &og);
334 result = ogg_stream_packetout (&state->os, header);
340 ret = VCEDIT_ERR_INVAL;
344 vorbis_synthesis_headerin (&state->vi, &state->vc, header);
347 state->booklen = header->bytes;
348 state->bookbuf = malloc (state->booklen);
349 memcpy (state->bookbuf, header->packet, header->bytes);
353 header = &header_codebooks;
358 buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE);
359 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
361 if (bytes == 0 && i < 2) {
362 ret = VCEDIT_ERR_INVAL;
366 ogg_sync_wrote (&state->oy, bytes);
369 /* Copy the vendor tag */
370 state->vendor = strdup (state->vc.vendor);
372 /* Headers are done! */
373 state->opened = true;
375 return VCEDIT_ERR_SUCCESS;
378 vcedit_clear_internals (state);
384 vcedit_write (vcedit_state *state)
386 ogg_stream_state streamout;
387 ogg_packet header_main, header_comments, header_codebooks, op;
388 ogg_page ogout, ogin;
389 ogg_int64_t granpos = 0;
391 char *buffer, tmpfile[PATH_MAX];
392 int s, result, bytes, needflush = 0, needout = 0;
396 return VCEDIT_ERR_INVAL;
398 strcpy (tmpfile, state->filename);
399 strcat (tmpfile, ".XXXXXX");
401 s = mkstemp (tmpfile);
403 return VCEDIT_ERR_TMPFILE;
405 out = fdopen (s, "wb");
410 return VCEDIT_ERR_TMPFILE;
413 state->prevW = state->extrapage = state->eosin = 0;
415 header_main.bytes = state->mainlen;
416 header_main.packet = state->mainbuf;
417 header_main.b_o_s = 1;
418 header_main.e_o_s = 0;
419 header_main.granulepos = 0;
421 header_codebooks.bytes = state->booklen;
422 header_codebooks.packet = state->bookbuf;
423 header_codebooks.b_o_s = 0;
424 header_codebooks.e_o_s = 0;
425 header_codebooks.granulepos = 0;
427 ogg_stream_init (&streamout, state->serial);
429 _commentheader_out (&state->vc, state->vendor, &header_comments);
431 ogg_stream_packetin (&streamout, &header_main);
432 ogg_stream_packetin (&streamout, &header_comments);
433 ogg_stream_packetin (&streamout, &header_codebooks);
435 while ((result = ogg_stream_flush (&streamout, &ogout))) {
436 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
437 if (tmp != (size_t) ogout.header_len)
440 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
441 if (tmp != (size_t) ogout.body_len)
445 while (_fetch_next_packet (state, &op, &ogin)) {
448 size = _blocksize (state, &op);
452 if (ogg_stream_flush (&streamout, &ogout)) {
453 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
454 if (tmp != (size_t) ogout.header_len)
457 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
458 if (tmp != (size_t) ogout.body_len)
461 } else if (needout) {
462 if (ogg_stream_pageout (&streamout, &ogout)) {
463 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
464 if (tmp != (size_t) ogout.header_len)
467 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
468 if (tmp != (size_t) ogout.body_len)
473 needflush = needout = 0;
475 if (op.granulepos == -1) {
476 op.granulepos = granpos;
477 ogg_stream_packetin (&streamout, &op);
479 /* granulepos is set, validly. Use it, and force a flush to
480 * account for shortened blocks (vcut) when appropriate
482 if (granpos > op.granulepos) {
483 granpos = op.granulepos;
484 ogg_stream_packetin (&streamout, &op);
487 ogg_stream_packetin (&streamout, &op);
495 while (ogg_stream_flush (&streamout, &ogout)) {
496 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
497 if (tmp != (size_t) ogout.header_len)
500 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
501 if (tmp != (size_t) ogout.body_len)
505 if (state->extrapage) {
506 tmp = fwrite (ogin.header, 1, ogin.header_len, out);
507 if (tmp != (size_t) ogin.header_len)
510 tmp = fwrite (ogin.body, 1, ogin.body_len, out);
511 if (tmp != (size_t) ogin.body_len)
515 /* clear it, because not all paths to here do */
518 while (!state->eosin) { /* We reached eos, not eof */
519 /* We copy the rest of the stream (other logical streams)
520 * through, a page at a time.
523 result = ogg_sync_pageout (&state->oy, &ogout);
529 /* Don't bother going through the rest, we can just
530 * write the page out now
532 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
533 if (tmp != (size_t) ogout.header_len)
536 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
537 if (tmp != (size_t) ogout.body_len)
542 buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE);
543 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
544 ogg_sync_wrote (&state->oy, bytes);
555 unlink (state->filename);
556 rename (tmpfile, state->filename);
559 ogg_stream_clear (&streamout);
561 /* We don't ogg_packet_clear() this, because the memory was
562 * allocated in _commentheader_out(), so we mirror that here
564 _ogg_free (header_comments.packet);
566 free (state->mainbuf);
567 free (state->bookbuf);
569 state->mainbuf = state->bookbuf = NULL;
572 return VCEDIT_ERR_INVAL;
574 vcedit_clear_internals (state);
576 return (vcedit_open (state) == VCEDIT_ERR_SUCCESS) ?
577 VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;