#!/usr/bin/perl -w

# A wrapper for vsound, realplayer and lame
# Homepage: http://scara.com/strec/
# 
# Copyright (c) 1995 by Christian Wolff (scarabaeus.org)
# Released under the GPL, see http://www.gnu.org/copyleft/gpl.html
# 
# What you'll need:
# VSound:     http://www.vsound.org/ (get version 0.6, apply patch from http://scara.com/strec/vsound-0.6.1.patch.tgz )
# RealPlayer: http://www.real.com/ (set sound device to OSS, not ALSA)
#  (old ver.: http://forms.real.com/real/player/blackjack.html)
# Lame:       http://lame.sourceforge.net/
# gnu wget:   http://wget.sunsite.dk/ [debian: 'apt-get install wget']
# LibID3v2    http://id3lib.sourceforge.net/
# id3v2       http://id3v2.sourceforge.net/ [debian: 'apt-get install id3v2' (includes libid3)]
# openRTSP    http://www.live555.com/liveMedia/ [debian: 'apt-get install livemedia-utils']
#   (compile, then copy 'live' directory to /usr/src/ or change $openRTSP below)
# ts2es:      https://code.google.com/p/tstools/
# ffmpeg:     https://www.ffmpeg.org/
# 
# How to do it:
# - Make sure all the software above is installed
#   The realplayer has to be configured to use "OSS" instead of the 
#   default "ALSA" audio driver. Also, disabling "PlayNow" and 
#   "TrueLive" can avoid noise during live stream recording.
# - Start with "strec.pl <file or URL> [<perfect>]"
#   - URL can be any http:, rtsp: or pnm: link
#     - The recording file name will be that of the .rm file, 
#       e.g. rtsp://server.com/dir/file.rm --> file.mp3
#   - file can be:
#     - filename.url: One line with a URL as above
#     - filename.strec: First line with the URL, second line with Title, Artist, Album and Year, separated by TABs, third line with Description.
#     - Otherwise, a line with 'rtsp:' from the file will be used as the URL
#     - The recording filename will be that of the file, 
#       e.g. /tmp/file.ram --> /tmp/file.mp3 (or $target/file.mp3, if $target is set below)
# - If you specify a non-zero value for <perfect>, the recoding will be
#   stopped if a DSP_RESET Alert is encountered.
# - Now the realplayer should start and you should see some messages like these:
#
 #  strec - a Stream Recorder, (c) 2005,2008 Christian Wolff
 #  URL[0]: rtsp://80.237.230.17:554/lounge/2008/080405_schludysimon_de.rm
 #   # fetching clip title
 #   # start recording betalounge_20080404_Simon_Lee_Slim_Schludy, running time: 4:00:25.991
 #   # Player --> VSOUND_DSPOUT=1 VSOUND_STDOUT=1 VSOUND_VERBOSE=1 VSOUND_STOPDELAY="30" LD_PRELOAD="/usr/local/lib/vsound/libvsound.so" realplay
 #  Warning: LD_PRELOAD="/usr/local/lib/vsound/libvsound.so"
 #  
 #  waiting 5/30
# - The "waiting" value will count up until the pre-buffering is finished, 
#   after that is continues like this:
#
 #  00:00:00.00 SNDCTL_DSP_SPEED    => 44100
 #  00:00:00.00 SNDCTL_DSP_CHANNELS => 2
 #  00:00:00.00 SNDCTL_DSP_SETFMT   => AFMT_S16_LE AFMT_S16_NE
 #  00:00:00.00 SNDCTL_DSP_SETFRAGMENT => 7fff000d
 #  00:00:00.00 SNDCTL_DSP_SETFMT   => AFMT_S16_LE AFMT_S16_NE
 #  00:00:00.00 SNDCTL_DSP_CHANNELS => 2
 #  00:00:00.00 SNDCTL_DSP_SPEED    => 44100
 #  00:00:00.00 SNDCTL_DSP_RESET
 #   # Encoder --> lame -r -x -h -s 44.1 -m j -b 128 --add-id3v2 --tt 'Simon Lee, Slim Schludy' --ta 'BetaLounge' --tl 'BetaLounge 20080404' --ty 2008 - betalounge_20080404_Simon_Lee_Slim_Schludy.mp3
 #  00:01:26.10 
