#!/usr/bin/perl
#
# -----------------------------------------------------------------------------
#
# zzzsplitstream -- split Ogg stream into separate files for each track
# Copyright (C) 2003 Fabian "zzznowman" Pietsch
# Licensed under GPLv2 or, at your option, any later version
#
# -----------------------------------------------------------------------------
#
# Version 0.1.4 (2003-12-21)
# ChangeLog:
#  o v0.1.1	initial release
#  o v0.1.2	Windows related fix: read in entire stream at once
#    		because of input record separator limitations in ActivePerl
#  o v0.1.3	return to nearly unmodified v0.1.1 source; binmode... -_-
#  o v0.1.4	added command line switches:
#    		-d	drop first & last file (almost certainly incomplete)
#    		-F	drop only the first file
#    		-L	drop only the last file
#		care has been taken to let "-" (to indicate input from stdin)
#    		still work (it stops option processing, though...)
#

use strict;
use IO::File;


# [[ parameters ]]
my $signature = 'OggS';
my ($dropfirst, $droplast) = (0, 0);

# Windows hack... -_-
# (on Linux, this can easily be done on a per-handle basis...)
$/ = $signature;


# [[ functions ]]
# mychomp: like chomp(), but with non-"\n"-delimiter
sub mychomp($$) {
	my $len = length($_[0]);
	my $dellen = length($_[1]);
	return unless (substr($_[0], $len - $dellen, $dellen) eq $_[1]);
	substr($_[0], $len - $dellen, undef, '');
}

# writetrack: write extracted track to disk
sub writetrack($$$) {
	my ($streamp, $trackidx, $trackdataref) = @_;

	# skip first track ($trackidx == 1) and any leading garbage ($trackidx == 0)
	# if user wishes so / $dropfirst is set
	if ($dropfirst && ($trackidx < 2)) {
		if ($trackidx == 0) {
			print("$streamp: skipping leading garbage\n");
		}
		else {
			print("$streamp: skipping track $trackidx\n");
		}
		return(1);
	}

	# leading garbage?
	if ($trackidx == 0) {
		print("$streamp: warning: leading garbage. saving as track $trackidx...\n");
	}

	# generate track name
	(my $trackp = ($streamp eq '-' ? 'StdIn' : $streamp)) =~ s/\.ogg$//;
	$trackp .= '.Track' . $trackidx . '.ogg';

	# check if the output file is already there
	if (-e $trackp) {
		print(STDERR "$streamp: $trackp already exists! skipping\n");
		return undef;
	}
	print("$streamp: writing $trackp...\n");

	# open file for track content
	my $track = IO::File->new("> $trackp");
	unless (defined($track)) {
		print(STDERR "$streamp: can't open $trackp: $!\n");
		return undef;
	}
	# support broken Windows environments...
	binmode($track);

	# output extracted track
	$track->print($$trackdataref);
	$track->close;
	return 1;
}


# [[ core ]]
# process command line options
while ($_ = $ARGV[0], /^-.+/) {
	shift;
	last if /^--$/;
	if (/^-d$/)	{ ($dropfirst, $droplast) = (1, 1); next; }
	if (/^-F$/)	{ $dropfirst = 1; next; }
	if (/^-L$/)	{ $droplast = 1; next; }
	die("Invalid option $_");
}

# process every remaining argument on command line as stream input file
foreach my $streamp (@ARGV) {
	print("Processing $streamp...\n");

	# open file
	my $stream = IO::File->new("< $streamp");
	unless (defined($stream)) {
		warn("Can't open $streamp: $!");
		next;
	}
	# support broken Windows environments...
	binmode($stream);
	#$stream->input_record_separator($signature);

	# check if it's really an Ogg stream
	my $buffer;
	unless ($stream->read($buffer, length($signature))) {
		print(STDERR "Can't read $streamp -- skipping\n");
		next;
	}
	unless ($buffer eq $signature) {
		print(STDERR "No Ogg signature found in $streamp -- skipping\n");
		next;
	}
	undef $buffer;

	# split the stream
	my ($trackidx, $trackdata) = (0, '');
	while (my $sequence = <$stream>) {
		# erase trailing "OggS" from sequence
		mychomp($sequence, $signature);

		# beginning of new track?
		my $byte = substr($sequence, 1, 1);
		unless (vec($byte, 1, 1)) {
			# still the same track; put the sequences together until next track
			$trackdata .= $signature . $sequence;
			next;
		}

		# write extracted track if data has been collected yet
		if (length($trackdata)) {
			writetrack($streamp, $trackidx, \$trackdata);
			$trackdata = '';
		}

		# start extracting next track
		$trackidx++; $trackdata = $signature . $sequence;
		print("$streamp: extracting track $trackidx...\n");
	}
	$stream->close;
	undef $stream;

	# write last extracted track (unless user wishes otherwise)
	writetrack($streamp, $trackidx, \$trackdata)
	  if (($droplast == 0) && length($trackdata));
	print("$streamp: done\n");
}

