/* 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 * * * Comment editing backend, suitable for use by nice frontend interfaces. * * last modified: $Id: vcedit.c,v 1.23 2003/09/03 07:58:05 calc Exp $ */ #include // For definition of ENABLE_OGG #ifdef ENABLE_OGG #include #include #include #include #include #include #include #include "vcedit.h" #define CHUNKSIZE 4096 vcedit_state *vcedit_new_state(void) { vcedit_state *state = malloc(sizeof(vcedit_state)); memset(state, 0, sizeof(vcedit_state)); state->oggtype = VCEDIT_IS_UNKNOWN; 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) { char *tmp; 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); } tmp = state->lasterror; memset(state, 0, sizeof(*state)); state->lasterror = tmp; } 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(vcedit_state *state, ogg_packet *op) { vorbis_comment *vc = state->vc; char *vendor = state->vendor; oggpack_buffer opb; oggpack_writeinit(&opb); if (state->oggtype == VCEDIT_IS_OGGVORBIS) { /* 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;icomments;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; if (state->oggtype == VCEDIT_IS_OGGVORBIS) { op->packetno = 1; } 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); } } /* * Next functions pulled straight from libvorbis, */ static void _v_readstring(oggpack_buffer *o,char *buf,int bytes){ while(bytes--){ *buf++=oggpack_read(o,8); } } /* * Next functions pulled straight from libvorbis, apart from one change * - disabled the EOP check */ static int _speex_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb) { int i; int vendorlen=oggpack_read(opb,32); if (vendorlen<0) goto err_out; vc->vendor=_ogg_calloc(vendorlen+1,1); _v_readstring(opb,vc->vendor,vendorlen); vc->comments=oggpack_read(opb,32); if (vc->comments<0) goto err_out; vc->user_comments=_ogg_calloc(vc->comments+1,sizeof(*vc->user_comments)); vc->comment_lengths=_ogg_calloc(vc->comments+1, sizeof(*vc->comment_lengths)); for (i=0;icomments;i++){ int len=oggpack_read(opb,32); if (len<0) goto err_out; vc->comment_lengths[i]=len; vc->user_comments[i]=_ogg_calloc(len+1,1); _v_readstring(opb,vc->user_comments[i],len); } //if(oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ return(0); err_out: vorbis_comment_clear(vc); return(1); } 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; int chunks = 0; 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); while(1) { 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) break; if(chunks++ >= 10) /* Bail if we don't find data in the first 40 kB */ { if(byteslasterror = _("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; } // We save the main header first, (it seems speex_packet_to_header munge's it??) state->mainlen = header_main.bytes; state->mainbuf = malloc(state->mainlen); memcpy(state->mainbuf, header_main.packet, header_main.bytes); state->oggtype = VCEDIT_IS_UNKNOWN; int headerpackets = 0; if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) == 0) { state->oggtype = VCEDIT_IS_OGGVORBIS; } #ifdef ENABLE_SPEEX else { // Done after "Ogg test" to avoid to display an error message in function // speex_packet_to_header when the file is not Speex. state->si = malloc(sizeof(SpeexHeader)); if((state->si = speex_packet_to_header((char*)(&header_main)->packet,(&header_main)->bytes))) state->oggtype = VCEDIT_IS_SPEEX; } #endif if (state->oggtype == VCEDIT_IS_UNKNOWN) { state->lasterror = _("Ogg bitstream contains neither speex or vorbis data."); goto err; } switch (state->oggtype) { case VCEDIT_IS_OGGVORBIS: header = &header_comments; headerpackets = 3; break; #ifdef ENABLE_SPEEX case VCEDIT_IS_SPEEX: header = &header_comments; headerpackets = 2 + state->si->extra_headers; break; #endif } oggpack_buffer opb; i = 1; while(ioy, &og); if(result == 0) break; /* Too little data so far */ else if(result == 1) { ogg_stream_pagein(state->os, &og); while(i< headerpackets) { result = ogg_stream_packetout(state->os, header); if(result == 0) break; if(result == -1) { state->lasterror = _("Corrupt secondary header."); goto err; } switch (state->oggtype) { case VCEDIT_IS_OGGVORBIS: vorbis_synthesis_headerin(state->vi, state->vc, header); switch (i) { // 0 packet was the vorbis header case 1: header = &header_codebooks; break; case 2: state->booklen = header->bytes; state->bookbuf = malloc(state->booklen); memcpy(state->bookbuf, header->packet, header->bytes); break; } break; case VCEDIT_IS_SPEEX: switch (i) { // 0 packet was the speex header case 1: oggpack_readinit(&opb,header->packet,header->bytes); _speex_unpack_comment(state->vc,&opb); break; default: state->lasterror = _("Need to save extra headers - TODO!!"); goto err; break; } } i++; } } } 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, &header_comments); ogg_stream_packetin(&streamout, &header_main); ogg_stream_packetin(&streamout, &header_comments); if (state->oggtype != VCEDIT_IS_SPEEX) 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)) { 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 (state->oggtype == VCEDIT_IS_OGGVORBIS) { int size; size = _blocksize(state, &op); granpos += size; 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; } } } /* Don't know about granulepos for speex, will just assume the original was appropriate. Not sure about the flushing?? */ else if (state->oggtype == VCEDIT_IS_SPEEX) { 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) { 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); 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; } #endif /* ENABLE_OGG */