#
# - The time value at the bottom will keep on going during the recording.
# - After the file has finished playing, you should see two more
#   DSP_RESET messages and success message like:
#     "FINISHED: clip is completed at 00:00:10.52"
#   The printed time is the recorded length of the .mp3 file. 
# - If there were any other DSP_RESET messages during the file recording, 
#   the connection got bad and you might have to start over again.
#   If that happens, a warning is also displayed: 
#     "ALERT! stream interruption at 00:00:05.70"
#
# v1.0 - 2005-05-01 initial release
# v1.1 - 2005-05-02 improved handling of multi-part recordings, add id3 tag after recoding is finished
# v1.2 - 2005-05-27 maintain a log file with all recordings
# v2.0 - 2008-09-18 Use vsound instead of dsproxy, remove now obsolete multi-threaded I/O-handling
# v2.1 - 2009-03-14 Terminate DSProxy with KILL instead of TERM signal
# v2.2 - 2014-11-13 Add support for .m3u8 playlists (Playlist of Transport Stream chunks, containing MP3 or H.263/AAC)
#

use strict;
use IPC::Open3;
use IO::Select;


# global parameter
my $debug = 1;  # whether to print debug messages
my $target = '';   # where to write the mp3 files
my $logfile = 'strec_log.txt';
my $tmp = '/tmp/'; # Path to writeable temporary directory, including trailing slash
my $inact_max = 30;  # inactivity timeout, in seconds

# applications
my $wget = 'wget -q';  # read file via http
my $wget_stdout = ${wget} . ' -O -';  # read file via http to stdout
my $openRTSP = '/usr/src/live/testProgs/openRTSP -t -S 0';  # download rtsp link
my $encoder = 'lame';
my $player = 'realplay';
#my $player = '/opt/real/RealPlayer/realplay.bin';
my $vsound = '/usr/local/lib/vsound/libvsound.so';
my $dsproxy = 'VSOUND_DSPOUT=1 VSOUND_STDOUT=1 VSOUND_VERBOSE=1 VSOUND_STOPDELAY="' . $inact_max . '" LD_PRELOAD="' . $vsound . '" ' . $player;
my $ts2es = 'ts2es';
my $ffmpeg = 'ffmpeg -y'; # -y: overwrite target
my $ffmpeg_opt = '-strict -2 -codec copy -bsf:a aac_adtstoasc'; # Allow experimental AAC, no re-encode, fix AAC structure


$| = 1;

my ($rec_pid, $dsp_pid) = (0, 0);
my $perfect = 0;
my $multipart = 0;
my $exitcode = 0;
my ($last_filename, $last_time) = ('', '00:00:00.00');
my $live = 0;

sub get_running_time($)
{
	my $rtsp = shift;
	my $runtime = 0;
	chomp $rtsp;
	open RUNTIME, "(mkdir -p ${tmp}rtsp$$~ ; cd ${tmp}rtsp$$~ ; ${openRTSP} ${rtsp}) 2>&1 |" or return 0;
	while (<RUNTIME>) {
#		$runtime = $1 if ($_=~/^a\=EndTime\:integer\;(\d+)/);
		$runtime = $1 * 1000 if ($_=~/^a\=length\:npt\=([0123456789.]+)/);
#		$runtime = ($2 - $1) * 1000 if ($_=~/^a\=range\:npt\=([0123456789.]+)\-([0123456789.]+)/);
	}
	close RUNTIME;
	`rm -rf ${tmp}rtsp$$~`;
	return $runtime;
}

sub get_title($)
{
	my $rtsp = shift;
	my $title = '';
	chomp $rtsp;
	open RUNTIME, "(mkdir -p ${tmp}rtsp$$~ ; cd ${tmp}rtsp$$~ ; ${openRTSP} ${rtsp}) 2>&1 |" or return 0;
	while (<RUNTIME>) {
		$title = $1 if ($_=~/^s=(.*)\r/);
	}
	close RUNTIME;
	`rm -rf ${tmp}rtsp$$~`;
	return $title;
}

