vcut.c 14.8 KB
Newer Older
1 2 3
/* This program is licensed under the GNU General Public License, version 2,
 * a copy of which is included with this program.
 *
4
 * (c) 2000-2001 Michael Smith <msmith@xiph.org>
5 6 7 8 9
 *
 *
 * Simple application to cut an ogg at a specified frame, and produce two
 * output files.
 *
10
 * last modified: $Id: vcut.c,v 1.9 2003/09/03 07:58:05 calc Exp $
11 12
 */

13 14 15 16
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

17 18 19 20 21 22 23 24 25 26
#include <stdio.h>
#include <stdlib.h>
#include <ogg/ogg.h>
#include <vorbis/codec.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#include "vcut.h"

27 28 29
#include <locale.h>
#include "i18n.h"

30
#ifdef _WIN32
31 32
#define FORMAT_INT64	  "%I64d"
#define FORMAT_INT64_TIME "+%I64d"
33
#else
34 35
#define FORMAT_INT64	  "%lld"
#define FORMAT_INT64_TIME "+%lld"
36
#endif
37

38
static vcut_packet *save_packet(ogg_packet *packet)
39
{
40
	vcut_packet *p = malloc(sizeof(vcut_packet));
Michael Smith's avatar
Michael Smith committed
41

42 43 44
	p->length = packet->bytes;
	p->packet = malloc(p->length);
	memcpy(p->packet, packet->packet, p->length);
45

46 47
	return p;
}
48

49 50 51
static void free_packet(vcut_packet *p)
{
	if(p)
52
	{
53 54 55
		if(p->packet)
			free(p->packet);
		free(p);
56
	}
57
}
58

59 60 61 62
static long get_blocksize(vcut_state *s, vorbis_info *vi, ogg_packet *op)
{
	int this = vorbis_packet_blocksize(vi, op);
	int ret = (this+s->prevW)/4;
63

64
	s->prevW = this;
Michael Smith's avatar
Michael Smith committed
65
	return ret;
66 67
}

68
static int update_sync(vcut_state *s, FILE *f)
69
{
70 71 72 73 74
	unsigned char *buffer = ogg_sync_buffer(s->sync_in, 4096);
	int bytes = fread(buffer,1,4096,f);
	ogg_sync_wrote(s->sync_in, bytes);
	return bytes;
}
Michael Smith's avatar
Michael Smith committed
75

76 77 78 79 80
/* 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;
81

82
	if(flush)
Michael Smith's avatar
Michael Smith committed
83
	{
84 85 86 87 88 89 90
		while(ogg_stream_flush(stream, &page))
		{
			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)
				return -1;
		}
Michael Smith's avatar
Michael Smith committed
91
	}
92
	else
Michael Smith's avatar
Michael Smith committed
93
	{
94 95 96 97 98 99 100
		while(ogg_stream_pageout(stream, &page))
		{
			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)
				return -1;
		}
Michael Smith's avatar
Michael Smith committed
101 102
	}

103 104 105 106
	return 0;
}


107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
/* 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, 
Michael Smith's avatar
Michael Smith committed
123
		FILE *in, FILE *f)
124 125 126 127 128 129 130 131 132 133 134 135 136
{
	int eos=0;
	ogg_page page;
	ogg_packet packet;
	ogg_int64_t granpos, prevgranpos;
	int result;

	while(!eos)
	{
		while(!eos)
		{
			int result = ogg_sync_pageout(s->sync_in, &page);
			if(result==0) break;
137
			else if(result<0) fprintf(stderr, _("Page error. Corrupt input.\n"));
138 139 140 141 142 143 144 145 146 147 148
			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);

149 150
						/* throw away result, but update state */
						get_blocksize(s,s->vi,&packet);
151 152 153

						if(result==0) break;
						else if(result==-1)
154
							fprintf(stderr, _("Bitstream error, continuing\n"));
155 156
						else
						{
157 158 159 160 161 162 163 164 165
							/* 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);

166
							ogg_stream_packetin(stream, &packet);
167 168
							if(write_pages_to_file(stream, f,0))
								return -1;
169 170 171 172 173
						}
					}
					prevgranpos = granpos;
				}
				else
174 175
					eos=1; /* First stream ends somewhere in this page.
							  We break of out this loop here. */
176 177 178

				if(ogg_page_eos(&page))
				{
179
					fprintf(stderr, _("Found EOS before cut point.\n"));
180 181 182 183 184 185
					eos=1;
				}
			}
		}
		if(!eos)
		{
186
			if(update_sync(s,in)==0) 
187
			{
188
				fprintf(stderr, _("Setting eos: update sync returned 0\n"));
189 190 191 192 193
				eos=1;
			}
		}
	}

