1#!/usr/bin/perl -w 2 3eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}' 4 if 0; # not running under some shell 5 6# If your Perl implementation resides somewhere other than 7# /usr/bin/perl, change the above line to reflect that. 8 9## Created: <Sun Oct 15 02:57:05 EDT 2000> 10## Time-stamp: <2002-04-25 10:23:44 foof> 11## Author: Alex Shinn <foof@debian.org> 12 13=head1 NAME 14 15pogg - Perl Ogg Vorbis Utility 16 17=head1 SYNOPSIS 18 19 pogg [play] [options] [file1.ogg file2.ogg...] 20 pogg encode [options] [file1.wav file2.wav...] 21 pogg decode [options] [file1.ogg file2.ogg...] 22 pogg convert [options] [file1.mp3 file2.mp3...] 23 pogg comment [options] [file1.ogg file2.ogg...] 24 25=head1 DESCRIPTION 26 27pogg is a general purpose tool for playing, encoding (from .wav), 28decoding (to .wav), converting (from .mp3) and commenting Ogg Vorbis 29files. External programs must be used to handle mp3 files, and 30currently pogg cannot write .ogg files (and thus cannot modify 31comments, encode to .ogg, or convert from mp3). 32 33=cut 34 35# Modules 36use strict; 37use Getopt::Long; 38use Ogg::Vorbis; 39use Ao; 40 41# Variables 42my $VERSION = '0.1'; 43my $MODIFIED = '2000/10/23'; 44 45# Config variables (external programs) 46my $mp3play = 'mpg123'; 47my $mp3info = 'mp3info'; 48my %mp3play_opts = ('buffer' => '-b', 49 'verbose' => '-v', 50 'quiet' => '-q'); 51my %mp3info_opts = ('TITLE' => '-t', 52 'ARTIST' => '-a', 53 'ALBUM' => '-l', 54 'GENRE' => '-G'); 55 56# Data variables 57my @devices = (); # devices to play to 58my @list = (); # List of sources to act on 59my $buffer; # raw data buffer 60my $bitstream=0; # current bitstream 61my $big_endian_p = 0; 62my $ogg = Ogg::Vorbis->new; # Ogg Vorbis Stream 63 64# Separate option modes 65my %opts = ('bits' => 16, 66 'rate' => 44100, 67 'channels' => 2, 68 'buffer' => 4096); 69my @play_opts = ('help|h|?!','pogg-version|version|V!','verbose|v!', 70 'quiet|q!','device|dev|d=s@','bits|b=i','rate|r=i', 71 'channels|c=i','buffer|s=i','random|rand|R!', 72 'sort|S=s'); 73my @comment_opts = ('help|h|?!','pogg-version|V!','title|t:s', 74 'VERSION|v:s','ALBUM|l:s','ARTIST|a:s', 75 'ORGANIZATION|o:s','DESCRIPTION|D:s','GENRE|g:s', 76 'DATE|d:s','LOCATION|p:s','COPYRIGHT|c:s', 77 'comment|C:s%'); 78my @opts = @play_opts; # most commands use the play_opts 79my @known_comments = ('TITLE','VERSION','ALBUM','ARTIST','ORGANIZATION', 80 'DESCRIPTION','GENRE','DATE','LOCATION','COPYRIGHT'); 81 82# Get command (sets default devices, etc.) 83my $command = shift; 84if ($command eq 'play') { 85} elsif ($command eq 'encode') { 86 die "Sorry, encode not yet supported\n"; 87} elsif ($command eq 'decode') { 88} elsif ($command eq 'convert') { 89 die "Sorry, convert not yet supported\n"; 90} elsif ($command eq 'comment') { 91 @opts = @comment_opts; 92} else { 93 # No command specified... default to play and push back the arg. 94 unshift @ARGV, $command; 95 $command = 'play'; 96} 97 98 99=head1 OPTIONS 100 101=over 4 102 103=item -h, --help 104 105Print a brief usage message. 106 107=item -v, --version 108 109Display version info. 110 111=item -d, --device DEVICE 112 113Add a device to play to. The device may be any of oss, esd, alsa, 114irix or solaris as the output device, or wav to write to a wav file. 115You may specify additional options to a device by appending a colon 116and comma-separated list of key=value pairs. Examples: 117 118 pogg file.ogg -d wav:file=output.wav 119 pogg file.ogg -d esd -d alsa:card=0,dev=1 120 121=item -b, --bits BITS 122 123Set the number of bits per sample (8 or 16, default 16). 124 125=item -r, --rate RATE 126 127Set the number of samples per second (default 44100). 128 129=item -c, --channels CHANNELS 130 131Set the number of channels (1 = mono, 2 = stereo). 132 133=item -s, --buffer SIZE 134 135Set buffer size (default 4096). 136 137=back 138 139=cut 140 141# Parse Options 142GetOptions(\%opts, @opts) or usage(); 143usage() if $opts{help}; 144if ($opts{version}) { version(); exit; } 145# Create buffer (just a string to store data) 146$buffer = 'x' x $opts{buffer}; 147# If no files, read file list from stdin 148@list = @ARGV; 149unless (@list) { 150 while (<>) { 151 chomp; 152 # Modify to recognize external (http) resources 153 if (-e $_) { 154 unshift @list, $_; 155 } 156 } 157} 158# Sort or shuffle list (may have to wait until we have more info on 159# sources). XXXX Add sort by genre, time, file type, etc. 160if ($opts{random} || (exists $opts{sort} && 161 ($opts{sort} =~ /^rand(om)?$/))) { 162 my $temp; 163 for (my $i=0; $i<@list; $i++) { 164 my $j = int(rand(@list)); 165 $temp = $list[$i]; 166 $list[$i] = $list[$j]; 167 $list[$j] = $temp; 168 } 169} elsif ($opts{sort}) { 170 if ($opts{sort} =~ /order|given|default/) { 171 # default, don't sort 172 } elsif ($opts{sort} =~ /^(asc(end(ing)?)?|alpha)$/i) { 173 @list = sort @list; 174 } elsif ($opts{sort} =~ /^(de?sc(end(ing)?)?|omega)$/i) { 175 @list = sort {$b cmp $a} @list; 176 } else { 177 # unkown method, give warning and leave list alone 178 warn "unknown sort method: $opts{sort}\n"; 179 } 180} 181# Handle special commands 182if ($command eq 'convert') { 183 do_convert(); 184 exit; 185} elsif ($command eq 'comment') { 186 do_comments(); 187 exit; 188} else { 189 # Configure default output device 190 unless ($opts{device}) { 191 if ($command eq 'play') { 192 $opts{device} = ['oss']; 193 } elsif ($command eq 'decode') { 194 $opts{device} = ['wav']; 195 } 196 } 197 # Open devices (change concept here... allow for default FILE.wav 198 # instead of output.wav). 199 open_devices(); 200 play_files(); 201 close_devices(); 202} 203 204 205# Try to play all listed files 206# XXXX rewrite as play_list(@) for recursion 207sub play_files { 208 no strict qw(subs); 209 version() unless $opts{quiet}; 210 foreach my $source (@list) { 211 print "\nPlaying $source\n" if ($opts{verbose}); 212 if ($source =~ /\.ogg$/) { 213 # Open the .ogg 214 unless (open(INPUT, "< $source")) { 215 warn "couldn't open $source\n"; 216 next; 217 } 218 if ($ogg->open(INPUT) < 0) { 219 warn "$source does not appear to be an Ogg Vorbis bitstream\n"; 220 next; 221 } else { 222 # Play the .ogg 223 unless ($opts{quiet}) { 224 write_info($ogg); 225 print "\n"; 226 } 227 my $t_time = $ogg->time_total; 228 my $t_min = int($t_time / 60); 229 my $t_sec = $t_time % 60; 230 while (1) { 231 my $bytes = $ogg->read($buffer, $opts{buffer}, $big_endian_p, 232 2, 1, $bitstream); 233 if ($bytes == 0) { 234 # EOS 235 last; 236 } elsif ($bytes < 0) { 237 # Error 238 warn "error in stream\n" unless $opts{quiet}; 239 } else { 240 # Successful read, play on each device 241 foreach my $ao (@devices) { 242 $ao->play($buffer, $bytes); 243 } 244 # Print info if verbose 245 if ($opts{verbose}) { 246 my $c_time = $ogg->time_tell; 247 my $c_min = int($c_time / 60); 248 my $c_sec = $c_time - 60 * $c_min; 249 my $r_min = int(($t_time - $c_time) / 60); 250 my $r_sec = ($t_time - $c_time) - 60 * $r_min; 251 printf STDERR "\rTime: %02li:%05.2f [%02li:%05.2f] %.1f kbit/s \r", 252 $c_min, $c_sec, $r_min, $r_sec, $ogg->bitrate_instant / 1000; 253 } 254 } 255 } 256 printf STDERR "\r%s\r\n", ' ' x 60; 257 } 258 } elsif ($source =~ /\.mp3$/) { 259 # Translate our options into the mp3 player's options 260 my $command_line = $mp3play; 261 while (my ($k, $v) = each(%opts)) { 262 if (exists $mp3play_opts{$k}) { 263 $command_line .= " \"$mp3play_opts{$k}\" \"$v\""; 264 } 265 } 266 # Call the external player, let it handle warnings/errors. 267 print "$command_line \"$source\"\n" unless $opts{quiet}; 268 `$command_line $source`; 269 } else { 270 warn "unknown format: $source\n"; 271 } 272 } 273} 274 275 276# Convert between ogg and mp3 277sub do_convert { 278 no strict qw(subs); 279 foreach my $source (@list) { 280 } 281} 282 283 284# Display comments (XXXX add modify when we can write) 285sub do_comments { 286 no strict qw(subs); 287 foreach my $source (@list) { 288 print "Commenting $source\n" if ($opts{verbose}); 289 if ($source =~ /\.ogg$/) { 290 # Comment .ogg 291 unless (open(INPUT, "< $source")) { 292 warn "couldn't open $source\n"; 293 next; 294 } 295 if ($ogg->open(INPUT) < 0) { 296 warn "$source does not appear to be an Ogg Vorbis bitstream\n"; 297 next; 298 } else { 299 # XXXX when available, add code to modify info 300 write_info($ogg); 301 $ogg->clear; 302 close(INPUT); 303 } 304 } elsif ($source =~ /\.mp3/) { 305 # Comment .mp3 306 my $command_line = $mp3info; 307 # Check known comments 308 foreach my $k (@known_comments) { 309 if ((exists $mp3info_opts{$k}) and (exists $opts{$k})) { 310 $command_line .= " \"$mp3info_opts{$k}\" \"$opts{$k}\""; 311 } 312 } 313 # Check for anything mp3info knows about that we don't 314 while (my ($k, $v) = each(%{$opts{comment}})) { 315 if (exists $mp3info_opts{$k}) { 316 $command_line .= " \"$mp3info_opts{$k}\" \"$v\""; 317 } 318 } 319 # Now run the command (die on error... we don't want to muck up 320 # multiple files). 321 open(OUTPUT, "$command_line $source |") || 322 die "couldn't run \`$command_line $source \`\n"; 323 while (<OUTPUT>) { print } 324 close(OUTPUT); 325 } else { 326 warn "unkown format: $source\n"; 327 } 328 } 329} 330 331 332# Open device and place in global @devices list 333sub open_devices { 334 foreach my $dev (@{$opts{device}}) { 335 my $opt_string = ''; 336 my %dev_opts = (); 337 if ($dev =~ /([^:]*):(.*)/) { 338 $dev = $1; 339 $opt_string = $2; 340 %dev_opts = split(/[,=]/, $opt_string); 341 } 342 my $dev_id = Ao::get_driver_id($dev); 343 my $ao = Ao::open($dev_id, $opts{bits}, $opts{rate}, 344 $opts{channels}, \%dev_opts) || 345 die "error: couldn't open device $dev\n"; 346 unshift @devices, $ao; 347 } 348} 349 350 351# Close devices in @devices list 352sub close_devices { 353 foreach my $ao (@devices) { 354 $ao->close; 355 } 356} 357 358 359sub write_info { 360 # Short, one line comment format like mp3info, followed by 361 # longer comments. 362 my %comments = %{$ogg->comment}; 363 my $info = $ogg->info; 364 my $time = $ogg->time_total; 365 my $min = int($time / 60); 366 my $sec = $time % 60; 367 my $rate = int($ogg->bitrate / 1024); 368 # Try to print like mp3info, add album because it's nice to know 369 print "$comments{TITLE}($comments{ARTIST}, $comments{ALBUM})", 370 ", $rate kbit/s ", ($info->channels == 2)?'stereo':'mono', 371 " (${min}m ${sec}s)\n"; 372 # Now print other known comments. 373 while (my ($k, $v) = each(%comments)) { 374 # XXXX Format nicer 375 if (grep {/^\Q$k\E$/} @known_comments) { 376 print "$k: $v\n" unless grep {/^\Q$k\E$/} ('TITLE','ARTIST','ALBUM'); 377 } else { 378 warn "unknown comment: $k\n"; 379 } 380 } 381} 382 383 384# Print a usage summary 385sub usage { 386 print <<EOF; 387usage: $0 [options] [command] [file ...] 388 389 -h, --help display this message 390 -v, --version print version info 391 -d, --device DEVICE add a device to play to 392 -b, --bits BITS set bits per sample (8 or 16) 393 -r, --rate RATE set samples per second (default 44100) 394 -c, --channels CHANNELS set channels (1 = mono, 2 = stereo) 395 -s, --buffer SIZE set default buffer size 396 -S, --sort METHOD specify a sorting method 397 -R, --random shuffle files (alias for --sort random) 398 399where COMMAND is any of play, encode, decode, convert or comment, and 400DEVICE is any of oss, esd, alsa, irix, solaris or wav. The device may 401be followed by an optional colon with a comma separated list of 402key=value parameters, such as --device wav:file=output.wav. 403EOF 404 exit 0; 405} 406 407# Print version info 408sub version { 409 print "pogg $VERSION ($MODIFIED)\n"; 410} 411 412 413=head1 AUTHOR 414 415Alex Shinn, foof@synthcode.com 416 417=head1 SEE ALSO 418 419Ogg::Vorbis(3pm), Ao(3pm), ogg123(1), oggenc(1), perl(1). 420 421=cut 422