# If the stream is a "live" stream, returns the title if available or 'Live' otherwise
sub get_live_title($)
{
	my $rtsp = shift;
	my $live = 0;
	my $title = '';
	my $dir = "${tmp}rtsp_live$$~";
	chomp $rtsp;
	open RTSP, "(mkdir -p ${dir} ; cd ${dir} ; ${openRTSP} ${rtsp}) 2>&1 |" or return '';
	while (<RTSP>) {
		$live = $1 if ($_=~/^a\=LiveStream\:integer\;(\d+)/);
		$title = $1 if ($_=~/^s=(.*)\r/);
	}
	close RTSP;
	`rm -rf ${dir}`;
	return $live ? ($title ? $title : 'Live') : '';
}

sub get_rtsp_from_ram($)
{
	my $ram = shift;
	my $rtsp = '';
	open RAM, "${wget_stdout} ${ram} |" or return $ram;
	while (<RAM>) {
		unless ($rtsp =~ /rtsp:/) {  # prefer rtsp, 
			$rtsp = $_ if /^\w+:/ and !/Usage/;       # but take any qualified link (pnm: etc.)
		}
	}
	close RAM;
	$rtsp = $ram unless ($rtsp);
	chomp $rtsp;
	return $rtsp;
}

sub get_rm_from_smil($)
{
	my $rtsp = shift;
	my $rm = '';
	my $dir = '${tmp}rtsp' . $$ . '~';
	`(mkdir -p ${dir} ; cd ${dir} ; ${openRTSP} ${rtsp}) > /dev/null 2>&1`;
	if (open SMIL, "${dir}/application-VND.RN-RMADRIVER-1" or open SMIL, "${tmp}rtsp$$~/application-SMIL-1") {
		while (<SMIL>) {
			s/\"\).*\(doc \"//;
			if ((!/logo/) and (!/feedback/) and /src=\\"(.*\.rm)\\"/) {
				my $smil = $1 . "\n";
				$rm .= $smil;
			}
		}
		close SMIL;
		$rm = $rtsp unless ($rm);
	} else {
		print STDERR "ERROR: can't open smil file in ${dir}, error $?: $!\n" if $debug;
		$rm = $rtsp;
	}
	`rm -rf ${dir}`;
	return $rm;
}