Michael Smith's avatar
Michael Smith committed
194 195 196 197
	/* Now, check to see if we reached a real EOS */
	if(granpos < s->cutpoint)
	{
		fprintf(stderr, 
198
				_("Cutpoint not within stream. Second file will be empty\n"));
199 200
		write_pages_to_file(stream, f,0);

Michael Smith's avatar
Michael Smith committed
201 202 203
		return -1;
	}

204 205
	while((result = ogg_stream_packetout(s->stream_in, &packet))!=0)
	{
Michael Smith's avatar
Michael Smith committed
206 207
		int bs;
		
208
		bs = get_blocksize(s, s->vi, &packet);
209 210 211 212
		prevgranpos += bs;

		if(prevgranpos > s->cutpoint)
		{
213 214 215
			s->packets[1] = save_packet(&packet);
			packet.granulepos = s->cutpoint; /* Set it! This 'truncates' the 
											  * final packet, as needed. */
216 217 218 219
			packet.e_o_s = 1;
			ogg_stream_packetin(stream, &packet);
			break;
		}
220 221 222
		if(s->packets[0])
			free_packet(s->packets[0]);
		s->packets[0] = save_packet(&packet);
223
		ogg_stream_packetin(stream, &packet);
224 225
		if(write_pages_to_file(stream,f, 0))
			return -1;
226 227
	}

228
	/* Check that we got at least two packets here, which we need later */
Michael Smith's avatar
Michael Smith committed
229 230
	if(!s->packets[0] || !s->packets[1])
	{
231
		fprintf(stderr, _("Unhandled special case: first file too short?\n"));
Michael Smith's avatar
Michael Smith committed
232 233 234
		return -1;
	}

235 236
	if(write_pages_to_file(stream,f, 0))
		return -1;
237 238 239 240

	/* Remaining samples in first packet */
	s->initialgranpos = prevgranpos - s->cutpoint; 

Michael Smith's avatar
Michael Smith committed
241
	return 0;
242 243
}

244 245 246 247 248 249 250 251 252 253
/* 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)
254 255 256 257 258 259
{
	ogg_packet packet;
	ogg_page page;
	int eos=0;
	int result;
	ogg_int64_t page_granpos, current_granpos=s->initialgranpos;
260
	ogg_int64_t packetnum=0; /* Should this start from 0 or 3 ? */
261 262 263 264 265 266

	packet.bytes = s->packets[0]->length;
	packet.packet = s->packets[0]->packet;
	packet.b_o_s = 0;
	packet.e_o_s = 0;
	packet.granulepos = 0;
267
	packet.packetno = packetnum++; 
268 269 270 271 272 273 274 275 276 277
	ogg_stream_packetin(stream,&packet);

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

278 279 280 281 282 283
	if(ogg_stream_flush(stream, &page)!=0)
	{
		fwrite(page.header,1,page.header_len,f);
		fwrite(page.body,1,page.body_len,f);
	}

284 285
	while(ogg_stream_flush(stream, &page)!=0)
	{
286 287 288 289
		/* Might this happen for _really_ high bitrate modes, if we're
		 * spectacularly unlucky? Doubt it, but let's check for it just
		 * in case.
		 */
290 291
		fprintf(stderr, _("ERROR: First two audio packets did not fit into one\n"
				        "       ogg page. File may not decode correctly.\n"));
292 293 294 295 296 297 298 299 300 301 302
		fwrite(page.header,1,page.header_len,f);
		fwrite(page.body,1,page.body_len,f);
	}

	while(!eos)
	{
		while(!eos)
		{
			result=ogg_sync_pageout(s->sync_in, &page);
			if(result==0) break;
			else if(result==-1)
303
				fprintf(stderr, _("Recoverable bitstream error\n"));
304 305 306 307 308 309 310 311 312
			else
			{
				page_granpos = ogg_page_granulepos(&page) - s->cutpoint;
				if(ogg_page_eos(&page))eos=1;
				ogg_stream_pagein(s->stream_in, &page);
				while(1)
				{
					result = ogg_stream_packetout(s->stream_in, &packet);
					if(result==0) break;
313
					else if(result==-1) fprintf(stderr, _("Bitstream error\n"));
314 315
					else
					{
316
						int bs = get_blocksize(s, s->vi, &packet);
317 318 319 320 321 322 323 324 325
						current_granpos += bs;
						if(current_granpos > page_granpos)
						{
							current_granpos = page_granpos;
						}

						packet.granulepos = current_granpos;
						packet.packetno = packetnum++;
						ogg_stream_packetin(stream, &packet);
326 327
						if(write_pages_to_file(stream,f, 0))
							return -1;
328 329 330 331 332 333
					}
				}
			}
		}
		if(!eos)
		{
334
			if(update_sync(s, in)==0)
335
			{
336
				fprintf(stderr, _("Update sync returned 0, setting eos\n"));
337 338 339 340
				eos=1;
			}
		}
	}
