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,
26 #include <vorbis/codec.h>
31 #define CHUNKSIZE 4096
33 static int vcedit_open (vcedit_state *state);
35 struct vcedit_state_St {
44 char filename[PATH_MAX];
48 unsigned char *mainbuf;
49 unsigned char *bookbuf;
52 const char *lasterror;
60 vcedit_state_new (const char *filename)
64 state = malloc (sizeof (vcedit_state));
68 memset (state, 0, sizeof (vcedit_state));
72 snprintf (state->filename, sizeof (state->filename),
75 state->in = fopen (state->filename, "rb");
77 if (vcedit_open (state) < 0) {
86 vcedit_error (vcedit_state *state)
88 return state->lasterror;
92 vcedit_comments (vcedit_state *state)
98 vcedit_clear_internals (vcedit_state *state)
101 vorbis_comment_clear (state->vc);
107 ogg_stream_clear (state->os);
113 ogg_sync_clear (state->oy);
118 free (state->vendor);
119 state->vendor = NULL;
121 free (state->mainbuf);
122 state->mainbuf = NULL;
125 free (state->bookbuf);
126 state->bookbuf = NULL;
130 vorbis_info_clear (state->vi);
136 state->prevW = state->extrapage = state->eosin = 0;
140 vcedit_state_ref (vcedit_state *state)
146 vcedit_state_unref (vcedit_state *state)
150 if (!state->refcount) {
152 vcedit_clear_internals (state);
157 /* Next two functions pulled straight from libvorbis, apart from one change
158 * - we don't want to overwrite the vendor string.
161 _v_writestring (oggpack_buffer *o, char *s, int len)
164 oggpack_write (o, *s++, 8);
169 _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);
189 for (i = 0; i < vc->comments; i++) {
190 if (vc->user_comments[i]) {
191 oggpack_write (&opb, vc->comment_lengths[i], 32);
192 _v_writestring (&opb, vc->user_comments[i],
193 vc->comment_lengths[i]);
195 oggpack_write (&opb, 0, 32);
199 oggpack_write (&opb, 1, 1);
201 op->packet = _ogg_malloc (oggpack_bytes (&opb));
202 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
204 op->bytes = oggpack_bytes (&opb);
209 oggpack_writeclear (&opb);
215 _blocksize (vcedit_state *s, ogg_packet *p)
217 int this = vorbis_packet_blocksize (s->vi, p);
218 int ret = (this + s->prevW) / 4;
231 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
236 result = ogg_stream_packetout (s->os, p);
244 while (ogg_sync_pageout (s->oy, page) <= 0) {
245 buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
246 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
247 ogg_sync_wrote (s->oy, bytes);
253 if (ogg_page_eos (page))
255 else if (ogg_page_serialno (page) != s->serial) {
261 ogg_stream_pagein (s->os, page);
263 return _fetch_next_packet (s, p, page);
267 vcedit_open (vcedit_state *state)
273 ogg_packet header_main, header_comments, header_codebooks;
276 state->oy = malloc (sizeof (ogg_sync_state));
277 ogg_sync_init (state->oy);
280 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
281 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
283 ogg_sync_wrote (state->oy, bytes);
285 if (ogg_sync_pageout (state->oy, &og) == 1)
288 /* Bail if we don't find data in the first 40 kB */
289 if (chunks++ >= 10) {
290 if (bytes < CHUNKSIZE)
291 state->lasterror = "Input truncated or empty.";
293 state->lasterror = "Input is not an Ogg bitstream.";
299 state->serial = ogg_page_serialno (&og);
301 state->os = malloc (sizeof (ogg_stream_state));
302 ogg_stream_init (state->os, state->serial);
304 state->vi = malloc (sizeof (vorbis_info));
305 vorbis_info_init (state->vi);
307 state->vc = malloc (sizeof (vorbis_comment));
308 vorbis_comment_init (state->vc);
310 if (ogg_stream_pagein (state->os, &og) < 0) {
311 state->lasterror = "Error reading first page of Ogg bitstream.";
315 if (ogg_stream_packetout (state->os, &header_main) != 1) {
316 state->lasterror = "Error reading initial header packet.";
320 if (vorbis_synthesis_headerin (state->vi, state->vc, &header_main) < 0) {
321 state->lasterror = "Ogg bitstream does not contain vorbis data.";
325 state->mainlen = header_main.bytes;
326 state->mainbuf = malloc (state->mainlen);
327 memcpy (state->mainbuf, header_main.packet, header_main.bytes);
330 header = &header_comments;
334 int result = ogg_sync_pageout (state->oy, &og);
337 break; /* Too little data so far */
340 ogg_stream_pagein (state->os, &og);
343 result = ogg_stream_packetout (state->os, header);
349 state->lasterror = "Corrupt secondary header.";
353 vorbis_synthesis_headerin (state->vi, state->vc, header);
356 state->booklen = header->bytes;
357 state->bookbuf = malloc (state->booklen);
358 memcpy (state->bookbuf, header->packet, header->bytes);
362 header = &header_codebooks;
367 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
368 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
370 if (bytes == 0 && i < 2) {
371 state->lasterror = "EOF before end of vorbis headers.";
375 ogg_sync_wrote (state->oy, bytes);
378 /* Copy the vendor tag */
379 state->vendor = strdup (state->vc->vendor);
381 /* Headers are done! */
385 vcedit_clear_internals (state);
391 vcedit_write (vcedit_state *state)
393 ogg_stream_state streamout;
394 ogg_packet header_main, header_comments, header_codebooks, op;
395 ogg_page ogout, ogin;
396 ogg_int64_t granpos = 0;
398 char *buffer, tmpfile[PATH_MAX];
399 int s, result, bytes, needflush = 0, needout = 0;
402 strcpy (tmpfile, state->filename);
403 strcat (tmpfile, ".XXXXXX");
405 s = mkstemp (tmpfile);
407 state->lasterror = "Error writing stream to output. "
408 "Cannot open temporary file.";
412 out = fdopen (s, "wb");
416 state->lasterror = "Error writing stream to output. "
417 "Cannot open temporary file.";
422 state->extrapage = 0;
424 header_main.bytes = state->mainlen;
425 header_main.packet = state->mainbuf;
426 header_main.b_o_s = 1;
427 header_main.e_o_s = 0;
428 header_main.granulepos = 0;
430 header_codebooks.bytes = state->booklen;
431 header_codebooks.packet = state->bookbuf;
432 header_codebooks.b_o_s = 0;
433 header_codebooks.e_o_s = 0;
434 header_codebooks.granulepos = 0;
436 ogg_stream_init (&streamout, state->serial);
438 _commentheader_out (state->vc, state->vendor, &header_comments);
440 ogg_stream_packetin (&streamout, &header_main);
441 ogg_stream_packetin (&streamout, &header_comments);
442 ogg_stream_packetin (&streamout, &header_codebooks);
444 while ((result = ogg_stream_flush (&streamout, &ogout))) {
445 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
446 if (tmp != (size_t) ogout.header_len)
449 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
450 if (tmp != (size_t) ogout.body_len)
454 while (_fetch_next_packet (state, &op, &ogin)) {
457 size = _blocksize (state, &op);
461 if (ogg_stream_flush (&streamout, &ogout)) {
462 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
463 if (tmp != (size_t) ogout.header_len)
466 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
467 if (tmp != (size_t) ogout.body_len)
470 } else if (needout) {
471 if (ogg_stream_pageout (&streamout, &ogout)) {
472 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
473 if (tmp != (size_t) ogout.header_len)
476 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
477 if (tmp != (size_t) ogout.body_len)
482 needflush = needout = 0;
484 if (op.granulepos == -1) {
485 op.granulepos = granpos;
486 ogg_stream_packetin (&streamout, &op);
488 /* granulepos is set, validly. Use it, and force a flush to
489 * account for shortened blocks (vcut) when appropriate
491 if (granpos > op.granulepos) {
492 granpos = op.granulepos;
493 ogg_stream_packetin (&streamout, &op);
496 ogg_stream_packetin (&streamout, &op);
504 while (ogg_stream_flush (&streamout, &ogout)) {
505 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
506 if (tmp != (size_t) ogout.header_len)
509 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
510 if (tmp != (size_t) ogout.body_len)
514 if (state->extrapage) {
515 tmp = fwrite (ogin.header, 1, ogin.header_len, out);
516 if (tmp != (size_t) ogin.header_len)
519 tmp = fwrite (ogin.body, 1, ogin.body_len, out);
520 if (tmp != (size_t) ogin.body_len)
524 /* clear it, because not all paths to here do */
527 while (!state->eosin) { /* We reached eos, not eof */
528 /* We copy the rest of the stream (other logical streams)
529 * through, a page at a time.
532 result = ogg_sync_pageout (state->oy, &ogout);
538 state->lasterror = "Corrupt or missing data, continuing...";
540 /* Don't bother going through the rest, we can just
541 * write the page out now
543 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
544 if (tmp != (size_t) ogout.header_len)
547 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
548 if (tmp != (size_t) ogout.body_len)
553 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
554 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
555 ogg_sync_wrote (state->oy, bytes);
566 unlink (state->filename);
567 rename (tmpfile, state->filename);
570 ogg_stream_clear (&streamout);
572 /* We don't ogg_packet_clear() this, because the memory was
573 * allocated in _commentheader_out(), so we mirror that here
575 _ogg_free (header_comments.packet);
577 free (state->mainbuf);
578 free (state->bookbuf);
580 state->mainbuf = state->bookbuf = NULL;
583 state->lasterror = "Error writing stream to output. "
584 "Output stream may be corrupted or truncated.";
588 vcedit_clear_internals (state);
590 state->in = fopen (state->filename, "rb");