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);
139 vcedit_state_ref (vcedit_state *state)
145 vcedit_state_unref (vcedit_state *state)
149 if (!state->refcount) {
151 vcedit_clear_internals (state);
156 /* Next two functions pulled straight from libvorbis, apart from one change
157 * - we don't want to overwrite the vendor string.
160 _v_writestring (oggpack_buffer *o, char *s, int len)
163 oggpack_write (o, *s++, 8);
168 _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
174 oggpack_writeinit (&opb);
177 oggpack_write (&opb, 0x03, 8);
178 _v_writestring (&opb, "vorbis", 6);
181 oggpack_write (&opb, strlen (vendor), 32);
182 _v_writestring (&opb, vendor, strlen (vendor));
185 oggpack_write (&opb, vc->comments, 32);
187 for (i = 0; i < vc->comments; i++) {
188 if (!vc->user_comments[i])
189 oggpack_write (&opb, 0, 32);
191 oggpack_write (&opb, vc->comment_lengths[i], 32);
192 _v_writestring (&opb, vc->user_comments[i],
193 vc->comment_lengths[i]);
197 oggpack_write (&opb, 1, 1);
199 op->packet = _ogg_malloc (oggpack_bytes (&opb));
200 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
202 op->bytes = oggpack_bytes (&opb);
207 oggpack_writeclear (&opb);
213 _blocksize (vcedit_state *s, ogg_packet *p)
217 this = vorbis_packet_blocksize (s->vi, p);
220 ret = (this + s->prevW) / 4;
228 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
233 result = ogg_stream_packetout (s->os, p);
241 while (ogg_sync_pageout (s->oy, page) <= 0) {
242 buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
243 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
244 ogg_sync_wrote (s->oy, bytes);
250 if (ogg_page_eos (page))
252 else if (ogg_page_serialno (page) != s->serial) {
258 ogg_stream_pagein (s->os, page);
260 return _fetch_next_packet (s, p, page);
264 vcedit_open (vcedit_state *state)
270 ogg_packet header_main, header_comments, header_codebooks;
273 state->oy = malloc (sizeof (ogg_sync_state));
274 ogg_sync_init (state->oy);
277 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
278 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
280 ogg_sync_wrote (state->oy, bytes);
282 if (ogg_sync_pageout (state->oy, &og) == 1)
285 /* Bail if we don't find data in the first 40 kB */
286 if (chunks++ >= 10) {
287 if (bytes < CHUNKSIZE)
288 state->lasterror = "Input truncated or empty.";
290 state->lasterror = "Input is not an Ogg bitstream.";
296 state->serial = ogg_page_serialno (&og);
298 state->os = malloc (sizeof (ogg_stream_state));
299 ogg_stream_init (state->os, state->serial);
301 state->vi = malloc (sizeof (vorbis_info));
302 vorbis_info_init (state->vi);
304 state->vc = malloc (sizeof (vorbis_comment));
305 vorbis_comment_init (state->vc);
307 if (ogg_stream_pagein (state->os, &og) < 0) {
308 state->lasterror = "Error reading first page of Ogg bitstream.";
312 if (ogg_stream_packetout (state->os, &header_main) != 1) {
313 state->lasterror = "Error reading initial header packet.";
317 if (vorbis_synthesis_headerin (state->vi, state->vc, &header_main) < 0) {
318 state->lasterror = "Ogg bitstream does not contain vorbis data.";
322 state->mainlen = header_main.bytes;
323 state->mainbuf = malloc (state->mainlen);
324 memcpy (state->mainbuf, header_main.packet, header_main.bytes);
327 header = &header_comments;
331 int result = ogg_sync_pageout (state->oy, &og);
334 break; /* Too little data so far */
337 ogg_stream_pagein (state->os, &og);
340 result = ogg_stream_packetout (state->os, header);
346 state->lasterror = "Corrupt secondary header.";
350 vorbis_synthesis_headerin (state->vi, state->vc, header);
353 state->booklen = header->bytes;
354 state->bookbuf = malloc (state->booklen);
355 memcpy (state->bookbuf, header->packet, header->bytes);
359 header = &header_codebooks;
364 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
365 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
367 if (bytes == 0 && i < 2) {
368 state->lasterror = "EOF before end of vorbis headers.";
372 ogg_sync_wrote (state->oy, bytes);
375 /* Copy the vendor tag */
376 state->vendor = strdup (state->vc->vendor);
378 /* Headers are done! */
382 vcedit_clear_internals (state);
388 vcedit_write (vcedit_state *state)
390 ogg_stream_state streamout;
391 ogg_packet header_main, header_comments, header_codebooks, op;
392 ogg_page ogout, ogin;
393 ogg_int64_t granpos = 0;
395 char *buffer, tmpfile[PATH_MAX];
396 int s, result, bytes, needflush = 0, needout = 0;
399 strcpy (tmpfile, state->filename);
400 strcat (tmpfile, ".XXXXXX");
402 s = mkstemp (tmpfile);
404 state->lasterror = "Error writing stream to output. "
405 "Cannot open temporary file.";
409 out = fdopen (s, "wb");
413 state->lasterror = "Error writing stream to output. "
414 "Cannot open temporary file.";
418 state->prevW = state->extrapage = state->eosin = 0;
420 header_main.bytes = state->mainlen;
421 header_main.packet = state->mainbuf;
422 header_main.b_o_s = 1;
423 header_main.e_o_s = 0;
424 header_main.granulepos = 0;
426 header_codebooks.bytes = state->booklen;
427 header_codebooks.packet = state->bookbuf;
428 header_codebooks.b_o_s = 0;
429 header_codebooks.e_o_s = 0;
430 header_codebooks.granulepos = 0;
432 ogg_stream_init (&streamout, state->serial);
434 _commentheader_out (state->vc, state->vendor, &header_comments);
436 ogg_stream_packetin (&streamout, &header_main);
437 ogg_stream_packetin (&streamout, &header_comments);
438 ogg_stream_packetin (&streamout, &header_codebooks);
440 while ((result = ogg_stream_flush (&streamout, &ogout))) {
441 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
442 if (tmp != (size_t) ogout.header_len)
445 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
446 if (tmp != (size_t) ogout.body_len)
450 while (_fetch_next_packet (state, &op, &ogin)) {
453 size = _blocksize (state, &op);
457 if (ogg_stream_flush (&streamout, &ogout)) {
458 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
459 if (tmp != (size_t) ogout.header_len)
462 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
463 if (tmp != (size_t) ogout.body_len)
466 } else if (needout) {
467 if (ogg_stream_pageout (&streamout, &ogout)) {
468 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
469 if (tmp != (size_t) ogout.header_len)
472 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
473 if (tmp != (size_t) ogout.body_len)
478 needflush = needout = 0;
480 if (op.granulepos == -1) {
481 op.granulepos = granpos;
482 ogg_stream_packetin (&streamout, &op);
484 /* granulepos is set, validly. Use it, and force a flush to
485 * account for shortened blocks (vcut) when appropriate
487 if (granpos > op.granulepos) {
488 granpos = op.granulepos;
489 ogg_stream_packetin (&streamout, &op);
492 ogg_stream_packetin (&streamout, &op);
500 while (ogg_stream_flush (&streamout, &ogout)) {
501 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
502 if (tmp != (size_t) ogout.header_len)
505 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
506 if (tmp != (size_t) ogout.body_len)
510 if (state->extrapage) {
511 tmp = fwrite (ogin.header, 1, ogin.header_len, out);
512 if (tmp != (size_t) ogin.header_len)
515 tmp = fwrite (ogin.body, 1, ogin.body_len, out);
516 if (tmp != (size_t) ogin.body_len)
520 /* clear it, because not all paths to here do */
523 while (!state->eosin) { /* We reached eos, not eof */
524 /* We copy the rest of the stream (other logical streams)
525 * through, a page at a time.
528 result = ogg_sync_pageout (state->oy, &ogout);
534 state->lasterror = "Corrupt or missing data, continuing...";
536 /* Don't bother going through the rest, we can just
537 * write the page out now
539 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
540 if (tmp != (size_t) ogout.header_len)
543 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
544 if (tmp != (size_t) ogout.body_len)
549 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
550 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
551 ogg_sync_wrote (state->oy, bytes);
562 unlink (state->filename);
563 rename (tmpfile, state->filename);
566 ogg_stream_clear (&streamout);
568 /* We don't ogg_packet_clear() this, because the memory was
569 * allocated in _commentheader_out(), so we mirror that here
571 _ogg_free (header_comments.packet);
573 free (state->mainbuf);
574 free (state->bookbuf);
576 state->mainbuf = state->bookbuf = NULL;
579 state->lasterror = "Error writing stream to output. "
580 "Output stream may be corrupted or truncated.";
584 vcedit_clear_internals (state);
586 state->in = fopen (state->filename, "rb");