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];
48 unsigned char *mainbuf;
49 unsigned char *bookbuf;
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_comments (vcedit_state *state)
126 return state->opened ? state->vc : NULL;
130 vcedit_clear_internals (vcedit_state *state)
132 ogg_stream_clear (state->os);
133 ogg_sync_clear (state->oy);
135 vorbis_info_clear (state->vi);
136 vorbis_comment_clear (state->vc);
138 free (state->vendor);
139 state->vendor = NULL;
141 free (state->mainbuf);
142 state->mainbuf = NULL;
145 free (state->bookbuf);
146 state->bookbuf = NULL;
150 state->opened = false;
154 vcedit_state_ref (vcedit_state *state)
160 vcedit_state_unref (vcedit_state *state)
162 if (--state->refcount)
166 vcedit_clear_internals (state);
168 vcedit_state_free (state);
171 /* Next two functions pulled straight from libvorbis, apart from one change
172 * - we don't want to overwrite the vendor string.
175 _v_writestring (oggpack_buffer *o, char *s, int len)
178 oggpack_write (o, *s++, 8);
183 _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
189 oggpack_writeinit (&opb);
192 oggpack_write (&opb, 0x03, 8);
193 _v_writestring (&opb, "vorbis", 6);
196 oggpack_write (&opb, strlen (vendor), 32);
197 _v_writestring (&opb, vendor, strlen (vendor));
200 oggpack_write (&opb, vc->comments, 32);
202 for (i = 0; i < vc->comments; i++) {
203 if (!vc->user_comments[i])
204 oggpack_write (&opb, 0, 32);
206 oggpack_write (&opb, vc->comment_lengths[i], 32);
207 _v_writestring (&opb, vc->user_comments[i],
208 vc->comment_lengths[i]);
212 oggpack_write (&opb, 1, 1);
214 op->packet = _ogg_malloc (oggpack_bytes (&opb));
215 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
217 op->bytes = oggpack_bytes (&opb);
222 oggpack_writeclear (&opb);
228 _blocksize (vcedit_state *s, ogg_packet *p)
232 this = vorbis_packet_blocksize (s->vi, p);
235 ret = (this + s->prevW) / 4;
243 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
248 result = ogg_stream_packetout (s->os, p);
256 while (ogg_sync_pageout (s->oy, page) <= 0) {
257 buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
258 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
259 ogg_sync_wrote (s->oy, bytes);
265 if (ogg_page_eos (page))
267 else if (ogg_page_serialno (page) != s->serial) {
273 ogg_stream_pagein (s->os, page);
275 return _fetch_next_packet (s, p, page);
279 vcedit_open (vcedit_state *state)
286 ogg_packet header_main, header_comments, header_codebooks;
289 state->in = fopen (state->filename, "rb");
291 return VCEDIT_ERR_OPEN;
293 ogg_sync_init (state->oy);
296 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
297 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
299 ogg_sync_wrote (state->oy, bytes);
301 if (ogg_sync_pageout (state->oy, &og) == 1)
304 /* Bail if we don't find data in the first 40 kB */
305 if (chunks++ >= 10) {
306 ogg_sync_clear (state->oy);
308 return VCEDIT_ERR_INVAL;
312 state->serial = ogg_page_serialno (&og);
314 ogg_stream_init (state->os, state->serial);
315 vorbis_info_init (state->vi);
316 vorbis_comment_init (state->vc);
318 if (ogg_stream_pagein (state->os, &og) < 0) {
319 ret = VCEDIT_ERR_INVAL;
323 if (ogg_stream_packetout (state->os, &header_main) != 1) {
324 ret = VCEDIT_ERR_INVAL;
328 if (vorbis_synthesis_headerin (state->vi, state->vc, &header_main) < 0) {
329 ret = VCEDIT_ERR_INVAL;
333 state->mainlen = header_main.bytes;
334 state->mainbuf = malloc (state->mainlen);
335 memcpy (state->mainbuf, header_main.packet, header_main.bytes);
338 header = &header_comments;
342 int result = ogg_sync_pageout (state->oy, &og);
345 break; /* Too little data so far */
348 ogg_stream_pagein (state->os, &og);
351 result = ogg_stream_packetout (state->os, header);
357 ret = VCEDIT_ERR_INVAL;
361 vorbis_synthesis_headerin (state->vi, state->vc, header);
364 state->booklen = header->bytes;
365 state->bookbuf = malloc (state->booklen);
366 memcpy (state->bookbuf, header->packet, header->bytes);
370 header = &header_codebooks;
375 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
376 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
378 if (bytes == 0 && i < 2) {
379 ret = VCEDIT_ERR_INVAL;
383 ogg_sync_wrote (state->oy, bytes);
386 /* Copy the vendor tag */
387 state->vendor = strdup (state->vc->vendor);
389 /* Headers are done! */
390 state->opened = true;
392 return VCEDIT_ERR_SUCCESS;
395 vcedit_clear_internals (state);
401 vcedit_write (vcedit_state *state)
403 ogg_stream_state streamout;
404 ogg_packet header_main, header_comments, header_codebooks, op;
405 ogg_page ogout, ogin;
406 ogg_int64_t granpos = 0;
408 char *buffer, tmpfile[PATH_MAX];
409 int s, result, bytes, needflush = 0, needout = 0;
413 return VCEDIT_ERR_INVAL;
415 strcpy (tmpfile, state->filename);
416 strcat (tmpfile, ".XXXXXX");
418 s = mkstemp (tmpfile);
420 return VCEDIT_ERR_TMPFILE;
422 out = fdopen (s, "wb");
427 return VCEDIT_ERR_TMPFILE;
430 state->prevW = state->extrapage = state->eosin = 0;
432 header_main.bytes = state->mainlen;
433 header_main.packet = state->mainbuf;
434 header_main.b_o_s = 1;
435 header_main.e_o_s = 0;
436 header_main.granulepos = 0;
438 header_codebooks.bytes = state->booklen;
439 header_codebooks.packet = state->bookbuf;
440 header_codebooks.b_o_s = 0;
441 header_codebooks.e_o_s = 0;
442 header_codebooks.granulepos = 0;
444 ogg_stream_init (&streamout, state->serial);
446 _commentheader_out (state->vc, state->vendor, &header_comments);
448 ogg_stream_packetin (&streamout, &header_main);
449 ogg_stream_packetin (&streamout, &header_comments);
450 ogg_stream_packetin (&streamout, &header_codebooks);
452 while ((result = 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)
462 while (_fetch_next_packet (state, &op, &ogin)) {
465 size = _blocksize (state, &op);
469 if (ogg_stream_flush (&streamout, &ogout)) {
470 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
471 if (tmp != (size_t) ogout.header_len)
474 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
475 if (tmp != (size_t) ogout.body_len)
478 } else if (needout) {
479 if (ogg_stream_pageout (&streamout, &ogout)) {
480 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
481 if (tmp != (size_t) ogout.header_len)
484 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
485 if (tmp != (size_t) ogout.body_len)
490 needflush = needout = 0;
492 if (op.granulepos == -1) {
493 op.granulepos = granpos;
494 ogg_stream_packetin (&streamout, &op);
496 /* granulepos is set, validly. Use it, and force a flush to
497 * account for shortened blocks (vcut) when appropriate
499 if (granpos > op.granulepos) {
500 granpos = op.granulepos;
501 ogg_stream_packetin (&streamout, &op);
504 ogg_stream_packetin (&streamout, &op);
512 while (ogg_stream_flush (&streamout, &ogout)) {
513 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
514 if (tmp != (size_t) ogout.header_len)
517 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
518 if (tmp != (size_t) ogout.body_len)
522 if (state->extrapage) {
523 tmp = fwrite (ogin.header, 1, ogin.header_len, out);
524 if (tmp != (size_t) ogin.header_len)
527 tmp = fwrite (ogin.body, 1, ogin.body_len, out);
528 if (tmp != (size_t) ogin.body_len)
532 /* clear it, because not all paths to here do */
535 while (!state->eosin) { /* We reached eos, not eof */
536 /* We copy the rest of the stream (other logical streams)
537 * through, a page at a time.
540 result = ogg_sync_pageout (state->oy, &ogout);
546 /* Don't bother going through the rest, we can just
547 * write the page out now
549 tmp = fwrite (ogout.header, 1, ogout.header_len, out);
550 if (tmp != (size_t) ogout.header_len)
553 tmp = fwrite (ogout.body, 1, ogout.body_len, out);
554 if (tmp != (size_t) ogout.body_len)
559 buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
560 bytes = fread (buffer, 1, CHUNKSIZE, state->in);
561 ogg_sync_wrote (state->oy, bytes);
572 unlink (state->filename);
573 rename (tmpfile, state->filename);
576 ogg_stream_clear (&streamout);
578 /* We don't ogg_packet_clear() this, because the memory was
579 * allocated in _commentheader_out(), so we mirror that here
581 _ogg_free (header_comments.packet);
583 free (state->mainbuf);
584 free (state->bookbuf);
586 state->mainbuf = state->bookbuf = NULL;
589 return VCEDIT_ERR_INVAL;
591 vcedit_clear_internals (state);
593 return (vcedit_open (state) == VCEDIT_ERR_SUCCESS) ?
594 VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;