Michael Smith's avatar
Michael Smith committed
341 342

	return 0;
343 344
}			

345
static void submit_headers_to_stream(ogg_stream_state *stream, vcut_state *s) 
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
{
	int i;
	for(i=0;i<3;i++)
	{
		ogg_packet p;
		p.bytes = s->headers[i]->length;
		p.packet = s->headers[i]->packet;
		p.b_o_s = ((i==0)?1:0);
		p.e_o_s = 0;
		p.granulepos=0;

		ogg_stream_packetin(stream, &p);
	}
}
									
361
/* Pull out and save the 3 header packets from the input file.
362 363
 * If the cutpoint arg was given as seconds, find the number
 * of samples.
364 365
 */
static int process_headers(vcut_state *s)
366 367 368 369 370 371 372
{
	vorbis_comment vc;
	ogg_page page;
	ogg_packet packet;
	int bytes;
	int i;
	unsigned char *buffer;
373
	ogg_int64_t samples;
374 375 376 377 378 379 380

	ogg_sync_init(s->sync_in);
	
	vorbis_info_init(s->vi);
	vorbis_comment_init(&vc);

	buffer = ogg_sync_buffer(s->sync_in, 4096);
381
	bytes = fread(buffer, 1, 4096, s->in);
382 383 384
	ogg_sync_wrote(s->sync_in, bytes);

	if(ogg_sync_pageout(s->sync_in, &page)!=1){
385
		fprintf(stderr, _("Input not ogg.\n"));
Michael Smith's avatar
Michael Smith committed
386
		return -1;
387 388 389 390 391 392 393 394
	}

	s->serial = ogg_page_serialno(&page);

	ogg_stream_init(s->stream_in, s->serial);

	if(ogg_stream_pagein(s->stream_in, &page) <0)
	{
395
		fprintf(stderr, _("Error in first page\n"));
Michael Smith's avatar
Michael Smith committed
396
		return -1;
397 398 399
	}

	if(ogg_stream_packetout(s->stream_in, &packet)!=1){
400
		fprintf(stderr, _("error in first packet\n"));
Michael Smith's avatar
Michael Smith committed
401
		return -1;
402 403 404 405
	}

	if(vorbis_synthesis_headerin(s->vi, &vc, &packet)<0)
	{
406
		fprintf(stderr, _("Error in primary header: not vorbis?\n"));
Michael Smith's avatar
Michael Smith committed
407
		return -1;
408 409
	}

410
	s->headers[0] = save_packet(&packet);
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425

	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){
426
						fprintf(stderr, _("Secondary header corrupt\n"));
Michael Smith's avatar
Michael Smith committed
427
						return -1;
428
					}
429
					s->headers[i+1] = save_packet(&packet);
430 431 432 433 434 435
					vorbis_synthesis_headerin(s->vi,&vc,&packet);
					i++;
				}
			}
		}
		buffer=ogg_sync_buffer(s->sync_in, 4096);
436
		bytes=fread(buffer,1,4096,s->in);
437 438
		if(bytes==0 && i<2)
		{
439
			fprintf(stderr, _("EOF in headers\n"));
Michael Smith's avatar
Michael Smith committed
440
			return -1;
441 442 443
		}
		ogg_sync_wrote(s->sync_in, bytes);
	}
Michael Smith's avatar
Michael Smith committed
444 445 446

	vorbis_comment_clear(&vc);

447 448 449 450 451
	if(s->time) {
	  samples = s->cutpoint * s->vi->rate;
	  s->cutpoint = samples;
	}

Michael Smith's avatar
Michael Smith committed
452
	return 0;
453 454
}

455 456

int main(int argc, char **argv)
457
{
458
	ogg_int64_t cutpoint;
459
	ogg_int64_t cutpoint_secs = 0;
460 461
	FILE *in,*out1,*out2;
	int ret=0;
462
	int time=0;
463
	vcut_state *state;
464

465 466 467 468
	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);

469 470 471
	if(argc<5)
	{
		fprintf(stderr, 
472
				_("Usage: vcut infile.ogg outfile1.ogg outfile2.ogg [cutpoint | +cutpoint]\n"));
473 474
		exit(1);
	}
475

476 477
	fprintf(stderr, _("WARNING: vcut is still experimental code.\n"
		"Check that the output files are correct before deleting sources.\n\n"));
478 479 480

	in = fopen(argv[1], "rb");
	if(!in) {
481
		fprintf(stderr, _("Couldn't open %s for reading\n"), argv[1]);
482 483 484 485
		exit(1);
	}
	out1 = fopen(argv[2], "wb");
	if(!out1) {
486
		fprintf(stderr, _("Couldn't open %s for writing\n"), argv[2]);
487 488 489 490
		exit(1);
	}
	out2 = fopen(argv[3], "wb");
	if(!out2) {
491
		fprintf(stderr, _("Couldn't open %s for writing\n"), argv[3]);
492 493 494
		exit(1);
	}

