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 <sys/types.h>
29 #include <vorbis/codec.h>
34 #define CHUNKSIZE 4096
36 struct vcedit_state_St {
50 unsigned char *mainbuf;
51 unsigned char *bookbuf;
63 vcedit_state_free (vcedit_state *s)
78 vcedit_state_init (vcedit_state *s, const char *filename)
82 strcpy (s->filename, filename);
88 vcedit_state_new (const char *filename)
93 len = strlen (filename);
97 s= malloc (sizeof (vcedit_state) + len + 1);
101 memset (s, 0, sizeof (vcedit_state));
103 if (!vcedit_state_init (s, filename)) {
104 vcedit_state_free (s);
112 vcedit_comments (vcedit_state *s)
114 return s->opened ? &s->vc : NULL;
118 vcedit_clear_internals (vcedit_state *s)
120 ogg_stream_clear (&s->os);
121 ogg_sync_clear (&s->oy);
123 vorbis_info_clear (&s->vi);
124 vorbis_comment_clear (&s->vc);
142 vcedit_state_ref (vcedit_state *s)
148 vcedit_state_unref (vcedit_state *s)
154 vcedit_clear_internals (s);
156 vcedit_state_free (s);
159 /* Next two functions pulled straight from libvorbis, apart from one change
160 * - we don't want to overwrite the vendor string.
163 _v_writestring (oggpack_buffer *o, char *s, int len)
166 oggpack_write (o, *s++, 8);
170 _commentheader_out (vcedit_state *s, ogg_packet *op)
176 oggpack_writeinit (&opb);
179 oggpack_write (&opb, 0x03, 8);
180 _v_writestring (&opb, "vorbis", 6);
183 len = strlen (s->vendor);
184 oggpack_write (&opb, len, 32);
185 _v_writestring (&opb, s->vendor, len);
188 oggpack_write (&opb, s->vc.comments, 32);
190 for (i = 0; i < s->vc.comments; i++) {
191 if (!s->vc.user_comments[i])
192 oggpack_write (&opb, 0, 32);
194 oggpack_write (&opb, s->vc.comment_lengths[i], 32);
195 _v_writestring (&opb, s->vc.user_comments[i],
196 s->vc.comment_lengths[i]);
200 oggpack_write (&opb, 1, 1);
202 op->bytes = oggpack_bytes (&opb);
203 op->packet = _ogg_malloc (op->bytes);
205 memcpy (op->packet, opb.buffer, op->bytes);
207 op->b_o_s = op->e_o_s = 0;
210 oggpack_writeclear (&opb);
216 _blocksize (vcedit_state *s, ogg_packet *p)
220 this = vorbis_packet_blocksize (&s->vi, p);
223 ret = (this + s->prevW) / 4;
231 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
236 result = ogg_stream_packetout (&s->os, p);
243 while (ogg_sync_pageout (&s->oy, page) != 1) {
244 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
245 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
246 ogg_sync_wrote (&s->oy, bytes);
248 if (!bytes && (feof (s->in) || ferror (s->in)))
252 if (ogg_page_eos (page))
254 else if (ogg_page_serialno (page) != s->serial) {
260 ogg_stream_pagein (&s->os, page);
262 return _fetch_next_packet (s, p, page);
266 vcedit_open (vcedit_state *s)
270 ogg_packet header_main, header_comments, header_codebooks;
274 size_t bytes, total = 0;
277 s->in = fopen (s->filename, "rb");
279 return VCEDIT_ERR_OPEN;
281 s->file_mode = stat (s->filename, &st) ? 0664 : st.st_mode;
283 ogg_sync_init (&s->oy);
286 /* Bail if we don't find data in the first 40 kB */
287 if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) {
288 ogg_sync_clear (&s->oy);
290 return VCEDIT_ERR_INVAL;
293 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
295 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
298 ogg_sync_wrote (&s->oy, bytes);
299 } while (ogg_sync_pageout (&s->oy, &og) != 1);
301 s->serial = ogg_page_serialno (&og);
303 ogg_stream_init (&s->os, s->serial);
304 vorbis_info_init (&s->vi);
305 vorbis_comment_init (&s->vc);
307 if (ogg_stream_pagein (&s->os, &og) < 0) {
308 ret = VCEDIT_ERR_INVAL;
312 if (ogg_stream_packetout (&s->os, &header_main) != 1) {
313 ret = VCEDIT_ERR_INVAL;
317 if (vorbis_synthesis_headerin (&s->vi, &s->vc, &header_main) < 0) {
318 ret = VCEDIT_ERR_INVAL;
322 s->mainlen = header_main.bytes;
323 s->mainbuf = malloc (s->mainlen);
324 memcpy (s->mainbuf, header_main.packet, header_main.bytes);
326 header = &header_comments;
329 if (feof (s->in) || ferror (s->in)) {
330 ret = VCEDIT_ERR_INVAL;
337 result = ogg_sync_pageout (&s->oy, &og);
339 break; /* Too little data so far */
344 ogg_stream_pagein (&s->os, &og);
347 result = ogg_stream_packetout (&s->os, header);
352 ret = VCEDIT_ERR_INVAL;
356 vorbis_synthesis_headerin (&s->vi, &s->vc, header);
359 s->booklen = header->bytes;
360 s->bookbuf = malloc (s->booklen);
361 memcpy (s->bookbuf, header->packet, header->bytes);
364 header = &header_codebooks;
368 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
370 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
371 ogg_sync_wrote (&s->oy, bytes);
374 /* Copy the vendor tag */
375 s->vendor = strdup (s->vc.vendor);
377 /* Headers are done! */
380 return VCEDIT_ERR_SUCCESS;
383 vcedit_clear_internals (s);
389 write_data (const void *buf, size_t size, size_t nmemb, FILE *stream)
394 w = fwrite (buf, size, nmemb, stream);
395 if (!w && ferror (stream))
406 vcedit_write (vcedit_state *s)
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 bool success = false;
415 int fd, result, bytes, needflush = 0, needout = 0;
418 return VCEDIT_ERR_INVAL;
420 strcpy (tmpfile, s->filename);
421 strcat (tmpfile, ".XXXXXX");
423 fd = mkstemp (tmpfile);
425 return VCEDIT_ERR_TMPFILE;
427 out = fdopen (fd, "wb");
432 return VCEDIT_ERR_TMPFILE;
435 s->prevW = s->extrapage = s->eosin = 0;
437 header_main.bytes = s->mainlen;
438 header_main.packet = s->mainbuf;
439 header_main.b_o_s = 1;
440 header_main.e_o_s = 0;
441 header_main.granulepos = 0;
443 header_codebooks.bytes = s->booklen;
444 header_codebooks.packet = s->bookbuf;
445 header_codebooks.b_o_s = 0;
446 header_codebooks.e_o_s = 0;
447 header_codebooks.granulepos = 0;
449 ogg_stream_init (&streamout, s->serial);
451 _commentheader_out (s, &header_comments);
453 ogg_stream_packetin (&streamout, &header_main);
454 ogg_stream_packetin (&streamout, &header_comments);
455 ogg_stream_packetin (&streamout, &header_codebooks);
457 while ((result = ogg_stream_flush (&streamout, &ogout))) {
458 if (!write_data (ogout.header, 1, ogout.header_len, out))
461 if (!write_data (ogout.body, 1, ogout.body_len, out))
465 while (_fetch_next_packet (s, &op, &ogin)) {
468 size = _blocksize (s, &op);
472 if (ogg_stream_flush (&streamout, &ogout)) {
473 if (!write_data (ogout.header, 1, ogout.header_len, out))
476 if (!write_data (ogout.body, 1, ogout.body_len, out))
479 } else if (needout) {
480 if (ogg_stream_pageout (&streamout, &ogout)) {
481 if (!write_data (ogout.header, 1, ogout.header_len, out))
484 if (!write_data (ogout.body, 1, ogout.body_len, out))
489 needflush = needout = 0;
491 if (op.granulepos == -1) {
492 op.granulepos = granpos;
493 ogg_stream_packetin (&streamout, &op);
495 /* granulepos is set, validly. Use it, and force a flush to
496 * account for shortened blocks (vcut) when appropriate
498 if (granpos > op.granulepos) {
499 granpos = op.granulepos;
500 ogg_stream_packetin (&streamout, &op);
503 ogg_stream_packetin (&streamout, &op);
511 while (ogg_stream_flush (&streamout, &ogout)) {
512 if (!write_data (ogout.header, 1, ogout.header_len, out))
515 if (!write_data (ogout.body, 1, ogout.body_len, out))
520 if (!write_data (ogin.header, 1, ogin.header_len, out))
523 if (!write_data (ogin.body, 1, ogin.body_len, out))
527 /* clear it, because not all paths to here do */
531 /* We copy the rest of the stream (other logical streams)
532 * through, a page at a time.
534 while ((result = ogg_sync_pageout (&s->oy, &ogout))) {
538 /* Don't bother going through the rest, we can just
539 * write the page out now
541 if (!write_data (ogout.header, 1, ogout.header_len, out))
544 if (!write_data (ogout.body, 1, ogout.body_len, out))
548 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
549 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
550 ogg_sync_wrote (&s->oy, bytes);
555 s->eosin = !bytes && feof (s->in);
568 unlink (s->filename);
569 rename (tmpfile, s->filename);
570 chmod (s->filename, s->file_mode);
573 ogg_stream_clear (&streamout);
575 /* We don't ogg_packet_clear() this, because the memory was
576 * allocated in _commentheader_out(), so we mirror that here
578 _ogg_free (header_comments.packet);
583 s->mainbuf = s->bookbuf = NULL;
586 return VCEDIT_ERR_INVAL;
588 vcedit_clear_internals (s);
590 return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ?
591 VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;