# record(url, filename, title, artist, album, year)
#  filename has to be without .mp3 extension
sub record
{
	my ($url, $filename, $title, $artist, $album, $year) = @_;
	
	my ($line, $time, $cmd, $param);
	my ($speed, $channels, $fmt) = (0, 0, '');
	my ($ns, $nc, $nf) = ($speed, $channels, $fmt);
	my $sequence = 0;
	my $run = 1;
	
	$rec_pid = 0;
	print STDERR " # Player --> ${dsproxy}\n" if $debug;
	$dsp_pid = open3(\*WR,\*RD,\*ERR, "${dsproxy} '${url}'");
	unless ($dsp_pid) {
		print STDERR " # Cannot open Player! $!\n" if $debug;
		$exitcode = 2;
		return;
	}
	
	my $timeout;
	my $stderr = '';
	my ($dsp_reset, $dsp_last) = ('', '00:00:00.00');
	my $dsp_s = IO::Select->new();
	$dsp_s->add(\*ERR);
	$dsp_s->add(\*RD);
	while ($run) {
		my @ready = $dsp_s->can_read(1);
		$timeout++;
		foreach (@ready) {
			my ($rc, $result);
			$timeout = 0;
			$rc = sysread($_, $result, 1024);
			
			if ($_ == \*ERR) {  # $result is from stderr
				print STDERR $result;
				
				# Accumulate and parse text from stderr of vsound
				$stderr .= $result;
				while (($stderr=~/[\r\n]/) && (($line, $stderr) = split /[\r\n]/, $stderr, 2)) {
					if ($line =~ /^(\d{2}\:\d{2}\:\d{2}\.\d{2}) ?(\w*)(.*)/) {
						($time, $cmd, $param) = ($1, $2, $3);
						$param = $1 if $param =~ /=> (.*)$/;
						$last_time = $time;
						$ns = $param if $cmd =~ /^SNDCTL_DSP_SPEED$/;
						$nc = $param if $cmd =~ /^SNDCTL_DSP_CHANNELS$/;
						$nf = $param if $cmd =~ /^SNDCTL_DSP_SETFMT$/;
						$nc = (($param =~ /^yes$/) ? 2 : ($param =~ /^no$/) ? 1 : $nc) if $cmd =~ /^SNDCTL_DSP_STEREO&/;
						if (($cmd =~ /^SNDCTL_DSP_/) && ($cmd !~ /^SNDCTL_DSP_RESET$/)) {
							$dsp_last = $time;
							$dsp_reset= '' if ($dsp_reset eq $time);
						}
						if ($dsp_reset && ($dsp_reset ne $time)) {  # single RESET has happened: stream hiccup
							print "ALERT! stream interruption at ${dsp_reset}\n";
							$dsp_reset = '';
							if (! $live || ($time!~/00:00:00:/)) {
								$exitcode = 1;
								$run = 0 if $perfect;
							}
						}
						if (($cmd =~ /^SNDCTL_DSP_RESET$/) && ($time ne $dsp_last)) {
							if ($dsp_reset eq $time) {  # 2nd consecutive RESET without sample output or other DSP ioctls inbetween: realplayer is finished
								if (! $live || ($time!~/00:00:00:/)) {  # mask out noise during startup of live stream
									print "FINISHED: clip is completed at ${dsp_reset}\n";
									$dsp_reset = '';
									$run = 0 unless $multipart;
								}
							} else {  # remember last RESET time
								$dsp_reset = $time;
							}
						}
					}
				}
				
				# sample frequency or number of channels has changed, start new recoder
				if (($speed != $ns) || ($channels != $nc)) {
					$speed = $ns if ($speed != $ns);
					$channels = $nc if ($channels != $nc);
					if ($speed && $channels) {
						my $recname;
						if ($rec_pid) {
							print STDERR " # Closing Encoder ...\n" if $debug;
							close REC;
							print STDERR " # Waiting for pid $rec_pid\n" if $debug;
							my $wait_ret = waitpid($rec_pid, 0);
							$rec_pid = 0;
							if ($wait_ret == -1) {
								print STDERR " # Encoder was already terminated.\n" if $debug;
							} elsif ($? & 0xFFFF) {
								my ($err, $sig, $core) = (($? >> 8) & 0xFF, $? & 0x7F, ($? & 0x80) >> 7);
								print STDERR " # Encoder failed - error ${err}, signal ${sig}, core ${core}\n" if $debug;
								$exitcode = 4 if ($err && ($err != 255));
							}
							if (open LOG, ">>$logfile") {
								print LOG "${last_filename} ${last_time}\n";
								close LOG;
							}
							print "Recorded ${last_time}, adding id3v1 tag to ${last_filename}\n";
							system "id3v2 -t \'${title}\' -a \'${artist}\' -A \'${album}\' -y ${year} ${last_filename}";
							print STDERR " # waiting for Encoder pid $rec_pid\n" if $debug;
							$sequence++;
							$recname = $filename . "_${sequence}.mp3";
							print "Starting additional segment file: ${recname}\n";
						} else {
							$recname = $filename . '.mp3';
						}
						$last_filename = $recname;
						my $bitrate = 
							($channels == 1) ? 
								(($speed > 22050) ? 96 : 64) 
							: 
								(($speed > 22050) ? 128 : 96);
						my $options = '-r -x -h';  # raw pcm, reverse byte order, high quality
						$options .= ' -s ' . $speed / 1000;  # pcm sample rate, in kHz (decimal fraction)
						$options .= ' -m ' . (($channels == 1) ? 'm' : 'j');  # 1 channel: mono, 2 channels:  joint stereo
						$options .= ' -b ' . $bitrate;
						$options .= " --add-id3v2 --tt \'${title}\' --ta \'${artist}\' --tl \'${album}\' --ty ${year}";
						print STDERR " # Encoder --> ${encoder} ${options} - ${recname}\n" if $debug;
						$rec_pid = open(REC, "| ${encoder} ${options} - ${recname}");
						unless ($rec_pid) {
							print STDERR " # Failed to start Encoder! $!\n" if $debug;
							kill_dsp();
							$exitcode = 2;
							return;
						}
					}
				}
				
			} else {  # $result is from stdout
				if ($rec_pid) {
					# Pipe audio samples from stdout to lame
					print REC $result if (! $live || ($last_time!~/00:00:00:/));  # pipe to Encoder, clip first second of live recordings (sometimes noisy)
				#} elsif ($result!~/LD_PRELOAD/) {
				#	print STDERR "\n";
				#	print "Recoder not open!\n";
				#	kill_dsp();
				#	$exitcode = 3;
				#	return;
				#} else {
				#	print "[REALPLAY}: $result \n";
				}
			}
		}
		print STDERR "\n" if $timeout == 1;
		print STDERR "waiting ${timeout}/${inact_max}\r" if $timeout;
		last if ($timeout >= $inact_max);
	}
	print STDERR "\n";
	kill_rec();
	kill_dsp();
}

