diff src/vorbis/vcedit.c @ 12:3da1b8942b8b trunk

[svn] - remove src/Input src/Output src/Effect src/General src/Visualization src/Container
author nenolod
date Mon, 18 Sep 2006 03:14:20 -0700
parents src/Input/vorbis/vcedit.c@54f9e753b511
children 6acf1bda788b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/vorbis/vcedit.c	Mon Sep 18 03:14:20 2006 -0700
@@ -0,0 +1,472 @@
+/* This program is licensed under the GNU Library General Public License, version 2,
+ * a copy of which is included with this program (LICENCE.LGPL).
+ *
+ * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au>
+ *
+ *
+ * Comment editing backend, suitable for use by nice frontend interfaces.
+ *
+ */
+#include "config.h"
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+
+#include "vcedit.h"
+#include <audacious/vfs.h>
+
+#define CHUNKSIZE 4096
+
+vcedit_state *
+vcedit_new_state(void)
+{
+    return g_new0(vcedit_state, 1);
+}
+
+char *
+vcedit_error(vcedit_state * state)
+{
+    return state->lasterror;
+}
+
+vorbis_comment *
+vcedit_comments(vcedit_state * state)
+{
+    return state->vc;
+}
+
+static void
+vcedit_clear_internals(vcedit_state * state)
+{
+    if (state->vc) {
+        vorbis_comment_clear(state->vc);
+        g_free(state->vc);
+        state->vc = NULL;
+    }
+    if (state->os) {
+        ogg_stream_clear(state->os);
+        g_free(state->os);
+        state->os = NULL;
+    }
+    if (state->oy) {
+        ogg_sync_clear(state->oy);
+        g_free(state->oy);
+        state->oy = NULL;
+    }
+    if (state->vendor) {
+        g_free(state->vendor);
+        state->vendor = NULL;
+    }
+}
+
+void
+vcedit_clear(vcedit_state * state)
+{
+    if (state) {
+        vcedit_clear_internals(state);
+        g_free(state);
+    }
+}
+
+/* Next two functions pulled straight from libvorbis, apart from one change
+ * - we don't want to overwrite the vendor string.
+ */
+static void
+_v_writestring(oggpack_buffer * o, char *s, int len)
+{
+    while (len--) {
+        oggpack_write(o, *s++, 8);
+    }
+}
+
+static int
+_commentheader_out(vorbis_comment * vc, char *vendor, ogg_packet * op)
+{
+    oggpack_buffer opb;
+
+    oggpack_writeinit(&opb);
+
+    /* preamble */
+    oggpack_write(&opb, 0x03, 8);
+    _v_writestring(&opb, "vorbis", 6);
+
+    /* vendor */
+    oggpack_write(&opb, strlen(vendor), 32);
+    _v_writestring(&opb, vendor, strlen(vendor));
+
+    /* comments */
+    oggpack_write(&opb, vc->comments, 32);
+    if (vc->comments) {
+        int i;
+        for (i = 0; i < vc->comments; i++) {
+            if (vc->user_comments[i]) {
+                oggpack_write(&opb, vc->comment_lengths[i], 32);
+                _v_writestring(&opb, vc->user_comments[i],
+                               vc->comment_lengths[i]);
+            }
+            else {
+                oggpack_write(&opb, 0, 32);
+            }
+        }
+    }
+    oggpack_write(&opb, 1, 1);
+
+    op->packet = _ogg_malloc(oggpack_bytes(&opb));
+    memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
+
+    op->bytes = oggpack_bytes(&opb);
+    op->b_o_s = 0;
+    op->e_o_s = 0;
+    op->granulepos = 0;
+
+    return 0;
+}
+
+static int
+_blocksize(vcedit_state * s, ogg_packet * p)
+{
+    int this = vorbis_packet_blocksize(&s->vi, p);
+    int ret = (this + s->prevW) / 4;
+
+    if (!s->prevW) {
+        s->prevW = this;
+        return 0;
+    }
+
+    s->prevW = this;
+    return ret;
+}
+
+static int
+_fetch_next_packet(vcedit_state * s, ogg_packet * p, ogg_page * page)
+{
+    int result;
+    char *buffer;
+    int bytes;
+
+    result = ogg_stream_packetout(s->os, p);
+
+    if (result > 0)
+        return 1;
+    else {
+        if (s->eosin)
+            return 0;
+        while (ogg_sync_pageout(s->oy, page) <= 0) {
+            buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
+            bytes = s->read(buffer, 1, CHUNKSIZE, s->in);
+            ogg_sync_wrote(s->oy, bytes);
+            if (bytes == 0)
+                return 0;
+        }
+        if (ogg_page_eos(page))
+            s->eosin = 1;
+        else if (ogg_page_serialno(page) != s->serial) {
+            s->eosin = 1;
+            s->extrapage = 1;
+            return 0;
+        }
+
+        ogg_stream_pagein(s->os, page);
+        return _fetch_next_packet(s, p, page);
+    }
+}
+
+
+int
+vcedit_open(vcedit_state * state, VFSFile * in)
+{
+    return vcedit_open_callbacks(state, (void *) in,
+                                 (vcedit_read_func) vfs_fread,
+                                 (vcedit_write_func) vfs_fwrite);
+}
+
+int
+vcedit_open_callbacks(vcedit_state * state, void *in,
+                      vcedit_read_func read_func,
+                      vcedit_write_func write_func)
+{
+    char *buffer;
+    int bytes, i;
+    ogg_packet *header;
+    ogg_packet header_main;
+    ogg_packet header_comments;
+    ogg_packet header_codebooks;
+    ogg_page og;
+
+    state->in = in;
+    state->read = read_func;
+    state->write = write_func;
+
+    state->oy = g_new(ogg_sync_state, 1);
+    ogg_sync_init(state->oy);
+
+    buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
+
+    bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
+
+    ogg_sync_wrote(state->oy, bytes);
+
+    if (ogg_sync_pageout(state->oy, &og) != 1) {
+        if (bytes < CHUNKSIZE)
+            state->lasterror = "Input truncated or empty.";
+        else
+            state->lasterror = "Input is not an Ogg bitstream.";
+        goto err;
+    }
+
+    state->serial = ogg_page_serialno(&og);
+
+    state->os = g_new(ogg_stream_state, 1);
+    ogg_stream_init(state->os, state->serial);
+
+    vorbis_info_init(&state->vi);
+
+    state->vc = g_new(vorbis_comment, 1);
+    vorbis_comment_init(state->vc);
+
+    if (ogg_stream_pagein(state->os, &og) < 0) {
+        state->lasterror = "Error reading first page of Ogg bitstream.";
+        goto err;
+    }
+
+    if (ogg_stream_packetout(state->os, &header_main) != 1) {
+        state->lasterror = "Error reading initial header packet.";
+        goto err;
+    }
+
+    if (vorbis_synthesis_headerin(&state->vi, state->vc, &header_main) < 0) {
+        state->lasterror = "Ogg bitstream does not contain vorbis data.";
+        goto err;
+    }
+
+    state->mainlen = header_main.bytes;
+    state->mainbuf = g_malloc(state->mainlen);
+    memcpy(state->mainbuf, header_main.packet, header_main.bytes);
+
+    i = 0;
+    header = &header_comments;
+    while (i < 2) {
+        while (i < 2) {
+            int result = ogg_sync_pageout(state->oy, &og);
+            if (result == 0)
+                break;          /* Too little data so far */
+            else if (result == 1) {
+                ogg_stream_pagein(state->os, &og);
+                while (i < 2) {
+                    result = ogg_stream_packetout(state->os, header);
+                    if (result == 0)
+                        break;
+                    if (result == -1) {
+                        state->lasterror = "Corrupt secondary header.";
+                        goto err;
+                    }
+                    vorbis_synthesis_headerin(&state->vi, state->vc, header);
+                    if (i == 1) {
+                        state->booklen = header->bytes;
+                        state->bookbuf = g_malloc(state->booklen);
+                        memcpy(state->bookbuf, header->packet, header->bytes);
+                    }
+                    i++;
+                    header = &header_codebooks;
+                }
+            }
+        }
+
+        buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
+        bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
+        if (bytes == 0 && i < 2) {
+            state->lasterror = "EOF before end of vorbis headers.";
+            goto err;
+        }
+        ogg_sync_wrote(state->oy, bytes);
+    }
+
+    /* Copy the vendor tag */
+    state->vendor = g_malloc(strlen(state->vc->vendor) + 1);
+    strcpy(state->vendor, state->vc->vendor);
+
+    /* Headers are done! */
+    return 0;
+
+  err:
+    vcedit_clear_internals(state);
+    return -1;
+}
+
+#if 0
+static void
+dump_state(vcedit_state * state)
+{
+}
+#endif
+
+int
+vcedit_write(vcedit_state * state, void *out)
+{
+
+    ogg_stream_state streamout;
+    ogg_packet header_main;
+    ogg_packet header_comments;
+    ogg_packet header_codebooks;
+
+    ogg_page ogout, ogin;
+    ogg_packet op;
+    ogg_int64_t granpos = 0;
+    int result;
+    char *buffer;
+    int bytes;
+    int needflush = 0, needout = 0;
+
+    state->eosin = 0;
+    state->extrapage = 0;
+
+    header_main.bytes = state->mainlen;
+    header_main.packet = state->mainbuf;
+    header_main.b_o_s = 1;
+    header_main.e_o_s = 0;
+    header_main.granulepos = 0;
+
+    header_codebooks.bytes = state->booklen;
+    header_codebooks.packet = state->bookbuf;
+    header_codebooks.b_o_s = 0;
+    header_codebooks.e_o_s = 0;
+    header_codebooks.granulepos = 0;
+
+    ogg_stream_init(&streamout, state->serial);
+
+    _commentheader_out(state->vc, state->vendor, &header_comments);
+
+    ogg_stream_packetin(&streamout, &header_main);
+    ogg_stream_packetin(&streamout, &header_comments);
+    ogg_stream_packetin(&streamout, &header_codebooks);
+
+    while ((result = ogg_stream_flush(&streamout, &ogout))) {
+        if (state->write(ogout.header, 1, ogout.header_len, out) !=
+            (size_t) ogout.header_len)
+            goto cleanup;
+        if (state->write(ogout.body, 1, ogout.body_len, out) !=
+            (size_t) ogout.body_len)
+            goto cleanup;
+    }
+
+    while (_fetch_next_packet(state, &op, &ogin)) {
+        int size;
+        size = _blocksize(state, &op);
+        granpos += size;
+
+        if (needflush) {
+            if (ogg_stream_flush(&streamout, &ogout)) {
+                if (state->write(ogout.header, 1, ogout.header_len,
+                                 out) != (size_t) ogout.header_len)
+                    goto cleanup;
+                if (state->write(ogout.body, 1, ogout.body_len,
+                                 out) != (size_t) ogout.body_len)
+                    goto cleanup;
+            }
+        }
+        else if (needout) {
+            if (ogg_stream_pageout(&streamout, &ogout)) {
+                if (state->write(ogout.header, 1, ogout.header_len,
+                                 out) != (size_t) ogout.header_len)
+                    goto cleanup;
+                if (state->write(ogout.body, 1, ogout.body_len,
+                                 out) != (size_t) ogout.body_len)
+                    goto cleanup;
+            }
+        }
+
+        needflush = needout = 0;
+
+        if (op.granulepos == -1) {
+            op.granulepos = granpos;
+            ogg_stream_packetin(&streamout, &op);
+        }
+        else {                  /* granulepos is set, validly. Use it, and force a flush to 
+                                   account for shortened blocks (vcut) when appropriate */
+            if (granpos > op.granulepos) {
+                granpos = op.granulepos;
+                ogg_stream_packetin(&streamout, &op);
+                needflush = 1;
+            }
+            else {
+                ogg_stream_packetin(&streamout, &op);
+                needout = 1;
+            }
+        }
+    }
+
+    streamout.e_o_s = 1;
+    while (ogg_stream_flush(&streamout, &ogout)) {
+        if (state->write(ogout.header, 1, ogout.header_len,
+                         out) != (size_t) ogout.header_len)
+            goto cleanup;
+        if (state->write(ogout.body, 1, ogout.body_len,
+                         out) != (size_t) ogout.body_len)
+            goto cleanup;
+    }
+
+    /* FIXME: freeing this here probably leaks memory */
+    /* Done with this, now */
+    vorbis_info_clear(&state->vi);
+
+    if (state->extrapage) {
+        if (state->write(ogin.header, 1, ogin.header_len,
+                         out) != (size_t) ogin.header_len)
+            goto cleanup;
+        if (state->write(ogin.body, 1, ogin.body_len, out) !=
+            (size_t) ogin.body_len)
+            goto cleanup;
+    }
+
+    state->eosin = 0;           /* clear it, because not all paths to here do */
+    while (!state->eosin) {     /* We reached eos, not eof */
+        /* We copy the rest of the stream (other logical streams)
+         * through, a page at a time. */
+        while (1) {
+            result = ogg_sync_pageout(state->oy, &ogout);
+            if (result == 0)
+                break;
+            if (result < 0)
+                state->lasterror = "Corrupt or missing data, continuing...";
+            else {
+                /* Don't bother going through the rest, we can just 
+                 * write the page out now */
+                if (state->write(ogout.header, 1, ogout.header_len,
+                                 out) != (size_t) ogout.header_len)
+                    goto cleanup;
+                if (state->write(ogout.body, 1, ogout.body_len, out) !=
+                    (size_t) ogout.body_len)
+                    goto cleanup;
+            }
+        }
+        buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
+        bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
+        ogg_sync_wrote(state->oy, bytes);
+        if (bytes == 0) {
+            state->eosin = 1;
+            break;
+        }
+    }
+
+
+  cleanup:
+    ogg_stream_clear(&streamout);
+    ogg_packet_clear(&header_comments);
+
+    g_free(state->mainbuf);
+    g_free(state->bookbuf);
+
+    vcedit_clear_internals(state);
+    if (!state->eosin) {
+        state->lasterror =
+            "Error writing stream to output. "
+            "Output stream may be corrupted or truncated.";
+        return -1;
+    }
+
+    return 0;
+}