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 {
43 char filename[PATH_MAX];
47 unsigned char *mainbuf;
48 unsigned char *bookbuf;
51 const char *lasterror;
59 vcedit_state_free (vcedit_state *state)
65 free (state->mainbuf);
66 free (state->bookbuf);
78 vcedit_state_init (vcedit_state *state)
82 state->oy = malloc (sizeof (ogg_sync_state));
86 state->os = malloc (sizeof (ogg_stream_state));
90 state->vc = malloc (sizeof (vorbis_comment));
94 state->vi = malloc (sizeof (vorbis_info));
102 vcedit_state_new (const char *filename)
106 state = malloc (sizeof (vcedit_state));
110 memset (state, 0, sizeof (vcedit_state));
112 if (!vcedit_state_init (state)) {
113 vcedit_state_free (state);
117 snprintf (state->filename, sizeof (state->filename),
124 vcedit_error (vcedit_state *state)
126 return state->lasterror;
130 vcedit_comments (vcedit_state *state)
136 vcedit_clear_internals (vcedit_state *state)
138 ogg_stream_clear (state->os);
139 ogg_sync_clear (state->oy);
141 vorbis_info_clear (state->vi);
142 vorbis_comment_clear (state->vc);
144 free (state->vendor);
145 state->vendor = NULL;
147 free (state->mainbuf);
148 state->mainbuf = NULL;
151 free (state->bookbuf);
152 state->bookbuf = NULL;
159 vcedit_state_ref (vcedit_state *state)
165 vcedit_state_unref (vcedit_state *state)
167 if (--state->refcount)
170 vcedit_clear_internals (state);
171 vcedit_state_free (state);
174 /* Next two functions pulled straight from libvorbis, apart from one change
175 * - we don't want to overwrite the vendor string.
178 _v_writestring (oggpack_buffer *o, char *s, int len)
181 oggpack_write (o, *s++, 8);
186 _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
192 oggpack_writeinit (&opb);
195 oggpack_write (&opb, 0x03, 8);
196 _v_writestring (&opb, "vorbis", 6);
199 oggpack_write (&opb, strlen (vendor), 32);
200 _v_writestring (&opb, vendor, strlen (vendor));
203 oggpack_write (&opb, vc->comments, 32);
205 for (i = 0; i < vc->comments; i++) {
206 if (!vc->user_comments[i])
207 oggpack_write (&opb, 0, 32);
209 oggpack_write (&opb, vc->comment_lengths[i], 32);
210 _v_writestring (&opb, vc->user_comments[i],
211 vc->comment_lengths[i]);
215 oggpack_write (&opb, 1, 1);
217 op->packet = _ogg_malloc (oggpack_bytes (&opb));
218 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
220 op->bytes = oggpack_bytes (&opb);
225 oggpack_writeclear (&opb);
231 _blocksize (vcedit_state *s, ogg_packet *p)
235 this = vorbis_packet_blocksize (s->vi, p);
238 ret = (this + s->prevW) / 4;
246 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
251 result = ogg_stream_packetout (s->os, p);
259 while (ogg_sync_pageout (s->oy, page) <= 0) {
260 buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
261 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
262 ogg_sync_wrote (s->oy, bytes);
268 if (ogg_page_eos (page))
270 else if (ogg_page_serialno (page) != s->serial) {
276 ogg_stream_pagein (s->os, page);
278 return _fetch_next_packet (s, p, page);
282 vcedit_open (vcedit_state *state)
288 ogg_packet header_main, header_comments, header_codebooks;
291 state->in = fopen (state->filename, "rb");
293 state->lasterror = "Cannot open file.";
297 ogg_sync_init (state->oy);
300 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
301 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
303 ogg_sync_wrote (state->oy, bytes);
305 if (ogg_sync_pageout (state->oy, &og) == 1)
308 /* Bail if we don't find data in the first 40 kB */
309 if (chunks++ >= 10) {
310 if (bytes < CHUNKSIZE)
311 state->lasterror = "Input truncated or empty.";
313 state->lasterror = "Input is not an Ogg bitstream.";
319 state->serial = ogg_page_serialno (&og);
321 ogg_stream_init (state->os, state->serial);
322 vorbis_info_init (state->vi);
323 vorbis_comment_init (state->vc);
325 if (ogg_stream_pagein (state->os, &og) < 0) {
326 state->lasterror = "Error reading first page of Ogg bitstream.";
330 if (ogg_stream_packetout (state->os, &header_main) != 1) {
331 state->lasterror = "Error reading initial header packet.";
335 if (vorbis_synthesis_headerin (state->vi, state->vc, &header_main) < 0) {
336 state->lasterror = "Ogg bitstream does not contain vorbis data.";
340 state->mainlen = header_main.bytes;
341 state->mainbuf = malloc (state->mainlen);
342 memcpy (state->mainbuf, header_main.packet, header_main.bytes);
345 header = &header_comments;
349 int result = ogg_sync_pageout (state->oy, &og);
352 break; /* Too little data so far */
355 ogg_stream_pagein (state->os, &og);
358 result = ogg_stream_packetout (state->os, header);
364 state->lasterror = "Corrupt secondary header.";
368 vorbis_synthesis_headerin (state->vi, state->vc, header);
371 state->booklen = header->bytes;
372 state->bookbuf = malloc (state->booklen);
373 memcpy (state->bookbuf, header->packet, header->bytes);
377 header = &header_codebooks;
382 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
383 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
385 if (bytes == 0 && i < 2) {
386 state->lasterror = "EOF before end of vorbis headers.";
390 ogg_sync_wrote (state->oy, bytes);
393 /* Copy the vendor tag */
394 state->vendor = strdup (state->vc->vendor);
396 /* Headers are done! */
400 vcedit_clear_internals (state);
406 vcedit_write (vcedit_state *state)
408 ogg_stream_state streamout;
409 ogg_packet header_main, header_comments, header_codebooks, op;
410 ogg_page ogout, ogin;
411 ogg_int64_t granpos = 0;
413 char *buffer, tmpfile[PATH_MAX];
414 int s, result, bytes, needflush = 0, needout = 0;
417 strcpy (tmpfile, state->filename);
418 strcat (tmpfile, ".XXXXXX");
420 s = mkstemp (tmpfile);
422 state->lasterror = "Error writing stream to output. "
423 "Cannot open temporary file.";
427 out = fdopen (s, "wb");
431 state->lasterror = "Error writing stream to output. "
432 "Cannot open temporary file.";
436 state->prevW = state->extrapage = state->eosin = 0;
438 header_main.bytes = state->mainlen;
439 header_main.packet = state->mainbuf;
440 header_main.b_o_s = 1;
441 header_main.e_o_s = 0;
442 header_main.granulepos = 0;
444 header_codebooks.bytes = state->booklen;
445 header_codebooks.packet = state->bookbuf;
446 header_codebooks.b_o_s = 0;
447 header_codebooks.e_o_s = 0;
448 header_codebooks.granulepos = 0;
450 ogg_stream_init (&streamout, state->serial);
452 _commentheader_out (state->vc, state->vendor, &header_comments);
454 ogg_stream_packetin (&streamout, &header_main);
455 ogg_stream_packetin (&streamout, &header_comments);
456 ogg_stream_packetin (&streamout, &header_codebooks);
458 while ((result = ogg_stream_flush (&streamout, &ogout))) {
459 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
460 if (tmp != (size_t) ogout.header_len)
463 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
464 if (tmp != (size_t) ogout.body_len)
468 while (_fetch_next_packet (state, &op, &ogin)) {
471 size = _blocksize (state, &op);
475 if (ogg_stream_flush (&streamout, &ogout)) {
476 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
477 if (tmp != (size_t) ogout.header_len)
480 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
481 if (tmp != (size_t) ogout.body_len)
484 } else if (needout) {
485 if (ogg_stream_pageout (&streamout, &ogout)) {
486 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
487 if (tmp != (size_t) ogout.header_len)
490 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
491 if (tmp != (size_t) ogout.body_len)
496 needflush = needout = 0;
498 if (op.granulepos == -1) {
499 op.granulepos = granpos;
500 ogg_stream_packetin (&streamout, &op);
502 /* granulepos is set, validly. Use it, and force a flush to
503 * account for shortened blocks (vcut) when appropriate
505 if (granpos > op.granulepos) {
506 granpos = op.granulepos;
507 ogg_stream_packetin (&streamout, &op);
510 ogg_stream_packetin (&streamout, &op);
518 while (ogg_stream_flush (&streamout, &ogout)) {
519 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
520 if (tmp != (size_t) ogout.header_len)
523 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
524 if (tmp != (size_t) ogout.body_len)
528 if (state->extrapage) {
529 tmp = fwrite (ogin.header, 1, ogin.header_len, out);
530 if (tmp != (size_t) ogin.header_len)
533 tmp = fwrite (ogin.body, 1, ogin.body_len, out);
534 if (tmp != (size_t) ogin.body_len)
538 /* clear it, because not all paths to here do */
541 while (!state->eosin) { /* We reached eos, not eof */
542 /* We copy the rest of the stream (other logical streams)
543 * through, a page at a time.
546 result = ogg_sync_pageout (state->oy, &ogout);
552 state->lasterror = "Corrupt or missing data, continuing...";
554 /* Don't bother going through the rest, we can just
555 * write the page out now
557 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
558 if (tmp != (size_t) ogout.header_len)
561 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
562 if (tmp != (size_t) ogout.body_len)
567 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
568 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
569 ogg_sync_wrote (state->oy, bytes);
580 unlink (state->filename);
581 rename (tmpfile, state->filename);
584 ogg_stream_clear (&streamout);
586 /* We don't ogg_packet_clear() this, because the memory was
587 * allocated in _commentheader_out(), so we mirror that here
589 _ogg_free (header_comments.packet);
591 free (state->mainbuf);
592 free (state->bookbuf);
594 state->mainbuf = state->bookbuf = NULL;
597 state->lasterror = "Error writing stream to output. "
598 "Output stream may be corrupted or truncated.";
602 vcedit_clear_internals (state);