sub kill_rec
{
	print STDERR " # Killing Encoder ...\n" if $debug;
	if ($rec_pid) {
		close REC;
		kill 'TERM', $rec_pid;
		print STDERR " # Waiting for pid $rec_pid\n" if $debug;
		my $wait_ret = waitpid($rec_pid, 0);
		$rec_pid = 0;
		if ($wait_ret == -1) {
			print STDERR " # Encoder was already terminated.\n" if $debug;
		} elsif ($? & 0xFFFF) {
			my ($err, $sig, $core) = (($? >> 8) & 0xFF, $? & 0x7F, ($? & 0x80) >> 7);
			print STDERR " # Encoder failed - error ${err}, signal ${sig}, core ${core}\n" if $debug;
			$exitcode = 4 if ($err && ($err != 255));
		}
	} else {
		print STDERR " # Encoder not running.\n" if $debug;
	}
	if (open LOG, ">>$logfile") {
		print LOG "${last_filename} ${last_time}\n";
		close LOG;
	}
}

sub kill_dsp
{
	print STDERR " # Killing Player ...\n" if $debug;
	if ($dsp_pid) {
		close WR;
		close RD;
		close ERR;
		#kill -15, $dsp_pid; # group 'TERM'
		kill -9, $dsp_pid; # group 'KILL'
		print STDERR " # Waiting for pid $dsp_pid\n" if $debug;
		my $wait_ret = waitpid($dsp_pid, 0);
		$dsp_pid = 0;
		if ($wait_ret == -1) {
			print STDERR " # Player was already terminated.\n" if $debug;
		} elsif ($? & 0xFFFF) {
			my ($err, $sig, $core) = (($? >> 8) & 0xFF, $? & 0x7F, ($? & 0x80) >> 7);
			print STDERR " # Player failed - error ${err}, signal ${sig}, core ${core}\n" if $debug;
		}
	} else {
		print STDERR " # Player already killed\n" if $debug;
	}
}

sub process_ts
{
	my $path = shift;
	my $ts_stream = shift;
	my $output = shift;
	
	print "Processing TS: '${ts_stream}'\n";
	
	if ($output =~ /\.mp4$/) {
		my $ts_out = $output;
		$ts_out =~ s/\.mp4$/.ts/;
		my $audio_out = $output;
		$audio_out =~ s/\.mp4$/.aac/;
		
		my $audio = $ts_stream;
		$audio =~ s/\.ts$/.aac/;
		
		`${wget} -O ${tmp}${ts_stream} ${path}${ts_stream} ; \
		 cat ${tmp}${ts_stream} >> ${ts_out} ; \
		 ${ts2es} -audio ${tmp}${ts_stream} ${tmp}${audio} ; \
		 rm ${tmp}${ts_stream} ; \
		 cat ${tmp}${audio} >> ${audio_out} ; \
		 rm ${tmp}${audio} \
		`;
	} else {
		my $audio = $ts_stream;
		$audio =~ s/\.ts$/.mp3/;
		
		`${wget} -O ${tmp}${ts_stream} ${path}${ts_stream} ; \
		 ${ts2es} -audio ${tmp}${ts_stream} ${tmp}${audio} ; \
		 rm ${tmp}${ts_stream} ; \
		 cat ${tmp}${audio} >> ${output} ; \
		 rm ${tmp}${audio} \
		`;
	}
}


# main

my ($url, $name, $filename, $title, $artist, $album, $releaseyear);

print "strec - a Stream Recorder, (c) 1995-2014 Christian Wolff\n";

$url = shift;
$perfect = shift;

