Logo Search packages:      
Sourcecode: tagtool version File versions  Download package

vcedit.c

/* 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.
 *
 * last modified: $Id: vcedit.c,v 1.1 2002/10/14 22:31:13 paol Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ogg/ogg.h>
#include <vorbis/codec.h>

#include "vcedit.h"
//#include "i18n.h"
#define _(s) s


#define CHUNKSIZE 4096

vcedit_state *vcedit_new_state(void)
{
      vcedit_state *state = malloc(sizeof(vcedit_state));
      memset(state, 0, sizeof(vcedit_state));

      return state;
}

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);
            free(state->vc);
      }
      if(state->os)
      {
            ogg_stream_clear(state->os);
            free(state->os);
      }
      if(state->oy)
      {
            ogg_sync_clear(state->oy);
            free(state->oy);
      }
      if(state->vendor)
            free(state->vendor);
      if(state->mainbuf)
            free(state->mainbuf);
      if(state->bookbuf)
            free(state->bookbuf);
      if(state->vi) {
            vorbis_info_clear(state->vi);
            free(state->vi);
      }

      memset(state, 0, sizeof(*state));
}

void vcedit_clear(vcedit_state *state)
{
      if(state)
      {
            vcedit_clear_internals(state);
            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;

      oggpack_writeclear(&opb);
      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, FILE *in)
{
      return vcedit_open_callbacks(state, (void *)in, 
                  (vcedit_read_func)fread, (vcedit_write_func)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 = malloc(sizeof(ogg_sync_state));
      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 = malloc(sizeof(ogg_stream_state));
      ogg_stream_init(state->os, state->serial);

      state->vi = malloc(sizeof(vorbis_info));
      vorbis_info_init(state->vi);

      state->vc = malloc(sizeof(vorbis_comment));
      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 = 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 = 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 = malloc(strlen(state->vc->vendor) +1);
      strcpy(state->vendor, state->vc->vendor);

      /* Headers are done! */
      return 0;

err:
      vcedit_clear_internals(state);
      return -1;
}

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;
      }

      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) {
                              fprintf(stderr, "Bumming out\n");
                              goto cleanup;
                        }
                        if(state->write(ogout.body,1,ogout.body_len, out) !=
                           (size_t) ogout.body_len) {
                              fprintf(stderr, "Bumming out 2\n");
                              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);

      free(state->mainbuf);
      free(state->bookbuf);
      state->mainbuf = state->bookbuf = NULL;

      if(!state->eosin)
      {
            state->lasterror =
                  _("Error writing stream to output. "
                    "Output stream may be corrupted or truncated.");
            return -1;
      }

      return 0;
}


Generated by  Doxygen 1.6.0   Back to index