Cache up internet radio for later *skippable* playback

If there’s an internet radio station you like, you might want to listen to it on the move, even when you don’t have internet.  It’s fairly trivial to slurp the MP3 stream with a tool like wget, but then you end up with a 4GB single file.  It’ll play fine, but when a crappy song comes on, it would be nice to skip ahead without having to hold down the “forward” button for a while. Worse, many MP3 players don’t keep track of position in a song, so when you start back up, you’ll be at the start of the stream again.

What I do is slurp the MP3 stream, but divide it into fixed chunks, like 5MB.  You can tell how much runtime per chunk based on the bitrate.  A 128kbps stream means there are 128*1024 bits per second, or about 0.983 megabytes/minute.  So 5MB is roughly 5 minutes.

This means I have a directory with a few hundred MP3 files to be played in sequence.  If you have gapless playback, you don’t even notice the track changes. If you’re listening to a lousy song, you can skip 5 minutes into the future just by pressing next track.  Even lousy MP3 players keep track of which song you were on, so your radio picks up where it left off, and even if you lose your place, it’s easier to find your way back, since you can just skip five minutes at a time until you start hearing new content.

To do this chunk download approach, I use this old perl script I wrote a few years ago. It’s a little ugly…if I were to write it today, it would probably be 10 lines of Python, but it works well enough, even on Windows (provided you have perl and wget).


use strict;

if (!@ARGV) {
	die < [basename] [size]

my $url = $ARGV[0];
my $basename = $ARGV[1] || "save";
my $chunksize = $ARGV[2] || 5*1024*1024;
my $blocksize = 4096;

my $chunkleft = $chunksize;

open STREAM, "wget -O- $url |" or die;
my $n=1;
while (1) {
	my $buf;
	my $filename = sprintf("%s-%03d.mp3",$basename,$n);
	open OUT, ">$filename" or die "$filename: $!\n";
	while ($chunkleft > 0) {
		my $r = read(STREAM, $buf, $blocksize);
		if ($r == 0) { die "EOF, exiting.\n"; } 
		elsif ($r<0) { die "stream: $!\n"; }

		$chunkleft -= $r;
		print OUT $buf;
	close OUT;
	$chunkleft = $chunksize;
	print "\nFinished writing $filename.\n";


Leave a Reply

Your email address will not be published. Required fields are marked *