$name = $url;
if ($url =~ /^\w+\:(.*)$/) {  # URL
	# URL specified on command line
	$name = $1;
	$name = $1 if $name =~ /^.*\/(.*?)\..*$/;
} else {  # file
	$name = $1 if $url =~ /^(.*?)\.\w*$/;
	open FILE, "$url" or die "can't open file ${url}!\n";
	if ($url =~ /\.url$/) {
		# File containing an URL specified on command line
		$url = <FILE>;
		chomp $url;
	} elsif ($url =~ /\.strec$/) {
		# .strec File containing an URL and clip title specified on command line
		my $path = '';
		$path = $1 if $url =~ /^(.*\/)/;
		$url = <FILE>;
		chomp $url;
		my $desig = <FILE>;
		chomp $desig;
		($title, $artist, $album, $releaseyear) = split /\t/, $desig;
		if ($url =~ /^\/.*\.ram/) {  # see if .ram file is in same dir as .strec file
			$url =~ s/^.*\//$path/ unless -e $url;
			if (open RAM, $url) {
				my $rm = '';
				while (<RAM>) {
					$rm .= $_ if ~/rtsp:/;
				}
				$multipart = 1 if ($rm =~ /\n.*\n/s);
				close RAM;
			}
		}
	} else {
		# File containing multiple URLs specified on command line
		my $rm = '';
		while (<FILE>) {
			$rm .= $_ if ~/rtsp:/;
		}
		if ($rm =~ /\n.*\n/s) {  # multiple rm links, create local .ram in /tmp
			if (open RAM, ">${tmp}strec_$$.ram") {
				print RAM $rm;
				close RAM;
				$url = "${tmp}strec_$$.ram";
				$multipart = 1;
			}
		} else {
			$url = $rm;
			chomp $url;
		}
	}
	close FILE;
}

