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 {
46 unsigned char *mainbuf;
47 unsigned char *bookbuf;
59 vcedit_state_free (vcedit_state *s)
74 vcedit_state_init (vcedit_state *s, const char *filename)
78 strcpy (s->filename, filename);
84 vcedit_state_new (const char *filename)
89 len = strlen (filename);
93 s= malloc (sizeof (vcedit_state) + len + 1);
97 memset (s, 0, sizeof (vcedit_state));
99 if (!vcedit_state_init (s, filename)) {
100 vcedit_state_free (s);
108 vcedit_comments (vcedit_state *s)
110 return s->opened ? &s->vc : NULL;
114 vcedit_clear_internals (vcedit_state *s)
116 ogg_stream_clear (&s->os);
117 ogg_sync_clear (&s->oy);
119 vorbis_info_clear (&s->vi);
120 vorbis_comment_clear (&s->vc);
138 vcedit_state_ref (vcedit_state *s)
144 vcedit_state_unref (vcedit_state *s)
150 vcedit_clear_internals (s);
152 vcedit_state_free (s);
155 /* Next two functions pulled straight from libvorbis, apart from one change
156 * - we don't want to overwrite the vendor string.
159 _v_writestring (oggpack_buffer *o, char *s, int len)
162 oggpack_write (o, *s++, 8);
166 _commentheader_out (vcedit_state *s, ogg_packet *op)
172 oggpack_writeinit (&opb);
175 oggpack_write (&opb, 0x03, 8);
176 _v_writestring (&opb, "vorbis", 6);
179 len = strlen (s->vendor);
180 oggpack_write (&opb, len, 32);
181 _v_writestring (&opb, s->vendor, len);
184 oggpack_write (&opb, s->vc.comments, 32);
186 for (i = 0; i < s->vc.comments; i++) {
187 if (!s->vc.user_comments[i])
188 oggpack_write (&opb, 0, 32);
190 oggpack_write (&opb, s->vc.comment_lengths[i], 32);
191 _v_writestring (&opb, s->vc.user_comments[i],
192 s->vc.comment_lengths[i]);
196 oggpack_write (&opb, 1, 1);
198 op->packet = _ogg_malloc (oggpack_bytes (&opb));
199 memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
201 op->bytes = oggpack_bytes (&opb);
206 oggpack_writeclear (&opb);
212 _blocksize (vcedit_state *s, ogg_packet *p)
216 this = vorbis_packet_blocksize (&s->vi, p);
219 ret = (this + s->prevW) / 4;
227 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
232 result = ogg_stream_packetout (&s->os, p);
239 while (ogg_sync_pageout (&s->oy, page) != 1) {
240 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
241 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
242 ogg_sync_wrote (&s->oy, bytes);
244 if (!bytes && (feof (s->in) || ferror (s->in)))
248 if (ogg_page_eos (page))
250 else if (ogg_page_serialno (page) != s->serial) {
256 ogg_stream_pagein (&s->os, page);
258 return _fetch_next_packet (s, p, page);
262 vcedit_open (vcedit_state *s)
266 ogg_packet header_main, header_comments, header_codebooks;
269 size_t bytes, total = 0;
272 s->in = fopen (s->filename, "rb");
274 return VCEDIT_ERR_OPEN;
276 ogg_sync_init (&s->oy);
279 /* Bail if we don't find data in the first 40 kB */
280 if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) {
281 ogg_sync_clear (&s->oy);
283 return VCEDIT_ERR_INVAL;
286 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
288 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
291 ogg_sync_wrote (&s->oy, bytes);
292 } while (ogg_sync_pageout (&s->oy, &og) != 1);
294 s->serial = ogg_page_serialno (&og);
296 ogg_stream_init (&s->os, s->serial);
297 vorbis_info_init (&s->vi);
298 vorbis_comment_init (&s->vc);
300 if (ogg_stream_pagein (&s->os, &og) < 0) {
301 ret = VCEDIT_ERR_INVAL;
305 if (ogg_stream_packetout (&s->os, &header_main) != 1) {
306 ret = VCEDIT_ERR_INVAL;
310 if (vorbis_synthesis_headerin (&s->vi, &s->vc, &header_main) < 0) {
311 ret = VCEDIT_ERR_INVAL;
315 s->mainlen = header_main.bytes;
316 s->mainbuf = malloc (s->mainlen);
317 memcpy (s->mainbuf, header_main.packet, header_main.bytes);
319 header = &header_comments;
322 if (feof (s->in) || ferror (s->in)) {
323 ret = VCEDIT_ERR_INVAL;
330 result = ogg_sync_pageout (&s->oy, &og);
332 break; /* Too little data so far */
337 ogg_stream_pagein (&s->os, &og);
340 result = ogg_stream_packetout (&s->os, header);
345 ret = VCEDIT_ERR_INVAL;
349 vorbis_synthesis_headerin (&s->vi, &s->vc, header);
352 s->booklen = header->bytes;
353 s->bookbuf = malloc (s->booklen);
354 memcpy (s->bookbuf, header->packet, header->bytes);
357 header = &header_codebooks;
361 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
363 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
364 ogg_sync_wrote (&s->oy, bytes);
367 /* Copy the vendor tag */
368 s->vendor = strdup (s->vc.vendor);
370 /* Headers are done! */
373 return VCEDIT_ERR_SUCCESS;
376 vcedit_clear_internals (s);
382 write_data (const void *buf, size_t size, size_t nmemb, FILE *stream)
387 w = fwrite (buf, size, nmemb, stream);
388 if (!w && ferror (stream))
399 vcedit_write (vcedit_state *s)
401 ogg_stream_state streamout;
402 ogg_packet header_main, header_comments, header_codebooks, op;
403 ogg_page ogout, ogin;
404 ogg_int64_t granpos = 0;
406 char *buffer, tmpfile[PATH_MAX];
407 bool success = false;
408 int fd, result, bytes, needflush = 0, needout = 0;
411 return VCEDIT_ERR_INVAL;
413 strcpy (tmpfile, s->filename);
414 strcat (tmpfile, ".XXXXXX");
416 fd = mkstemp (tmpfile);
418 return VCEDIT_ERR_TMPFILE;
420 out = fdopen (fd, "wb");
425 return VCEDIT_ERR_TMPFILE;
428 s->prevW = s->extrapage = s->eosin = 0;
430 header_main.bytes = s->mainlen;
431 header_main.packet = s->mainbuf;
432 header_main.b_o_s = 1;
433 header_main.e_o_s = 0;
434 header_main.granulepos = 0;
436 header_codebooks.bytes = s->booklen;
437 header_codebooks.packet = s->bookbuf;
438 header_codebooks.b_o_s = 0;
439 header_codebooks.e_o_s = 0;
440 header_codebooks.granulepos = 0;
442 ogg_stream_init (&streamout, s->serial);
444 _commentheader_out (s, &header_comments);
446 ogg_stream_packetin (&streamout, &header_main);
447 ogg_stream_packetin (&streamout, &header_comments);
448 ogg_stream_packetin (&streamout, &header_codebooks);
450 while ((result = ogg_stream_flush (&streamout, &ogout))) {
451 if (!write_data (ogout.header, 1, ogout.header_len, out))
454 if (!write_data (ogout.body, 1, ogout.body_len, out))
458 while (_fetch_next_packet (s, &op, &ogin)) {
461 size = _blocksize (s, &op);
465 if (ogg_stream_flush (&streamout, &ogout)) {
466 if (!write_data (ogout.header, 1, ogout.header_len, out))
469 if (!write_data (ogout.body, 1, ogout.body_len, out))
472 } else if (needout) {
473 if (ogg_stream_pageout (&streamout, &ogout)) {
474 if (!write_data (ogout.header, 1, ogout.header_len, out))
477 if (!write_data (ogout.body, 1, ogout.body_len, out))
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 if (!write_data (ogout.header, 1, ogout.header_len, out))
508 if (!write_data (ogout.body, 1, ogout.body_len, out))
513 if (!write_data (ogin.header, 1, ogin.header_len, out))
516 if (!write_data (ogin.body, 1, ogin.body_len, out))
520 /* clear it, because not all paths to here do */
524 /* We copy the rest of the stream (other logical streams)
525 * through, a page at a time.
527 while ((result = ogg_sync_pageout (&s->oy, &ogout))) {
531 /* Don't bother going through the rest, we can just
532 * write the page out now
534 if (!write_data (ogout.header, 1, ogout.header_len, out))
537 if (!write_data (ogout.body, 1, ogout.body_len, out))
541 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
542 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
543 ogg_sync_wrote (&s->oy, bytes);
548 s->eosin = !bytes && feof (s->in);
561 unlink (s->filename);
562 rename (tmpfile, s->filename);
565 ogg_stream_clear (&streamout);
567 /* We don't ogg_packet_clear() this, because the memory was
568 * allocated in _commentheader_out(), so we mirror that here
570 _ogg_free (header_comments.packet);
575 s->mainbuf = s->bookbuf = NULL;
578 return VCEDIT_ERR_INVAL;
580 vcedit_clear_internals (s);
582 return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ?
583 VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;