[xiph-commits] r15515 - in trunk/vorbis-tools: . vcut
ivo at svn.xiph.org
ivo at svn.xiph.org
Sun Nov 9 14:34:21 PST 2008
Author: ivo
Date: 2008-11-09 14:34:21 -0800 (Sun, 09 Nov 2008)
New Revision: 15515
Modified:
trunk/vorbis-tools/CHANGES
trunk/vorbis-tools/vcut/vcut.c
trunk/vorbis-tools/vcut/vcut.h
Log:
Support for chained streams in vcut. Patch by Michael Gold. Closes #1455
Modified: trunk/vorbis-tools/CHANGES
===================================================================
--- trunk/vorbis-tools/CHANGES 2008-11-09 12:50:37 UTC (rev 15514)
+++ trunk/vorbis-tools/CHANGES 2008-11-09 22:34:21 UTC (rev 15515)
@@ -8,6 +8,7 @@
* Corrected SUBLANG values in intl/localename.c (#1415)
* Modify -v to -V on oggenc and oggdec for consistency (#1112)
* Fix for utf8_decode in Windows; improves behavior in vorbiscomment (#268)
+ * Updated gettextize to version 0.17
* ogg123: backported fix from libfishsound to patch the Speex decoder (#1347)
* ogg123: fixed CPU issue when outputting to a closed pipe (#1357)
* ogg123: return value to stop decoding after buffer is shut down (#1357)
@@ -30,6 +31,7 @@
* oggenc: lyrics support using .lrc as source; requires libkate (#1403)
* ogginfo: support for information in Kate streams (#1360)
* vcut: 64 bit fixes (#1366)
+ * vcut: support for chained streams (#1455)
* vorbiscomment: correct memory allocation (#472)
* vorbiscomment: validate raw UTF-8 sent to vorbiscomment (#268)
* vorbiscomment: fix segfault when using --tag (#1439)
Modified: trunk/vorbis-tools/vcut/vcut.c
===================================================================
--- trunk/vorbis-tools/vcut/vcut.c 2008-11-09 12:50:37 UTC (rev 15514)
+++ trunk/vorbis-tools/vcut/vcut.c 2008-11-09 22:34:21 UTC (rev 15515)
@@ -14,13 +14,16 @@
#include <config.h>
#endif
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/types.h>
#include <ogg/ogg.h>
#include <vorbis/codec.h>
#include <time.h>
#include <errno.h>
#include <string.h>
+#include <unistd.h>
#include "vcut.h"
@@ -47,46 +50,57 @@
#endif
#endif
-static vcut_packet *save_packet(ogg_packet *packet)
+static void clear_packet(vcut_packet *p)
{
- vcut_packet *p = malloc(sizeof(vcut_packet));
-
- p->length = packet->bytes;
- p->packet = malloc(p->length);
- memcpy(p->packet, packet->packet, p->length);
-
- return p;
+ if(p->packet)
+ free(p->packet);
+ p->packet = NULL;
}
-static void free_packet(vcut_packet *p)
+/* Returns 0 for success, or -1 on failure. */
+static void *vcut_malloc(size_t size)
{
- if(p)
- {
- if(p->packet)
- free(p->packet);
- free(p);
- }
+ void *ret = malloc(size);
+ /* FIXME: libogg will probably crash if one of its malloc calls fails,
+ * so we can't always catch an OOM error. */
+ if(!ret)
+ fprintf(stderr, _("Out of memory\n"));
+ return ret;
}
-static long get_blocksize(vcut_state *s, vorbis_info *vi, ogg_packet *op)
+/* Returns 0 for success, or -1 on failure. */
+static int save_packet(ogg_packet *packet, vcut_packet *p)
{
- int this = vorbis_packet_blocksize(vi, op);
- int ret = (this+s->prevW)/4;
+ clear_packet(p);
+ p->length = packet->bytes;
+ p->packet = vcut_malloc(p->length);
+ if(!p->packet)
+ return -1;
- s->prevW = this;
+ memcpy(p->packet, packet->packet, p->length);
+ return 0;
+}
+
+static long get_blocksize(vcut_vorbis_stream *vs, ogg_packet *op)
+{
+ int this = vorbis_packet_blocksize(&vs->vi, op);
+ int ret = (this+vs->prevW)/4;
+
+ vs->prevW = this;
return ret;
}
-static int update_sync(vcut_state *s, FILE *f)
+static int update_sync(vcut_state *s)
{
- char *buffer = ogg_sync_buffer(s->sync_in, 4096);
- int bytes = fread(buffer,1,4096,f);
- ogg_sync_wrote(s->sync_in, bytes);
+ char *buffer = ogg_sync_buffer(&s->sync_in, 4096);
+ int bytes = fread(buffer, 1, 4096, s->in);
+ ogg_sync_wrote(&s->sync_in, bytes);
return bytes;
}
-/* Returns 0 for success, or -1 on failure. */
-static int write_pages_to_file(ogg_stream_state *stream,
+/* Writes pages to the given file, or discards them if file is NULL.
+ * Returns 0 for success, or -1 on failure. */
+static int write_pages_to_file(ogg_stream_state *stream,
FILE *file, int flush)
{
ogg_page page;
@@ -95,6 +109,7 @@
{
while(ogg_stream_flush(stream, &page))
{
+ if(!file) continue;
if(fwrite(page.header,1,page.header_len, file) != page.header_len)
return -1;
if(fwrite(page.body,1,page.body_len, file) != page.body_len)
@@ -105,6 +120,7 @@
{
while(ogg_stream_pageout(stream, &page))
{
+ if(!file) continue;
if(fwrite(page.header,1,page.header_len, file) != page.header_len)
return -1;
if(fwrite(page.body,1,page.body_len, file) != page.body_len)
@@ -116,414 +132,125 @@
}
-/* Write the first stream to output file until we get to the appropriate
- * cut point.
- *
- * We need to do the following:
- * - Adjust the end of the stream to note the new end of stream.
- * - Change the final granulepos to be the cutpoint value, so that we don't
- * decode the extra data past this.
- * - Save the final two packets in the stream to temporary buffers.
- * These two packets then become the first two packets in the 2nd stream
- * (we need two packets because of the overlap-add nature of vorbis).
- * - For each packet, buffer it (it could be the 2nd last packet, we don't
- * know yet (but we could optimise this decision based on known maximum
- * block sizes, and call get_blocksize(), because this updates internal
- * state needed for sample-accurate block size calculations.
- */
-static int process_first_stream(vcut_state *s, ogg_stream_state *stream,
- FILE *in, FILE *f)
+/* Flushes and closes the output stream, leaving the file open.
+ * Returns 0 for success, or -1 on failure. */
+static int close_output_stream(vcut_state *s)
{
- int eos=0;
- ogg_page page;
- ogg_packet packet;
- ogg_int64_t granpos, prevgranpos = -1;
- int result;
+ assert(s->output_stream_open);
- while(!eos)
+ if(write_pages_to_file(&s->stream_out, s->out, 1) != 0)
{
- while(!eos)
- {
- int result = ogg_sync_pageout(s->sync_in, &page);
- if(result==0) break;
- else if(result<0) fprintf(stderr, _("Page error. Corrupt input.\n"));
- else
- {
- granpos = ogg_page_granulepos(&page);
- ogg_stream_pagein(s->stream_in, &page);
-
- if(granpos < s->cutpoint)
- {
- while(1)
- {
- result=ogg_stream_packetout(s->stream_in, &packet);
-
- /* throw away result, but update state */
- get_blocksize(s,s->vi,&packet);
-
- if(result==0) break;
- else if(result==-1)
- fprintf(stderr, _("Bitstream error, continuing\n"));
- else
- {
- if(packet.e_o_s)
- s->e_o_s=1;
-
- /* We need to save the last packet in the first
- * stream - but we don't know when we're going
- * to get there. So we have to keep every packet
- * just in case.
- */
- if(s->packets[0])
- free_packet(s->packets[0]);
- s->packets[0] = save_packet(&packet);
-
- ogg_stream_packetin(stream, &packet);
-
- /* Flush the stream after the second audio
- * packet, which is necessary if we need the
- * decoder to discard some samples from the
- * beginning of this packet.
- */
- if(packet.packetno == 4
- && packet.granulepos != -1)
- {
- if(write_pages_to_file(stream, f,1))
- return -1;
- }
- else if(write_pages_to_file(stream, f,0))
- return -1;
- }
- }
- prevgranpos = granpos;
- }
- else
- eos=1; /* First stream ends somewhere in this page.
- We break of out this loop here. */
-
- if(ogg_page_eos(&page) && !eos)
- {
- fprintf(stderr, _("Found EOS before cut point.\n"));
- eos=1;
- }
- }
- }
- if(!eos)
- {
- if(update_sync(s,in)==0)
- {
- fprintf(stderr, _("Setting EOS: update sync returned 0\n"));
- eos=1;
- }
- }
- }
-
- /* Now, check to see if we reached a real EOS */
- if(granpos < s->cutpoint)
- {
- fprintf(stderr,
- _("Cutpoint not within stream. Second file will be empty\n"));
- write_pages_to_file(stream, f,0);
-
+ fprintf(stderr, _("Couldn't flush output stream\n"));
return -1;
}
- while((result = ogg_stream_packetout(s->stream_in, &packet))!=0)
- {
- int bs;
-
- if(packet.e_o_s)
- s->e_o_s=1;
- bs = get_blocksize(s, s->vi, &packet);
- if(prevgranpos == -1)
- {
- /* this is the first audio packet; the second one normally
- * starts at position 0 */
- prevgranpos = 0;
- }
- else if(prevgranpos == 0 && !packet.e_o_s)
- {
- /* the second packet; if our calculated granule position is
- * greater than granpos, it means some audio samples must be
- * discarded from the beginning when decoding (in this case,
- * the Vorbis I spec. requires that this is the last packet
- * on its page) */
- prevgranpos = bs;
- if(prevgranpos > granpos)
- prevgranpos = granpos;
- }
- else prevgranpos += bs;
-
- if(prevgranpos >= s->cutpoint && s->packets[0])
- {
- s->packets[1] = save_packet(&packet);
- packet.granulepos = s->cutpoint; /* Set it! This 'truncates' the
- * final packet, as needed. */
- packet.e_o_s = 1;
- ogg_stream_packetin(stream, &packet);
- break;
- }
- if(s->packets[0])
- free_packet(s->packets[0]);
- s->packets[0] = save_packet(&packet);
- ogg_stream_packetin(stream, &packet);
- if(write_pages_to_file(stream,f, 0))
- return -1;
- }
-
- /* Check that we got at least two packets here, which we need later */
- if(!s->packets[0] || !s->packets[1])
- {
- fprintf(stderr, _("Unhandled special case: first file too short?\n"));
- return -1;
- }
-
- if(write_pages_to_file(stream,f, 0))
- return -1;
-
- s->pagegranpos = granpos;
- /* Remaining samples in first packet */
- s->initialgranpos = prevgranpos - s->cutpoint;
-
+ ogg_stream_clear(&s->stream_out);
+ s->output_stream_open = 0;
return 0;
}
-/* Process second stream.
- *
- * We need to do more packet manipulation here, because we need to calculate
- * a new granulepos for every packet, since the old ones are now invalid.
- * Start by placing the modified first and second packets into the stream.
- * Then just proceed through the stream modifying packno and granulepos for
- * each packet, using the granulepos which we track block-by-block.
- */
-static int process_second_stream(vcut_state *s, ogg_stream_state *stream,
- FILE *in, FILE *f)
+/* Closes the output file and stream.
+ * Returns 0 for success, or -1 on failure. */
+static int close_output_file(vcut_state *s)
{
- ogg_packet packet;
- ogg_page page;
- int eos=s->e_o_s;
- int result;
- ogg_int64_t page_granpos, current_granpos=s->initialgranpos;
- ogg_int64_t packetnum=0; /* Should this start from 0 or 3 ? */
+ FILE *out = s->out;
+ if(s->output_stream_open && (close_output_stream(s) != 0))
+ return -1;
- packet.bytes = s->packets[0]->length;
- packet.packet = s->packets[0]->packet;
- packet.b_o_s = 0;
- packet.e_o_s = eos;
- packet.granulepos = 0;
- packet.packetno = packetnum++;
- ogg_stream_packetin(stream,&packet);
-
- if(eos)
+ s->out = NULL;
+ if(!s->drop_output && fclose(out) != 0)
{
- /* Don't write the second file. Normally, we set the granulepos
- * of its second audio packet so audio samples will be discarded
- * from the beginning when decoding; but if that's also the last
- * packet, the samples will be discarded from the end instead,
- * which would corrupt the audio. */
-
- /* We'll still consider this a success; even if we could create
- * such a short file, it would probably be useless. */
- fprintf(stderr, _("Cutpoint too close to end of file."
- " Second file will be empty.\n"));
+ fprintf(stderr, _("Couldn't close output file\n"));
+ return -1;
}
- else
- {
- packet.bytes = s->packets[1]->length;
- packet.packet = s->packets[1]->packet;
- packet.b_o_s = 0;
- packet.e_o_s = 0;
- packet.granulepos = s->initialgranpos;
- packet.packetno = packetnum++;
- ogg_stream_packetin(stream,&packet);
- }
- if(ogg_stream_flush(stream, &page)!=0)
- {
- fwrite(page.header,1,page.header_len,f);
- fwrite(page.body,1,page.body_len,f);
- }
-
- while(ogg_stream_flush(stream, &page)!=0)
- {
- /* Might this happen for _really_ high bitrate modes, if we're
- * spectacularly unlucky? Doubt it, but let's check for it just
- * in case.
- */
- fprintf(stderr, _("ERROR: First two audio packets did not fit into one\n"
- " Ogg page. File may not decode correctly.\n"));
- fwrite(page.header,1,page.header_len,f);
- fwrite(page.body,1,page.body_len,f);
- }
-
- page_granpos = s->pagegranpos - s->cutpoint;
- while(!eos)
- {
- result = ogg_stream_packetout(s->stream_in, &packet);
- if(result==0) /* another page is needed */
- {
- while(!eos)
- {
- result=ogg_sync_pageout(s->sync_in, &page);
- if(result==0) /* need more data */
- {
- if(update_sync(s, in)==0)
- {
- fprintf(stderr,
- _("Update sync returned 0, setting EOS\n"));
- eos=1;
- }
- }
- else if(result==-1)
- fprintf(stderr, _("Recoverable bitstream error\n"));
- else /* got a page */
- {
- ogg_stream_pagein(s->stream_in, &page);
- page_granpos = ogg_page_granulepos(&page)-s->cutpoint;
- break;
- }
- }
- }
- else if(result==-1) fprintf(stderr, _("Bitstream error\n"));
- else /* got a packet */
- {
- int bs = get_blocksize(s, s->vi, &packet);
- current_granpos += bs;
- if(current_granpos > page_granpos)
- {
- current_granpos = page_granpos;
- }
-
- if(packet.e_o_s) eos=1;
- packet.granulepos = current_granpos;
- packet.packetno = packetnum++;
- ogg_stream_packetin(stream, &packet);
- if(write_pages_to_file(stream,f, 0))
- return -1;
- }
- }
-
+ s->drop_output = 0;
return 0;
}
-static void submit_headers_to_stream(ogg_stream_state *stream, vcut_state *s)
+static void submit_headers_to_stream(vcut_state *s)
{
+ vcut_vorbis_stream *vs = &s->vorbis_stream;
int i;
for(i=0;i<3;i++)
{
ogg_packet p;
- p.bytes = s->headers[i]->length;
- p.packet = s->headers[i]->packet;
+ p.bytes = vs->headers[i].length;
+ p.packet = vs->headers[i].packet;
+ assert(p.packet);
p.b_o_s = ((i==0)?1:0);
p.e_o_s = 0;
p.granulepos=0;
- ogg_stream_packetin(stream, &p);
+ ogg_stream_packetin(&s->stream_out, &p);
}
}
-/* Pull out and save the 3 header packets from the input file.
- * If the cutpoint arg was given as seconds, find the number
- * of samples.
- */
-static int process_headers(vcut_state *s)
+/* Writes the header packets to the output stream. These 3 packets must
+ * already be stored in s->vorbis_stream->headers. */
+static void open_output_stream(vcut_state *s)
{
- vorbis_comment vc;
- ogg_page page;
- ogg_packet packet;
- int bytes;
- int i;
- char *buffer;
- ogg_int64_t samples;
+ /* ogg_stream_init should only fail if stream_out is null */
+ int rv = ogg_stream_init(&s->stream_out, ++s->serial_out);
+ assert(rv == 0);
- ogg_sync_init(s->sync_in);
-
- vorbis_info_init(s->vi);
- vorbis_comment_init(&vc);
+ submit_headers_to_stream(s);
+ s->output_stream_open = 1;
+}
- buffer = ogg_sync_buffer(s->sync_in, 4096);
- bytes = fread(buffer, 1, 4096, s->in);
- ogg_sync_wrote(s->sync_in, bytes);
+/* Opens the given output file; or sets s->drop_output if the filename is ".".
+ * Returns 0 for success, or -1 on failure. */
+static int open_output_file(vcut_state *s, char *filename)
+{
+ assert(s->out == NULL);
- if(ogg_sync_pageout(s->sync_in, &page)!=1){
- fprintf(stderr, _("Input not ogg.\n"));
- return -1;
- }
-
- s->serial = ogg_page_serialno(&page);
-
- ogg_stream_init(s->stream_in, s->serial);
-
- if(ogg_stream_pagein(s->stream_in, &page) <0)
+ if(strcmp(filename, ".") == 0)
{
- fprintf(stderr, _("Error in first page\n"));
- return -1;
+ s->out = NULL;
+ s->drop_output = 1;
}
- if(ogg_stream_packetout(s->stream_in, &packet)!=1){
- fprintf(stderr, _("Error in first packet\n"));
- return -1;
- }
-
- if(vorbis_synthesis_headerin(s->vi, &vc, &packet)<0)
+ else
{
- fprintf(stderr, _("Error in primary header: not Vorbis?\n"));
- return -1;
- }
-
- s->headers[0] = save_packet(&packet);
-
- i=0;
- while(i<2)
- {
- while(i<2) {
- int res = ogg_sync_pageout(s->sync_in, &page);
- if(res==0)break;
- if(res==1)
- {
- ogg_stream_pagein(s->stream_in, &page);
- while(i<2)
- {
- res = ogg_stream_packetout(s->stream_in, &packet);
- if(res==0)break;
- if(res<0){
- fprintf(stderr, _("Secondary header corrupt\n"));
- return -1;
- }
- s->headers[i+1] = save_packet(&packet);
- vorbis_synthesis_headerin(s->vi,&vc,&packet);
- i++;
- }
- }
- }
- buffer=ogg_sync_buffer(s->sync_in, 4096);
- bytes=fread(buffer,1,4096,s->in);
- if(bytes==0 && i<2)
- {
- fprintf(stderr, _("EOF in headers\n"));
+ s->out = fopen(filename, "wb");
+ s->drop_output = 0;
+ if(!s->out) {
+ fprintf(stderr, _("Couldn't open %s for writing\n"), filename);
return -1;
}
- ogg_sync_wrote(s->sync_in, bytes);
}
+ return 0;
+}
- vorbis_comment_clear(&vc);
-
- if(s->time) {
- samples = s->cutpoint * s->vi->rate;
- s->cutpoint = samples;
- }
-
+/* Opens an output stream; if necessary, opens the next output file first.
+ * Returns 0 for success, or -1 on failure. */
+static int open_next_output_stream(vcut_state *s)
+{
+ if(!s->out && !s->drop_output)
+ {
+ char *outname;
+ vcut_segment *segment = s->next_segment;
+ if(!segment) return 0;
+
+ outname = segment->filename;
+ s->next_segment = segment->next;
+ free(segment);
+
+ if(open_output_file(s,outname)!=0)
+ return -1;
+ }
+
+ open_output_stream(s);
return 0;
}
int main(int argc, char **argv)
{
- ogg_int64_t cutpoint;
- FILE *in,*out1,*out2;
int ret=0;
- int time=0;
- vcut_state *state;
+ vcut_state state;
+ vcut_segment *seg;
+ memset(&state, 0, sizeof(state));
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
@@ -531,201 +258,398 @@
if(argc<5)
{
- fprintf(stderr,
- _("Usage: vcut infile.ogg outfile1.ogg outfile2.ogg [cutpoint | +cutpoint]\n"));
+ printf(_("Usage: vcut infile.ogg outfile1.ogg"
+ " outfile2.ogg [cutpoint | +cuttime]\n"));
+ printf(_("To avoid creating an output file,"
+ " specify \".\" as its name.\n"));
exit(1);
}
- fprintf(stderr, _("WARNING: vcut is still experimental code.\n"
- "Check that the output files are correct before deleting sources.\n\n"));
-
- in = fopen(argv[1], "rb");
- if(!in) {
+ state.in = fopen(argv[1], "rb");
+ if(!state.in) {
fprintf(stderr, _("Couldn't open %s for reading\n"), argv[1]);
exit(1);
}
- out1 = fopen(argv[2], "wb");
- if(!out1) {
- fprintf(stderr, _("Couldn't open %s for writing\n"), argv[2]);
+
+ seg = vcut_malloc(sizeof(vcut_segment));
+ if(!seg)
exit(1);
- }
- out2 = fopen(argv[3], "wb");
- if(!out2) {
- fprintf(stderr, _("Couldn't open %s for writing\n"), argv[3]);
+ seg->cuttime = -1;
+ seg->cutpoint = -1;
+ seg->filename = argv[2];
+ state.next_segment = seg;
+ seg->next = vcut_malloc(sizeof(vcut_segment));
+
+ seg = seg->next;
+ if(!seg)
exit(1);
- }
+ seg->cuttime = -1;
+ seg->filename = argv[3];
+ seg->next = NULL;
if(strchr(argv[4], '+') != NULL) {
- if(sscanf(argv[4], FORMAT_INT64_TIME, &cutpoint) != 1) {
+ if(sscanf(argv[4], "%lf", &seg->cuttime) != 1) {
fprintf(stderr, _("Couldn't parse cutpoint \"%s\"\n"), argv[4]);
exit(1);
}
- time = 1;
- } else if(sscanf(argv[4], FORMAT_INT64, &cutpoint) != 1) {
+ } else if(sscanf(argv[4], FORMAT_INT64, &seg->cutpoint) != 1) {
fprintf(stderr, _("Couldn't parse cutpoint \"%s\"\n"), argv[4]);
exit(1);
}
- if(time) {
- fprintf(stderr, _("Processing: Cutting at %lld seconds\n"), (long long)cutpoint);
+ if(seg->cuttime >= 0) {
+ printf(_("Processing: Cutting at %lf seconds\n"), seg->cuttime);
} else {
- fprintf(stderr, _("Processing: Cutting at %lld samples\n"), (long long)cutpoint);
+ printf(_("Processing: Cutting at %lld samples\n"),
+ (long long)seg->cutpoint);
}
- state = vcut_new();
+ /* include the PID in the random seed so two instances of vcut that
+ * run in the same second will use different serial numbers */
+ srand(time(NULL) ^ getpid());
+ state.serial_out = rand();
- vcut_set_files(state, in,out1,out2);
- vcut_set_cutpoint(state, cutpoint, time);
-
- if(vcut_process(state))
+ if(vcut_process(&state) != 0)
{
fprintf(stderr, _("Processing failed\n"));
ret = 1;
}
+ vcut_clear(&state);
+ fclose(state.in);
- vcut_free(state);
-
- fclose(in);
- fclose(out1);
- fclose(out2);
-
return ret;
}
-int vcut_process(vcut_state *s)
+/* Returns 0 for success, or -1 on failure. */
+int process_audio_packet(vcut_state *s,
+ vcut_vorbis_stream *vs, ogg_packet *packet)
{
- ogg_stream_state stream_out_first;
- ogg_stream_state stream_out_second;
+ int bs = get_blocksize(vs, packet);
+ long cut_on_eos = 0;
+ ogg_int64_t packet_start_granpos = vs->granulepos;
+ ogg_int64_t gp_to_global_sample_adj;
- /* Read headers in, and save them */
- if(process_headers(s))
+ if(packet->granulepos >= 0)
{
- fprintf(stderr, _("Error reading headers\n"));
- return -1;
+ /* If this is the second audio packet, and our calculated
+ * granule position is greater than the packet's, this means
+ * some audio samples must be discarded from the beginning
+ * when decoding (in this case, the Vorbis I spec. requires
+ * that this be the last packet on its page, so its granulepos
+ * will be available). Likewise, if the last packet's
+ * granulepos is less than expected, samples should be
+ * discarded from the end. */
+
+ if(vs->granulepos == 0 && packet->granulepos != bs)
+ {
+ /* this stream starts at a non-zero granulepos */
+ vs->initial_granpos = packet->granulepos - bs;
+ if(vs->initial_granpos < 0)
+ vs->initial_granpos = 0;
+ }
+ else if(packet->granulepos != (vs->granulepos + bs)
+ && vs->granulepos > 0 && !packet->e_o_s)
+ {
+ fprintf(stderr, _("WARNING: unexpected granulepos "
+ FORMAT_INT64 " (expected " FORMAT_INT64 ")\n"),
+ packet->granulepos, (vs->granulepos + bs));
+ }
+ vs->granulepos = packet->granulepos;
}
+ else if(vs->granulepos == -1)
+ {
+ /* This is the first non-header packet. The next packet
+ * will start at granulepos 0, or will be the last packet
+ * on its page (as mentioned above). */
+ vs->granulepos = 0;
+ }
+ else
+ {
+ vs->granulepos += bs;
+ }
- /* ok, headers are all in, and saved */
- vorbis_synthesis_init(s->vd,s->vi);
- vorbis_block_init(s->vd,s->vb);
+ gp_to_global_sample_adj = s->prevstream_samples - vs->initial_granpos;
+ while(1)
+ {
+ ogg_int64_t rel_cutpoint, rel_sample;
+ if(!s->next_segment) break;
- ogg_stream_init(&stream_out_first,s->serial); /* first file gets original */
- srand(time(NULL));
- ogg_stream_init(&stream_out_second, rand()); /* second gets random */
+ if(s->next_segment->cuttime >= 0)
+ {
+ /* convert cuttime to cutpoint (a sample number) */
+ rel_cutpoint = vs->vi.rate * (s->next_segment->cuttime
+ - s->prevstream_time);
+ }
+ else
+ {
+ if(s->next_segment->cutpoint == -1)
+ break;
+ rel_cutpoint = (s->next_segment->cutpoint
+ - s->prevstream_samples);
+ }
- submit_headers_to_stream(&stream_out_first, s);
- if(write_pages_to_file(&stream_out_first, s->out1, 1))
- return -1;
+ rel_sample = vs->granulepos - vs->initial_granpos;
+ if(rel_sample <= rel_cutpoint)
+ break;
- submit_headers_to_stream(&stream_out_second, s);
- if(write_pages_to_file(&stream_out_second, s->out2, 1))
+ /* reached the cutpoint */
+
+ if(rel_sample - bs > rel_cutpoint)
+ {
+ /* We passed the cutpoint without finding it. This could mean
+ * that the granpos values are discontinuous (in which case
+ * we'd have shown an "Unexpected granulepos" error), or that
+ * the cutpoints are not sorted correctly. */
+ fprintf(stderr, _("Cutpoint not found\n"));
+ return -1;
+ }
+
+ if(rel_sample < bs && !s->drop_output)
+ {
+ fprintf(stderr, _("Can't produce a file starting"
+ " and ending between sample positions " FORMAT_INT64
+ " and " FORMAT_INT64 "\n"),
+ packet_start_granpos + gp_to_global_sample_adj - 1,
+ vs->granulepos + gp_to_global_sample_adj);
+ return -1;
+ }
+
+ /* Set it! This 'truncates' the final packet, as needed. */
+ packet->granulepos = rel_cutpoint;
+ cut_on_eos = packet->e_o_s;
+ packet->e_o_s = 1;
+ if(write_packet(s, packet) != 0)
+ return -1;
+ if(close_output_file(s) != 0)
+ return -1;
+
+ packet->granulepos = vs->granulepos - rel_cutpoint;
+ vs->samples_cut = rel_cutpoint;
+ packet->e_o_s = cut_on_eos;
+
+ if(vs->last_packet.packet && vs->granulepos != rel_cutpoint)
+ {
+ ogg_packet ref_packet;
+ ref_packet.bytes = vs->last_packet.length;
+ ref_packet.packet = vs->last_packet.packet;
+ ref_packet.b_o_s = 0;
+ ref_packet.e_o_s = 0;
+ ref_packet.granulepos = 0;
+
+ if(write_packet(s, &ref_packet) != 0)
+ return -1;
+ }
+ else break;
+ }
+
+ /* We need to save the last packet in the first
+ * stream - but we don't know when we're going
+ * to get there. So we have to keep every packet
+ * just in case. */
+ if(save_packet(packet, &vs->last_packet) != 0)
return -1;
- if(process_first_stream(s, &stream_out_first, s->in, s->out1))
+ /* write the packet (header or audio) to the output stream */
+ if(vs->granulepos > 0)
{
- fprintf(stderr, _("Error writing first output file\n"));
- return -1;
+ packet->granulepos = vs->granulepos
+ - vs->initial_granpos - vs->samples_cut;
+ if(packet->granulepos < bs && cut_on_eos && !s->drop_output)
+ {
+ fprintf(stderr, _("Can't produce a file starting between sample"
+ " positions " FORMAT_INT64 " and " FORMAT_INT64 ".\n"),
+ packet_start_granpos + gp_to_global_sample_adj - 1,
+ vs->granulepos + gp_to_global_sample_adj);
+ fprintf(stderr, _("Specify \".\" as the second output file"
+ " to suppress this error.\n"));
+ return -1;
+ }
}
+ return write_packet(s, packet);
+}
- ogg_stream_clear(&stream_out_first);
+/* Writes a packet, opening an output stream/file if necessary.
+ * Returns 0 for success, or -1 on failure. */
+int write_packet(vcut_state *s, ogg_packet *packet)
+{
+ int flush;
+ if(!s->output_stream_open && open_next_output_stream(s) != 0)
+ return -1;
- if(process_second_stream(s, &stream_out_second, s->in, s->out2))
+ flush = (s->stream_out.packetno == 4 && packet->granulepos != -1)
+ || packet->e_o_s;
+ ogg_stream_packetin(&s->stream_out, packet);
+
+ if(write_pages_to_file(&s->stream_out, s->out, flush) != 0)
{
- fprintf(stderr, _("Error writing second output file\n"));
+ fprintf(stderr, _("Couldn't write packet to output file\n"));
return -1;
}
- ogg_stream_clear(&stream_out_second);
+ if(packet->e_o_s && close_output_stream(s) != 0)
+ return -1;
+
return 0;
}
-vcut_state *vcut_new(void)
+/* Returns 0 for success, or -1 on failure. */
+int process_page(vcut_state *s, ogg_page *page)
{
- vcut_state *s = malloc(sizeof(vcut_state));
- memset(s,0,sizeof(vcut_state));
+ int headercount;
+ int result;
+ vcut_vorbis_stream *vs = &s->vorbis_stream;
- s->sync_in = malloc(sizeof(ogg_sync_state));
- s->stream_in = malloc(sizeof(ogg_stream_state));
- s->vd = malloc(sizeof(vorbis_dsp_state));
- s->vi = malloc(sizeof(vorbis_info));
- s->vb = malloc(sizeof(vorbis_block));
+ if(!s->vorbis_init)
+ {
+ if(!ogg_page_bos(page))
+ {
+ fprintf(stderr, _("BOS not set on first page of stream\n"));
+ return -1;
+ }
- s->headers = malloc(sizeof(vcut_packet)*3);
- memset(s->headers, 0, sizeof(vcut_packet)*3);
- s->packets = malloc(sizeof(vcut_packet)*2);
- memset(s->packets, 0, sizeof(vcut_packet)*2);
+ memset(vs, 0, sizeof(*vs));
+ vs->serial = ogg_page_serialno(page);
+ vs->granulepos = -1;
+ vs->initial_granpos = 0;
+ ogg_stream_init(&vs->stream_in, vs->serial);
+ vorbis_info_init(&vs->vi);
+ vorbis_comment_init(&vs->vc);
+ s->vorbis_init = 1;
+ }
+ else if(vs->serial != ogg_page_serialno(page))
+ {
+ fprintf(stderr, _("Multiplexed bitstreams are not supported\n"));
+ return -1;
+ }
- return s;
-}
+ /* count the headers */
+ for(headercount = 0; headercount < 3; ++headercount)
+ if(!vs->headers[headercount].packet) break;
-/* Full cleanup of internal state and vorbis/ogg structures */
-void vcut_free(vcut_state *s)
-{
- if(s)
+ result = ogg_stream_pagein(&vs->stream_in, page);
+ while(1)
{
- if(s->packets)
- {
- if(s->packets[0])
- free_packet(s->packets[0]);
- if(s->packets[1])
- free_packet(s->packets[1]);
- free(s->packets);
- }
+ ogg_packet packet;
+ result = ogg_stream_packetout(&vs->stream_in, &packet);
- if(s->headers)
+ if(result==0) break;
+ else if(result==-1)
{
- int i;
- for(i=0; i < 3; i++)
- if(s->headers[i])
- free_packet(s->headers[i]);
- free(s->headers);
+ if(headercount < 3)
+ {
+ fprintf(stderr, _("Header packet corrupt\n"));
+ return -1;
+ }
+ else
+ {
+ if(!s->input_corrupt)
+ fprintf(stderr, _("Bitstream error, continuing\n"));
+ s->input_corrupt = 1;
+ continue;
+ }
}
- if(s->vb)
+ if(headercount < 3) /* this is a header packet */
{
- vorbis_block_clear(s->vb);
- free(s->vb);
+ if(vorbis_synthesis_headerin(&vs->vi, &vs->vc, &packet)<0)
+ {
+ fprintf(stderr, _("Error in header: not vorbis?\n"));
+ return -1;
+ }
+
+ if(save_packet(&packet, &vs->headers[headercount]) != 0)
+ return -1;
+
+ ++headercount;
}
- if(s->vd)
+ else /* this is an audio (non-header) packet */
{
- vorbis_dsp_clear(s->vd);
- free(s->vd);
+ result = process_audio_packet(s, vs, &packet);
+ if(result != 0)
+ return result;
}
- if(s->vi)
+ }
+
+ if(ogg_page_eos(page))
+ {
+ if(vs->granulepos >= 0)
{
- vorbis_info_clear(s->vi);
- free(s->vi);
+ ogg_int64_t samples = vs->granulepos - vs->initial_granpos;
+ s->prevstream_samples += samples;
+ s->prevstream_time += (double)samples / vs->vi.rate;
}
- if(s->stream_in)
+ vcut_vorbis_clear(vs);
+ s->vorbis_init = 0;
+ }
+
+ return 0;
+}
+
+/* Returns 0 for success, or -1 on failure. */
+int vcut_process(vcut_state *s)
+{
+ int first_page = 1;
+ do
+ {
+ ogg_page page;
+ int result;
+
+ while((result = ogg_sync_pageout(&s->sync_in, &page)) == 1)
{
- ogg_stream_clear(s->stream_in);
- free(s->stream_in);
+ if(process_page(s, &page) != 0)
+ return -1;
}
- if(s->sync_in)
+
+ if(result < 0 && !s->input_corrupt)
{
- ogg_sync_clear(s->sync_in);
- free(s->sync_in);
+ if(first_page)
+ {
+ fprintf(stderr, _("Input not ogg.\n"));
+ return -1;
+ }
+
+ fprintf(stderr, _("Page error, continuing\n"));
+ /* continue, but don't print this error again */
+ s->input_corrupt = 1;
}
+ first_page = 0;
+ }
+ while(update_sync(s) > 0);
- free(s);
+ if(s->vorbis_init)
+ {
+ fprintf(stderr, _("WARNING: input file ended unexpectedly\n"));
}
+ else if(s->next_segment)
+ {
+ fprintf(stderr, _("WARNING: found EOS before cutpoint\n"));
+ }
+ return close_output_file(s);
}
-void vcut_set_files(vcut_state *s, FILE *in, FILE *out1, FILE *out2)
+void vcut_vorbis_clear(vcut_vorbis_stream *vs)
{
- s->in = in;
- s->out1 = out1;
- s->out2 = out2;
-}
+ int i;
+ clear_packet(&vs->last_packet);
-void vcut_set_cutpoint(vcut_state *s, ogg_int64_t cutpoint, int time)
-{
- s->cutpoint = cutpoint;
- s->time = time;
+ for(i=0; i < 3; i++)
+ clear_packet(&vs->headers[i]);
+
+ vorbis_block_clear(&vs->vb);
+ vorbis_dsp_clear(&vs->vd);
+ vorbis_info_clear(&vs->vi);
+ ogg_stream_clear(&vs->stream_in);
}
-void vcut_time_to_samples(ogg_int64_t *time, ogg_int64_t *samples, FILE *in)
+/* Full cleanup of internal state and vorbis/ogg structures */
+void vcut_clear(vcut_state *s)
{
-
+ if(s->vorbis_init)
+ {
+ vcut_vorbis_clear(&s->vorbis_stream);
+ s->vorbis_init = 0;
+ }
+ ogg_sync_clear(&s->sync_in);
}
Modified: trunk/vorbis-tools/vcut/vcut.h
===================================================================
--- trunk/vorbis-tools/vcut/vcut.h 2008-11-09 12:50:37 UTC (rev 15514)
+++ trunk/vorbis-tools/vcut/vcut.h 2008-11-09 22:34:21 UTC (rev 15515)
@@ -10,29 +10,69 @@
unsigned char *packet;
} vcut_packet;
+/* this structure stores data associated with a single input stream; it will be
+ cleared between streams if the input file has multiple chained streams */
typedef struct {
- ogg_sync_state *sync_in;
- ogg_stream_state *stream_in;
- vorbis_dsp_state *vd;
- vorbis_block *vb;
- vorbis_info *vi;
+ ogg_stream_state stream_in;
+ vorbis_dsp_state vd;
+ vorbis_block vb;
+ vorbis_info vi;
+ vorbis_comment vc;
int prevW;
- ogg_int64_t initialgranpos;
- ogg_int64_t pagegranpos;
- int e_o_s;
- int time;
- ogg_int64_t cutpoint;
+
+ /* granulepos is -1 before any packets are seen, and 0 after the first
+ packet; otherwise it's the GP of the last sample seen */
+ ogg_int64_t granulepos;
+
+ /* the granulepos of the first sample (>= 0, since samples with a negative
+ GP are discarded); always 0 for files produced by oggenc or vcut, but
+ may be > 0 for data recorded from a stream (for example) */
+ ogg_int64_t initial_granpos;
+
+ /* the number of samples already cut from this stream (all
+ granule positions */
+ ogg_int64_t samples_cut;
+
unsigned int serial;
- vcut_packet **headers; /* 3 */
- vcut_packet **packets; /* 2 */
+ vcut_packet headers[3];
+ vcut_packet last_packet;
+} vcut_vorbis_stream;
- FILE *in,*out1,*out2;
+typedef struct vcut_segment {
+ double cuttime; /* number of seconds at which to cut;
+ -1 if cutting by sample number */
+ ogg_int64_t cutpoint; /* sample number at which to cut */
+ char *filename; /* name of the file to contain data after the CP */
+ struct vcut_segment *next; /* data for next cut, or NULL */
+} vcut_segment;
+
+typedef struct {
+ /* pointer to a linked list of segments/cutpoints; the first element has
+ cuttime == cutpoint == -1 and represents the data before any cutpoint */
+ vcut_segment *next_segment;
+
+ /* the input file may have multiple chained streams; these variables store
+ the number of samples and seconds that passed before the beginning of
+ the current stream */
+ ogg_int64_t prevstream_samples; /* # of samples in prev. streams */
+ double prevstream_time; /* # of seconds past before this stream */
+
+ FILE *in;
+ ogg_sync_state sync_in;
+ int input_corrupt; /* 1 if we've complained about corruption */
+ int vorbis_init; /* 1 if vorbis_stream initialized */
+ vcut_vorbis_stream vorbis_stream;
+
+ FILE *out;
+ int drop_output; /* 1 if we don't want any output */
+ int output_stream_open; /* 1 if stream_out initialized */
+ ogg_stream_state stream_out;
+ unsigned int serial_out; /* serial # for the next output stream */
} vcut_state;
int vcut_process(vcut_state *state);
-void vcut_set_files(vcut_state *s, FILE *in, FILE *out1, FILE *out2);
-void vcut_set_cutpoint(vcut_state *s, ogg_int64_t cutpoint, int time);
-vcut_state *vcut_new(void);
-void vcut_free(vcut_state *state);
+void vcut_clear(vcut_state *state);
+void vcut_vorbis_clear(vcut_vorbis_stream *state);
+int write_packet(vcut_state *s, ogg_packet *packet);
#endif /* __VCUT_H */
More information about the commits
mailing list