495 496 497 498 499 500 501 502 503 504
	if(strchr(argv[4], '+') != NULL) {
	  if(sscanf(argv[4], FORMAT_INT64_TIME, &cutpoint) != 1) {
	    fprintf(stderr, _("Couldn't parse cutpoint \"%s\"\n"), argv[4]);
            exit(1);
	  }
	  time = 1;
	} else if(sscanf(argv[4], FORMAT_INT64, &cutpoint) != 1) {
	    fprintf(stderr, _("Couldn't parse cutpoint \"%s\"\n"), argv[4]);
            exit(1);
	}
505

506 507 508 509 510
	if(time) {
	  fprintf(stderr, _("Processing: Cutting at %lld seconds\n"), cutpoint);
	} else {
	  fprintf(stderr, _("Processing: Cutting at %lld samples\n"), cutpoint);
	}
511 512 513 514

	state = vcut_new();

	vcut_set_files(state, in,out1,out2);
515
	vcut_set_cutpoint(state, cutpoint, time);
516 517 518

	if(vcut_process(state))
	{
519
		fprintf(stderr, _("Processing failed\n"));
520 521 522
		ret = 1;
	}

523

524 525 526 527 528 529 530
	vcut_free(state);

	fclose(in);
	fclose(out1);
	fclose(out2);

	return ret;
531 532
}

533
int vcut_process(vcut_state *s)
534
{
535 536 537 538 539 540
	ogg_stream_state  stream_out_first;
	ogg_stream_state  stream_out_second;

	/* Read headers in, and save them */
	if(process_headers(s))
	{
541
		fprintf(stderr, _("Error reading headers\n"));
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
		return -1;
	}

	/* ok, headers are all in, and saved */
	vorbis_synthesis_init(s->vd,s->vi);
	vorbis_block_init(s->vd,s->vb);

	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 */

	submit_headers_to_stream(&stream_out_first, s);
	if(write_pages_to_file(&stream_out_first, s->out1, 1))
		return -1;

	submit_headers_to_stream(&stream_out_second, s);
	if(write_pages_to_file(&stream_out_second, s->out2, 1))
		return -1;

	
	if(process_first_stream(s, &stream_out_first, s->in, s->out1))
	{
564
		fprintf(stderr, _("Error writing first output file\n"));
565 566
		return -1;
	}
567

568 569 570
	ogg_stream_clear(&stream_out_first);

	if(process_second_stream(s, &stream_out_second, s->in, s->out2))
571
	{
572
		fprintf(stderr, _("Error writing second output file\n"));
573
		return -1;
574
	}
575 576 577
	ogg_stream_clear(&stream_out_second);

	return 0;
578 579
}

580
vcut_state *vcut_new(void)
581
{
582 583
	vcut_state *s = malloc(sizeof(vcut_state));
	memset(s,0,sizeof(vcut_state));
584

585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
	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));

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

	return s;
}

/* Full cleanup of internal state and vorbis/ogg structures */
void vcut_free(vcut_state *s)
{
	if(s)
603
	{
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
		if(s->packets)
		{
			if(s->packets[0])
				free_packet(s->packets[0]);
			if(s->packets[1])
				free_packet(s->packets[1]);
			free(s->packets);
		}

		if(s->headers)
		{
			int i;
			for(i=0; i < 3; i++)
				if(s->headers[i])
					free_packet(s->headers[i]);
			free(s->headers);
		}

		if(s->vb)
		{
			vorbis_block_clear(s->vb);
			free(s->vb);
		}
		if(s->vd)
		{
			vorbis_dsp_clear(s->vd);
			free(s->vd);
		}
		if(s->vi)
		{
			vorbis_info_clear(s->vi);
			free(s->vi);
		}
		if(s->stream_in)
		{
			ogg_stream_clear(s->stream_in);
			free(s->stream_in);
		}
		if(s->sync_in)
		{
			ogg_sync_clear(s->sync_in);
			free(s->sync_in);
		}

		free(s);
649 650 651
	}
}

652 653 654 655 656 657 658
void vcut_set_files(vcut_state *s, FILE *in, FILE *out1, FILE *out2)
{
	s->in = in;
	s->out1 = out1;
	s->out2 = out2;
}

659
void vcut_set_cutpoint(vcut_state *s, ogg_int64_t cutpoint, int time)
660 661
{
	s->cutpoint = cutpoint;
662
	s->time = time;
663
}
664

665 666
void vcut_time_to_samples(ogg_int64_t *time, ogg_int64_t *samples, FILE *in)
{
667

668
}
669