print STDERR "URL[0]: ${url}\n" if $debug;
if ($url =~ /\.m3u8$/) { # Extracting of audio portion from transport streams referenced by .m3u8 playlist file
	my $path = $url;
	$path =~ s/^(.*\/).*$/$1/;
	$filename = $name;
	print STDERR " # start recording ${name}\n" if $debug;
	if ($target) {
		$target .= '/' unless $target =~ /\/$/;
		$filename =~ s/^.*\///;
		$filename = $target . $filename;
		$logfile =~ s/^.*\///;
		$logfile = $target . $logfile;
	}
	$filename = $1 if $filename =~ /^(.*)\.mp3$/;
	if ($url =~ /\.mp4/) {
		$last_filename = $filename . '.mp4'; # Capture h.263 video and aac audio
	} else {
		$last_filename = $filename . '.mp3'; # Capture mp3 audio
	}
	my $basename = $url;
	$basename =~ s/^.*\///;
	$basename =~ s/\..*?$//;
	printf " # Output file: ${last_filename}\n";
	`mv -f ${last_filename} ${last_filename}~` if -e $last_filename;
	if ($last_filename =~ /\.mp4$/) {
		my $ts_out = $filename . '.ts';
		`mv -f ${ts_out} ${ts_out}~` if -e $ts_out;
		my $audio_out = $filename . '.aac';
		`mv -f ${audio_out} ${audio_out}~` if -e $audio_out;
	}
	
	my $runmsec = 0.0;
	open PLAYLIST, "${wget_stdout} '${url}' |" or die "Can not download playlist '${url}'!\n";
	while (<PLAYLIST>) {
		next if (/^#/);
		if (/(.*\.m3u8)/) {
			my $chunk = $1;
			printf " # Getting chunk '${chunk}'\n";
			open CHUNK, "${wget_stdout} '${path}${chunk}' |" or die "Can not download chunk '${path}${chunk}'!\n";
			while (<CHUNK>) {
				if (/^#EXTINF\:(\d*\.\d*)\,/) {
					$runmsec += ($1 * 1000.0);
				}
				next if (/^#/);
				if (/(.*\.ts)/) {
					process_ts($path, $1, $last_filename);
				}
			}
			close CHUNK;
		}
	}
	close PLAYLIST;
	
	my $f = $runmsec;
	my $s = $f / 1000; $f %= 1000;
	my $m = $s / 60; $s %= 60;
	my $h = $m / 60; $m %= 60;
	$last_time = sprintf("%d:%02d:%02d.%03d", $h, $m, $s, $f);
	
	$title = $basename unless $title;
	$artist = $name unless $artist;
	$album = $artist unless $album;
	$releaseyear = '2014' unless $releaseyear;
	
	if ($last_filename =~ /\.mp4$/) {
		my $aac = $filename . '.aac';
		# Pack AAC into M4A audio container
		`${ffmpeg} -i ${aac} ${ffmpeg_opt} -metadata title="${title}" -metadata artist="${artist}" -metadata album="${album}" -metadata date="${releaseyear}" ${filename}.m4a`;
		`rm ${aac}` if -s "${filename}.m4a";
		my $ts = $filename . '.ts';
		# Pack TS (AAC and H.263) into MP4 video container
		`${ffmpeg} -i ${ts} ${ffmpeg_opt} -metadata title="${title}" -metadata artist="${artist}" -metadata album="${album}" -metadata date="${releaseyear}" ${filename}.mp4`;
		`rm ${ts}` if -s "${filename}.mp4";
	}
	if (open LOG, ">>$logfile") {
		print LOG "${last_filename} ${last_time}\n";
		close LOG;
	}
} else { # Playback .ram or .rm file with RealPlayer, then rip from DSP API and re-encode with Lame
	my $orl = $url;
	$url = get_rtsp_from_ram($url) if $url =~ /^http:/;
	print STDERR "URL[1]: ${url}\n" if ($debug && ($url ne $orl));
	if ($url =~ /\.smil$/) {
		$orl = $url;
		my $rm = get_rm_from_smil($url);
		if ($rm =~ /\n.*\n/s) {  # multiple rm links, create local .ram in /tmp
			if (open RAM, "${tmp}strec_$$.ram") {
				print RAM $rm;
				close RAM;
				$url = "${tmp}strec_$$.ram";
				$multipart = 1;
			}
		} else {
			$url = $rm;
			chomp $url;
		}
		print STDERR "URL[2]: ${url}\n" if ($debug && ($url ne $orl));
	}
	$multipart = 1 if ($url =~ /\.smil$/);
	
	print STDERR " # fetching clip title\n";
	$live = get_live_title($url);
	if ($live) {
		my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime(time());
		$year += 1900; $month++;
		my $timestamp = sprintf('%04d-%02d-%02d_%02d-%02d-%02d', $year, $month, $day, $hour, $min, $sec);
		
		$artist = $name;
		$title = $live unless $title;
		$album = $timestamp;
		$releaseyear = $year;
		$filename = "${name}_${timestamp}";
		print STDERR " # start recording ${name} - ${title} at ${timestamp}\n" if $debug;
	} else {
		my $runmsecs = get_running_time($url);
		my $f = $runmsecs;
		my $s = $f / 1000; $f %= 1000;
		my $m = $s / 60; $s %= 60;
		my $h = $m / 60; $m %= 60;
		my $runtime = sprintf("%d:%02d:%02d.%03d", $h, $m, $s, $f);
		
		$title = get_title($url) unless $title;
		$artist = $name unless $artist;
		$album = $artist unless $album;
		$releaseyear = '2005' unless $releaseyear;
		$filename = $name;
		print STDERR " # start recording ${name}, running time: ${runtime}\n" if $debug;
		print "Clip length is ${runtime}\n" unless $debug;
	}
	
	if ($target) {
		$target .= '/' unless $target =~ /\/$/;
		$filename =~ s/^.*\///;
		$filename = $target . $filename;
		$logfile =~ s/^.*\///;
		$logfile = $target . $logfile;
	}
	$filename = $1 if $filename =~ /^(.*)\.mp3$/;
	
	$SIG{CHLD} = sub {
		print STDERR "Got SIGCHLD, waiting... ";
		wait;
		print STDERR "done.\n";
	};
	
	record($url, $filename, $title, $artist, $album, $releaseyear);
}

if ($last_filename =~ /\.mp3$/) {
	print "Recorded ${last_time}, adding id3v1 tag to ${last_filename}\n";
	system "id3v2 -t \'${title}\' -a \'${artist}\' -A \'${album}\' -y ${releaseyear} ${last_filename}";
} else {
	print "Recorded ${last_time} in ${last_filename}\n";
}

print STDERR " # Exiting with code ${exitcode}\n" if $debug;
exit $exitcode;

