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 static int vcedit_open (vcedit_state *state);
36 struct vcedit_state_St {
45 char filename[PATH_MAX];
49 unsigned char *mainbuf;
50 unsigned char *bookbuf;
53 const char *lasterror;
61 vcedit_state_free (vcedit_state *state)
67 free (state->mainbuf);
68 free (state->bookbuf);
80 vcedit_state_init (vcedit_state *state)
84 state->oy = malloc (sizeof (ogg_sync_state));
88 state->os = malloc (sizeof (ogg_stream_state));
92 state->vc = malloc (sizeof (vorbis_comment));
96 state->vi = malloc (sizeof (vorbis_info));
104 vcedit_state_new (const char *filename)
108 state = malloc (sizeof (vcedit_state));
112 memset (state, 0, sizeof (vcedit_state));
114 if (!vcedit_state_init (state)) {
115 vcedit_state_free (state);
119 snprintf (state->filename, sizeof (state->filename),
122 state->in = fopen (state->filename, "rb");
124 if (vcedit_open (state) < 0) {
125 vcedit_state_free (state);
133 vcedit_error (vcedit_state *state)
135 return state->lasterror;
139 vcedit_comments (vcedit_state *state)
145 vcedit_clear_internals (vcedit_state *state)
147 ogg_stream_clear (state->os);
148 ogg_sync_clear (state->oy);
150 vorbis_info_clear (state->vi);
151 vorbis_comment_clear (state->vc);
153 free (state->vendor);
154 state->vendor = NULL;
156 free (state->mainbuf);
157 state->mainbuf = NULL;
160 free (state->bookbuf);
161 state->bookbuf = NULL;
168 vcedit_state_ref (vcedit_state *state)
174 vcedit_state_unref (vcedit_state *state)
176 if (--state->refcount)
179 vcedit_clear_internals (state);
180 vcedit_state_free (state);
183 /* Next two functions pulled straight from libvorbis, apart from one change
184 * - we don't want to overwrite the vendor string.
187 _v_writestring (oggpack_buffer *o, char *s, int len)
190 oggpack_write (o, *s++, 8);
195 _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
201 oggpack_writeinit (&opb);
204 oggpack_write (&opb, 0x03, 8);
205 _v_writestring (&opb, "vorbis", 6);
208 oggpack_write (&opb, strlen (vendor), 32);
209 _v_writestring (&opb, vendor, strlen (vendor));
212 oggpack_write (&opb, vc->comments, 32);
214 for (i = 0; i < vc->comments; i++) {
215 if (!vc->user_comments[i])
216 oggpack_write (&opb, 0, 32);
218 oggpack_write (&opb, vc->comment_lengths[i], 32);
219 _v_writestring (&opb, vc->user_comments[i],
220 vc->comment_lengths[i]);
224 oggpack_write (&opb, 1, 1);
226 op->packet = _ogg_malloc (oggpack_bytes (&opb));
227 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
229 op->bytes = oggpack_bytes (&opb);
234 oggpack_writeclear (&opb);
240 _blocksize (vcedit_state *s, ogg_packet *p)
244 this = vorbis_packet_blocksize (s->vi, p);
247 ret = (this + s->prevW) / 4;
255 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
260 result = ogg_stream_packetout (s->os, p);
268 while (ogg_sync_pageout (s->oy, page) <= 0) {
269 buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
270 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
271 ogg_sync_wrote (s->oy, bytes);
277 if (ogg_page_eos (page))
279 else if (ogg_page_serialno (page) != s->serial) {
285 ogg_stream_pagein (s->os, page);
287 return _fetch_next_packet (s, p, page);
291 vcedit_open (vcedit_state *state)
297 ogg_packet header_main, header_comments, header_codebooks;
300 ogg_sync_init (state->oy);
303 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
304 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
306 ogg_sync_wrote (state->oy, bytes);
308 if (ogg_sync_pageout (state->oy, &og) == 1)
311 /* Bail if we don't find data in the first 40 kB */
312 if (chunks++ >= 10) {
313 if (bytes < CHUNKSIZE)
314 state->lasterror = "Input truncated or empty.";
316 state->lasterror = "Input is not an Ogg bitstream.";
322 state->serial = ogg_page_serialno (&og);
324 ogg_stream_init (state->os, state->serial);
325 vorbis_info_init (state->vi);
326 vorbis_comment_init (state->vc);
328 if (ogg_stream_pagein (state->os, &og) < 0) {
329 state->lasterror = "Error reading first page of Ogg bitstream.";
333 if (ogg_stream_packetout (state->os, &header_main) != 1) {
334 state->lasterror = "Error reading initial header packet.";
338 if (vorbis_synthesis_headerin (state->vi, state->vc, &header_main) < 0) {
339 state->lasterror = "Ogg bitstream does not contain vorbis data.";
343 state->mainlen = header_main.bytes;
344 state->mainbuf = malloc (state->mainlen);
345 memcpy (state->mainbuf, header_main.packet, header_main.bytes);
348 header = &header_comments;
352 int result = ogg_sync_pageout (state->oy, &og);
355 break; /* Too little data so far */
358 ogg_stream_pagein (state->os, &og);
361 result = ogg_stream_packetout (state->os, header);
367 state->lasterror = "Corrupt secondary header.";
371 vorbis_synthesis_headerin (state->vi, state->vc, header);
374 state->booklen = header->bytes;
375 state->bookbuf = malloc (state->booklen);
376 memcpy (state->bookbuf, header->packet, header->bytes);
380 header = &header_codebooks;
385 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
386 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
388 if (bytes == 0 && i < 2) {
389 state->lasterror = "EOF before end of vorbis headers.";
393 ogg_sync_wrote (state->oy, bytes);
396 /* Copy the vendor tag */
397 state->vendor = strdup (state->vc->vendor);
399 /* Headers are done! */
403 vcedit_clear_internals (state);
409 vcedit_write (vcedit_state *state)
411 ogg_stream_state streamout;
412 ogg_packet header_main, header_comments, header_codebooks, op;
413 ogg_page ogout, ogin;
414 ogg_int64_t granpos = 0;
416 char *buffer, tmpfile[PATH_MAX];
417 int s, result, bytes, needflush = 0, needout = 0;
420 strcpy (tmpfile, state->filename);
421 strcat (tmpfile, ".XXXXXX");
423 s = mkstemp (tmpfile);
425 state->lasterror = "Error writing stream to output. "
426 "Cannot open temporary file.";
430 out = fdopen (s, "wb");
434 state->lasterror = "Error writing stream to output. "
435 "Cannot open temporary file.";
439 state->prevW = state->extrapage = state->eosin = 0;
441 header_main.bytes = state->mainlen;
442 header_main.packet = state->mainbuf;
443 header_main.b_o_s = 1;
444 header_main.e_o_s = 0;
445 header_main.granulepos = 0;
447 header_codebooks.bytes = state->booklen;
448 header_codebooks.packet = state->bookbuf;
449 header_codebooks.b_o_s = 0;
450 header_codebooks.e_o_s = 0;
451 header_codebooks.granulepos = 0;
453 ogg_stream_init (&streamout, state->serial);
455 _commentheader_out (state->vc, state->vendor, &header_comments);
457 ogg_stream_packetin (&streamout, &header_main);
458 ogg_stream_packetin (&streamout, &header_comments);
459 ogg_stream_packetin (&streamout, &header_codebooks);
461 while ((result = 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)
471 while (_fetch_next_packet (state, &op, &ogin)) {
474 size = _blocksize (state, &op);
478 if (ogg_stream_flush (&streamout, &ogout)) {
479 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
480 if (tmp != (size_t) ogout.header_len)
483 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
484 if (tmp != (size_t) ogout.body_len)
487 } else if (needout) {
488 if (ogg_stream_pageout (&streamout, &ogout)) {
489 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
490 if (tmp != (size_t) ogout.header_len)
493 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
494 if (tmp != (size_t) ogout.body_len)
499 needflush = needout = 0;
501 if (op.granulepos == -1) {
502 op.granulepos = granpos;
503 ogg_stream_packetin (&streamout, &op);
505 /* granulepos is set, validly. Use it, and force a flush to
506 * account for shortened blocks (vcut) when appropriate
508 if (granpos > op.granulepos) {
509 granpos = op.granulepos;
510 ogg_stream_packetin (&streamout, &op);
513 ogg_stream_packetin (&streamout, &op);
521 while (ogg_stream_flush (&streamout, &ogout)) {
522 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
523 if (tmp != (size_t) ogout.header_len)
526 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
527 if (tmp != (size_t) ogout.body_len)
531 if (state->extrapage) {
532 tmp = fwrite (ogin.header, 1, ogin.header_len, out);
533 if (tmp != (size_t) ogin.header_len)
536 tmp = fwrite (ogin.body, 1, ogin.body_len, out);
537 if (tmp != (size_t) ogin.body_len)
541 /* clear it, because not all paths to here do */
544 while (!state->eosin) { /* We reached eos, not eof */
545 /* We copy the rest of the stream (other logical streams)
546 * through, a page at a time.
549 result = ogg_sync_pageout (state->oy, &ogout);
555 state->lasterror = "Corrupt or missing data, continuing...";
557 /* Don't bother going through the rest, we can just
558 * write the page out now
560 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
561 if (tmp != (size_t) ogout.header_len)
564 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
565 if (tmp != (size_t) ogout.body_len)
570 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
571 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
572 ogg_sync_wrote (state->oy, bytes);
583 unlink (state->filename);
584 rename (tmpfile, state->filename);
587 ogg_stream_clear (&streamout);
589 /* We don't ogg_packet_clear() this, because the memory was
590 * allocated in _commentheader_out(), so we mirror that here
592 _ogg_free (header_comments.packet);
594 free (state->mainbuf);
595 free (state->bookbuf);
597 state->mainbuf = state->bookbuf = NULL;
600 state->lasterror = "Error writing stream to output. "
601 "Output stream may be corrupted or truncated.";
605 vcedit_clear_internals (state);
607 state->in = fopen (state->filename, "rb");