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 {
51 ogg_packet packet_main;
52 ogg_packet packet_code_books;
64 ogg_packet_init (ogg_packet *p, unsigned char *buf, long len)
69 p->b_o_s = p->e_o_s = 0;
70 p->granulepos = p->packetno = 0;
74 ogg_packet_dup_data (ogg_packet *p)
78 tmp = malloc (p->bytes);
82 memcpy (tmp, p->packet, p->bytes);
89 vcedit_state_free (vcedit_state *s)
102 vcedit_state_init (vcedit_state *s, const char *filename)
106 ogg_packet_init (&s->packet_main, NULL, 0);
107 ogg_packet_init (&s->packet_code_books, NULL, 0);
109 strcpy (s->filename, filename);
115 vcedit_state_new (const char *filename)
120 len = strlen (filename);
124 s = malloc (sizeof (vcedit_state) + len + 1);
128 memset (s, 0, sizeof (vcedit_state));
130 if (!vcedit_state_init (s, filename)) {
131 vcedit_state_free (s);
139 vcedit_comments (vcedit_state *s)
141 return s->opened ? &s->vc : NULL;
145 vcedit_clear_internals (vcedit_state *s)
147 ogg_stream_clear (&s->os);
148 ogg_sync_clear (&s->oy);
150 vorbis_info_clear (&s->vi);
151 vorbis_comment_clear (&s->vc);
156 ogg_packet_clear (&s->packet_main);
157 ogg_packet_clear (&s->packet_code_books);
164 vcedit_state_ref (vcedit_state *s)
170 vcedit_state_unref (vcedit_state *s)
176 vcedit_clear_internals (s);
178 vcedit_state_free (s);
181 /* Next two functions pulled straight from libvorbis, apart from one change
182 * - we don't want to overwrite the vendor string.
185 _v_writestring (oggpack_buffer *o, const char *s, int len)
188 oggpack_write (o, *s++, 8);
192 write_comments (vcedit_state *s, ogg_packet *packet)
198 ogg_packet_init (packet, NULL, 0);
200 oggpack_writeinit (&opb);
203 oggpack_write (&opb, 0x03, 8);
204 _v_writestring (&opb, "vorbis", 6);
207 len = strlen (s->vendor);
208 oggpack_write (&opb, len, 32);
209 _v_writestring (&opb, s->vendor, len);
212 oggpack_write (&opb, s->vc.comments, 32);
214 for (i = 0; i < s->vc.comments; i++)
215 if (!s->vc.user_comments[i])
216 oggpack_write (&opb, 0, 32);
218 oggpack_write (&opb, s->vc.comment_lengths[i], 32);
219 _v_writestring (&opb, s->vc.user_comments[i],
220 s->vc.comment_lengths[i]);
223 oggpack_write (&opb, 1, 1);
225 packet->bytes = oggpack_bytes (&opb);
227 packet->packet = _ogg_malloc (packet->bytes);
231 memcpy (packet->packet, opb.buffer, packet->bytes);
233 oggpack_writeclear (&opb);
239 _blocksize (vcedit_state *s, ogg_packet *p)
243 this = vorbis_packet_blocksize (&s->vi, p);
246 ret = (this + s->prevW) / 4;
254 _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
259 result = ogg_stream_packetout (&s->os, p);
266 while (ogg_sync_pageout (&s->oy, page) != 1) {
267 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
268 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
269 ogg_sync_wrote (&s->oy, bytes);
271 if (!bytes && (feof (s->in) || ferror (s->in)))
275 if (ogg_page_eos (page))
277 else if (ogg_page_serialno (page) != s->serial) {
279 s->extra_page = true;
284 ogg_stream_pagein (&s->os, page);
286 return _fetch_next_packet (s, p, page);
290 vcedit_open (vcedit_state *s)
293 ogg_packet packet_comments, *header = &packet_comments;
297 size_t bytes, total = 0;
300 s->in = fopen (s->filename, "rb");
302 return VCEDIT_ERR_OPEN;
304 s->file_mode = stat (s->filename, &st) ? 0664 : st.st_mode;
306 ogg_sync_init (&s->oy);
309 /* Bail if we don't find data in the first 40 kB */
310 if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) {
311 ogg_sync_clear (&s->oy);
313 return VCEDIT_ERR_INVAL;
316 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
318 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
321 ogg_sync_wrote (&s->oy, bytes);
322 } while (ogg_sync_pageout (&s->oy, &page) != 1);
324 s->serial = ogg_page_serialno (&page);
326 ogg_stream_init (&s->os, s->serial);
327 vorbis_info_init (&s->vi);
328 vorbis_comment_init (&s->vc);
330 if (ogg_stream_pagein (&s->os, &page) < 0) {
331 ret = VCEDIT_ERR_INVAL;
335 if (ogg_stream_packetout (&s->os, &s->packet_main) != 1) {
336 ret = VCEDIT_ERR_INVAL;
340 if (!ogg_packet_dup_data (&s->packet_main)) {
341 s->packet_main.packet = NULL;
342 ret = VCEDIT_ERR_INVAL;
346 if (vorbis_synthesis_headerin (&s->vi, &s->vc, &s->packet_main) < 0) {
347 ret = VCEDIT_ERR_INVAL;
351 ogg_packet_init (&packet_comments, NULL, 0);
354 if (feof (s->in) || ferror (s->in)) {
355 ret = VCEDIT_ERR_INVAL;
362 result = ogg_sync_pageout (&s->oy, &page);
364 break; /* Too little data so far */
369 ogg_stream_pagein (&s->os, &page);
372 result = ogg_stream_packetout (&s->os, header);
377 ret = VCEDIT_ERR_INVAL;
381 if (i++ == 1 && !ogg_packet_dup_data (header)) {
382 header->packet = NULL;
383 ret = VCEDIT_ERR_INVAL;
387 vorbis_synthesis_headerin (&s->vi, &s->vc, header);
389 header = &s->packet_code_books;
393 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
395 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
396 ogg_sync_wrote (&s->oy, bytes);
399 /* Copy the vendor tag */
400 s->vendor = strdup (s->vc.vendor);
402 /* Headers are done! */
405 return VCEDIT_ERR_SUCCESS;
408 vcedit_clear_internals (s);
414 write_data (const void *buf, size_t size, size_t nmemb, FILE *stream)
419 w = fwrite (buf, size, nmemb, stream);
420 if (!w && ferror (stream))
431 write_page (FILE *f, ogg_page *p)
433 return write_data (p->header, 1, p->header_len, f) &&
434 write_data (p->body, 1, p->body_len, f);
438 vcedit_write (vcedit_state *s)
440 ogg_stream_state stream;
442 ogg_page page_out, page_in;
443 ogg_int64_t granpos = 0;
445 char *buffer, tmpfile[PATH_MAX];
446 bool success = false, need_flush = false, need_out = false;
447 int fd, result, bytes;
450 return VCEDIT_ERR_INVAL;
452 strcpy (tmpfile, s->filename);
453 strcat (tmpfile, ".XXXXXX");
455 fd = mkstemp (tmpfile);
457 return VCEDIT_ERR_TMPFILE;
459 out = fdopen (fd, "wb");
464 return VCEDIT_ERR_TMPFILE;
468 s->extra_page = s->eos = false;
470 ogg_stream_init (&stream, s->serial);
472 /* write "main" packet */
473 s->packet_main.b_o_s = 1;
474 ogg_stream_packetin (&stream, &s->packet_main);
475 s->packet_main.b_o_s = 0;
477 /* prepare and write comments */
478 if (!write_comments (s, &packet)) {
479 ogg_stream_clear (&stream);
483 return VCEDIT_ERR_INVAL;
486 ogg_stream_packetin (&stream, &packet);
487 ogg_packet_clear (&packet);
489 /* write codebooks */
490 ogg_stream_packetin (&stream, &s->packet_code_books);
492 while (ogg_stream_flush (&stream, &page_out))
493 if (!write_page (out, &page_out))
496 while (_fetch_next_packet (s, &packet, &page_in)) {
500 size = _blocksize (s, &packet);
504 write = ogg_stream_flush (&stream, &page_out);
506 write = ogg_stream_pageout (&stream, &page_out);
508 if (write && !write_page (out, &page_out))
511 need_flush = need_out = false;
513 if (packet.granulepos == -1) {
514 packet.granulepos = granpos;
515 ogg_stream_packetin (&stream, &packet);
517 /* granulepos is set, validly. Use it, and force a flush to
518 * account for shortened blocks (vcut) when appropriate
520 if (granpos > packet.granulepos) {
521 granpos = packet.granulepos;
522 ogg_stream_packetin (&stream, &packet);
525 ogg_stream_packetin (&stream, &packet);
533 while (ogg_stream_flush (&stream, &page_out))
534 if (!write_page (out, &page_out))
537 if (s->extra_page && !write_page (out, &page_in))
540 /* clear it, because not all paths to here do */
544 /* We copy the rest of the stream (other logical streams)
545 * through, a page at a time.
547 while ((result = ogg_sync_pageout (&s->oy, &page_out)))
548 if (result == 1 && !write_page (out, &page_out))
551 buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
552 bytes = fread (buffer, 1, CHUNKSIZE, s->in);
553 ogg_sync_wrote (&s->oy, bytes);
557 } while (bytes || !feof (s->in));
559 s->eos = success = true;
569 unlink (s->filename);
570 rename (tmpfile, s->filename);
571 chmod (s->filename, s->file_mode);
574 ogg_stream_clear (&stream);
577 return VCEDIT_ERR_INVAL;
579 vcedit_clear_internals (s);
581 return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ?
582 VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;