source: gutenbach/debian/lib/sipbmp3-filter @ 6761cd4

debianmacno-cupsnodebathenaweb
Last change on this file since 6761cd4 was 6761cd4, checked in by jhawk <jhawk>, 21 years ago
Fix missing "
die" on ogg123.

Belated style cleanups:

80 column lines

"
die" not "or die"
  • Property mode set to 100755
File size: 16.8 KB
Line 
1#!/usr/athena/bin/perl
2# Play the data on STDIN as an audio file
3#
4# $Id: sipbmp3-filter,v 1.10 2003-10-22 20:36:08 jhawk Exp $
5# $Source: /tmp/tmp.UFBNno9997/RCS/sipbmp3-filter,v $
6#
7# TODO
8# ----
9# Make this structured code. It's a mess.
10# Repeat what we just played for EXT files too
11# Support HTTP Auth on ogg streams
12# License, cleanup and package
13#
14# Jered Floyd <jered@mit.edu> takes very little credit for this code
15
16use Getopt::Std;
17
18# Get the MP3Info module from this directory, because I suck.
19unshift(@INC, "/usr/local/bin");
20require MP3::Info;
21import MP3::Info;
22&use_winamp_genres();
23
24print STDERR "STDERR FROM SPOOL FILTER\n";
25
26# set real uid to be effective uid
27$< = $>;
28
29# Attach necessary lockers
30system("/bin/athena/attach -h -n -q infoagents sipb outland ' .
31    'consult 2>&1 > /dev/null");
32
33# Select the correct output device and set the volume
34system("/usr/local/bin/audio_setdevice -out headphones 100 ' .
35    '2>&1 </dev/null > /dev/null");
36
37# The command line we get from lpd is (no spaces between options and args):
38#  -C lpr -C class
39#  -A LPRng internal identifier
40#  -H originating host
41#  -J lpr -J jobname (default: list of files)
42#  -L lpr -U username
43#  -P logname
44#  -Q queuename (lpr -Q)
45#  -a printcap af (accounting file name)
46#  -d printcap sd entry (spool dir)
47#  -e print job data file name (currently being processed)
48#  -h print job originiating host (same as -H)
49#  -j job number in spool queue
50#  -k print job control file name
51#  -l printcap pl (page length)
52#  -n user name (same as -L)
53#  -s printcap sf (status file)
54#  -w printcap pw (page width)
55#  -x printcap px (page x dimension)
56#  -y printcap py (page y dimension)
57# accounting file name
58
59# All the filter_options from lpd
60getopt('ACFHJLPQRZacdefhijklnprswxy', \%opts);
61
62# Status messages at start of playback
63open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c sipb-auto -i ' .
64  'sipbmp3@xcb -s "SIPB LPR music spooler"');
65print(ZEPHYR "User $opts{'n'} on host $opts{'H'} is playing:\n");
66
67# So, the file we're currently processing is "-d/-e".
68# Do some magic to make sure it's an MP3, and get the important bits
69# to zephyr out.
70open(DATAFILE, "$opts{'d'}/$opts{'e'}");
71sysread(DATAFILE, $magic, 3);
72close(DATAFILE);
73
74# MPEG header is beshort &0xffe0
75# meditate upon the header:
76($magic0, $magic1, @magic2) = unpack("C3", $magic);
77if ((($magic0 & 0xff) == 0xff) &&
78    (($magic1 & 0xe0) == 0xe0)) {
79  # MPEG audio file, we like it
80  # Fall through
81} elsif ($magic eq "ID3") {
82  # ID3 v2 does sketchy things with "garbage" beginning with ID3 at file start.
83  # MPEG audio file with ID3 tag, we like it
84  # Fall through
85} elsif ($magic eq "EXT") {
86  # This is an external stream reference (a jered-special)
87  &play_external_reference(\%opts, $magic);
88  exit 0;
89} elsif ($magic eq "htt") {
90  # This is an external stream reference (a jered-special)
91  &play_external_reference(\%opts, $magic);
92  exit 0;
93} elsif ($magic eq "Ogg") {
94  # Ogg-Vorbis audio file
95  &play_ogg_audio(\%opts);
96  exit 0;
97} else {
98    # add more cases later, whine for now
99    printf(ZEPHYR "I don't think this is an MPEG audio file... %02x %02x\n",
100           $magic0, $magic1);
101    print ZEPHYR "I'm going to try playing it as one anyway.\n";
102}
103# Default
104&play_mpeg_audio(\%opts);
105
106
107# Play an external stream reference
108sub play_external_reference {
109    # Retrieve those command line opts.
110    my %opts = %{shift(@_)};
111    my $magic = shift(@_);
112
113    # External references are *not* playlists; they only support
114    # a single URL in order to be fair.  A reference is structured as
115    # follows:
116    #
117    # EXT <format>\n
118    # <URI>[ <user:pass>]\n
119    # Descriptive text of the file that will be\n
120    # played; this may span multiple lines until the\n
121    # EOF
122    #
123    # Where <URI> is a valid URI for the stream, and <format> is a
124    # recognized format name (currently 'MP3' or 'OGG'). <user:pass>
125    # is an optional user and password pair for HTTP Basic Auth.
126    my $format, $uri, $userpass;
127
128    if ("$magic" eq "EXT") {
129      if (<STDIN> =~ /^EXT (.*)$/) {
130        # Found the header
131        $format = $1;
132      } else {
133        print ZEPHYR "Couldn't read EXT header\n";
134        close(ZEPHYR);
135      }
136 
137      if (<STDIN> =~ /^(\S*)\s*(.*)$/) {
138        # Found the URI (and optionally user:pass)
139        $uri = $1;
140        if ($2) {
141        $userpass = $2;
142        }
143      } else {
144        print ZEPHYR "Couldn't read URI for external reference\n";
145        close(ZEPHYR);
146      }
147  } elsif ("$magic" eq "htt") {
148      if (<STDIN> =~ /^(\S*)\s*(.*)$/) {
149          $uri=$1;
150          my $pid = open(W3M, "-|");
151          unless (defined $pid) {
152              print ZEPHYR "Couldn't fork: $!\n";
153              close(ZEPHYR);
154              return;
155          }
156
157          if ($pid) { #parent
158              $contenttype="unknown";
159              while (<W3M>) {
160                  if ($_ =~ /^Content-Type:\s(\S*)/) {
161                      $contenttype=$1;
162                  }
163              }
164              if ($contenttype eq "audio/mpeg") { $format="MP3" }
165              elsif ($contenttype eq "application/x-ogg") { $format="OGG" }
166              elsif ($contenttype eq "application/ogg") { $format="OGG" }
167              else {
168                  print ZEPHYR
169                      "Unknown Content-Type $contenttype for URI $uri\n";
170                  close(ZEPHYR);
171              }
172              close(W3M) || print ZEPHYR "w3m exited $?\n";
173          }
174          else { # child
175              exec("/mit/sipb/bin/w3m", "-dump_head", $uri)
176                  || die "Couldn't exec";
177          }
178      } else {
179        print ZEPHYR "Couldn't read URI for external reference\n";
180        close(ZEPHYR);
181    }
182  } else {
183      print ZEPHYR "Unknown syntax in play_external_reference(): $magic\n";
184      close(ZEPHYR);
185  }
186
187    # Echo the rest to the zephyr
188    while (<STDIN>) {
189      print ZEPHYR $_;
190    }
191    print ZEPHYR "\n";
192
193    # Play the thing the right way
194    if (($format eq "MP3") ||
195        ($format eq "mp3")) {
196      print ZEPHYR "Playing MP3 audio stream...\n";
197      close(ZEPHYR);
198      &play_mpeg_stream($uri, $userpass);
199    } elsif (($format eq "OGG") ||
200             ($format eq "ogg")) {
201      print ZEPHYR "Playing OggVorbis audio stream...\n";
202      close(ZEPHYR);
203      &play_ogg_stream($uri, $userpass);
204    } else {
205      print ZEPHYR "Unrecognized stream format: $format\n";
206      close(ZEPHYR);
207    }
208}
209
210
211# Play an MPEG audio stream
212# play_mpeg_stream(URI, userpass)
213sub play_mpeg_stream {
214  my $uri = shift(@_);
215  my $userpass = shift(@_);
216
217  # Add the user:pass argument if is was given to us
218  my $up = '';
219  if ($userpass) {
220    $up = '-u ' . $userpass;
221  }
222
223  system("ps ax | grep ogg123 | awk '{print $2}' | xargs kill -9");
224  system("ps ax | grep mpg123 | awk '{print $2}' | xargs kill -9");
225  system("chmod a+rw /dev/audio");
226
227  # Prepare to write status:
228  open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c sipb-auto -i ' .
229       'sipbmp3@xcb -s "SIPB LPR music spooler"');
230     
231  # fork for mpg123
232  my $pid = open(MP3STATUS, "-|");
233  unless (defined $pid) {
234      print ZEPHYR "Couldn't fork: $!\n";
235      close(ZEPHYR);
236      return;
237  }
238 
239  if ($pid) { #parent
240      # Check if there were any errors
241      if ($_ = <MP3STATUS>) {
242          print ZEPHYR "Playback completed with the following errors:\n";
243          print ZEPHYR $_;
244          while (<MP3STATUS>) {
245              print ZEPHYR $_;
246          }
247      } else {
248          print ZEPHYR "Playback completed successfully.\n";
249      }
250      close(MP3STATUS) || print ZEPHYR "mpg123 exited $?\n";
251     
252      close(ZEPHYR);
253  }
254  else { # child
255      # redirect STDERR to STDOUT
256      open STDERR, '>&STDOUT';
257      exec("/mit/infoagents/bin/mpg123", "-b16384", "-q", $up, $uri) ||
258          die "Couldn't exec";
259  }
260}
261
262# ID3 comments often have useless crap because tools like iTunes were
263# written by drooling idiots
264sub filter_comment {
265  my $comment = shift(@_);
266
267  if ($comment =~ /^engiTunes_CDDB/) {
268    return undef;
269  }
270  return $comment;
271}
272
273
274# Play an MPEG audio file
275sub play_mpeg_audio {
276    # Retrieve those command line opts.
277    my %opts = %{shift(@_)};
278
279    my %MPEGModes = ( 0 => "stereo",
280                      1 => "joint-stereo",
281                      2 => "dual-channel",
282                      3 => "single-channel");
283
284    # If it's an MP3 file, try to extract useful data
285    my $tag = get_mp3tag("$opts{'d'}/$opts{'e'}");
286    if (!$tag) {
287        print ZEPHYR "No ID3 tag found\n";
288        my @fnamearray = split /,/, $opts{'J'};
289        foreach $fname (@fnamearray) {
290            print ZEPHYR "Filename: $fname\n";
291        }
292#           print ZEPHYR "Filename: $opts{'J'}\n\n";
293    } else {
294      print ZEPHYR "Title          : $tag->{TITLE}\n";
295      print ZEPHYR "Artist         : $tag->{ARTIST}\n";
296      print ZEPHYR "Album          : $tag->{ALBUM}\n";
297      if ($tag->{TRACKNUM} =~ /(.*)\/.*/) {
298          $tag->{TRACKNUM} = $1;
299      }
300      if ($tag->{TRACKNUM}) {
301          print ZEPHYR "Track #        : $tag->{TRACKNUM}\n";
302      }
303      if ($tag->{YEAR}) {
304        print ZEPHYR "Year           : $tag->{YEAR}\n";
305      }
306      if ($tag->{GENRE}) {
307        # iTunes (?) does something weird with genres, leaving them
308        # as the string "(##)" where ## is the genre type. Let's deal
309        # with this.
310        if ($tag->{GENRE} =~ /^\((\d*)\)$/) {
311          $tag->{GENRE} = $MP3::Info::mp3_genres[$1];
312        }
313        if ($tag->{GENRE} =~ /^(\d*)$/) {
314          $tag->{GENRE} = $MP3::Info::mp3_genres[$1];
315        }
316        print ZEPHYR "Genre          : $tag->{GENRE}\n";
317      }
318      if (ref $tag->{COMMENT} eq 'ARRAY') {
319        foreach $index (0 .. $#{$tag->{COMMENT}}) {
320          if ($comment = filter_comment(@{$tag->{COMMENT}}[$index])) {
321            print ZEPHYR "Comment        : $comment\n";
322          }
323        }
324      } elsif ($tag->{COMMENT}) {
325        if ($comment = filter_comment($tag->{COMMENT})) {
326          print ZEPHYR "Comment        : $comment\n";
327        }
328      }
329
330      # Maybe get some extended ID3v2 info?
331      my $v2tag = get_mp3tag("$opts{'d'}/$opts{'e'}", 2, 1);
332      %lessinfo = %$v2tag;
333      foreach $dtag (keys %MP3::Info::v2_to_v1_names) {
334        delete $lessinfo{$dtag};
335      }
336      delete $lessinfo{'GENRE'};
337      # Delete annoying useless crap
338      my @bad_tags = ('GEO', 'GEOB', # General encapsulated object
339                      'PIC', 'APIC', # Attached picture.
340                      );
341      foreach $dtag (@bad_tags) {
342          delete $lessinfo{$dtag};
343      }
344      while (($key,$val) = each %lessinfo) {
345        printf ZEPHYR "%-15s: %s\n", $MP3::Info::v2_tag_names{$key}, $val;
346      }
347      print ZEPHYR "\n";
348    }
349
350    my $info = get_mp3info("$opts{'d'}/$opts{'e'}");
351    if (!$info) {
352        print ZEPHYR "No MPEG header found\n";
353    } else {
354        print ZEPHYR "MPEG $info->{VERSION} layer $info->{LAYER}, ";
355        if ($info->{VBR}) {
356            print ZEPHYR "VBR ";
357        }
358        print ZEPHYR "$info->{BITRATE} kbit/s, $info->{FREQUENCY} kHz ";
359        print ZEPHYR $MPEGModes{$info->{STEREO}};
360        print ZEPHYR "\n\n";
361        printf ZEPHYR "Track length: %d:%02ds\n", $info->{MM}, $info->{SS};
362    }
363    close(ZEPHYR);
364
365    # Play the file
366    # mpg123 is a crock.  If you don't give it -q, it needs to be on a pty
367    # or it SEGVs. Really.
368
369    system("chmod a+rw /dev/audio");
370    system("ps ax | grep ogg123 | awk '{print $2}' | xargs kill -9");
371    system("ps ax | grep mpg123 | awk '{print $2}' | xargs kill -9");
372    system("/mit/infoagents/bin/mpg123 -b 16384 -q - >/tmp/mpg123.out 2>&1");
373
374    # Done. Status:
375    open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c sipb-auto -i ' .
376         'sipbmp3@xcb -s "SIPB LPR music spooler"');
377
378    # Check if there were any errors
379    open(MP3STATUS, "/tmp/mpg123.out");
380    if ($_ = <MP3STATUS>) {
381        print ZEPHYR "Playback completed with the following errors:\n";
382        print ZEPHYR $_;
383        while (<MP3STATUS>) {
384            print ZEPHYR $_;
385        }
386    } else {
387        print ZEPHYR "Playback completed successfully.\n";
388        # Repeat tag data for those playing at home
389        if (!$tag) {
390          print ZEPHYR "No ID3 tag found\n";
391          print ZEPHYR "Filename: $opts{'J'}\n\n";
392        } else {
393          print ZEPHYR "Title          : $tag->{TITLE}\n";
394          print ZEPHYR "Artist         : $tag->{ARTIST}\n";
395          print ZEPHYR "Album          : $tag->{ALBUM}\n";
396          if ($tag->{TRACKNUM} =~ /(.*)\/.*/) {
397              $tag->{TRACKNUM} = $1;
398          }
399          if ($tag->{TRACKNUM}) {
400              print ZEPHYR "Track #        : $tag->{TRACKNUM}\n";
401          }
402          if ($tag->{YEAR}) {
403            print ZEPHYR "Year           : $tag->{YEAR}\n";
404          }
405          if ($tag->{GENRE}) {
406              print ZEPHYR "Genre          : $tag->{GENRE}\n";
407          }
408          if (ref $tag->{COMMENT} eq 'ARRAY') {
409            foreach $index (0 .. $#{$tag->{COMMENT}}) {
410              if ($comment = filter_comment(@{$tag->{COMMENT}}[$index])) {
411                print ZEPHYR "Comment        : $comment\n";
412              }
413            }
414          } elsif ($tag->{COMMENT}) {
415            if ($comment = filter_comment($tag->{COMMENT})) {
416              print ZEPHYR "Comment        : $comment\n";
417            }
418          }
419
420          # others
421          while (($key,$val) = each %lessinfo) {
422            printf ZEPHYR "%-15s: %s\n", $MP3::Info::v2_tag_names{$key}, $val;
423          }
424        }
425    }
426    close(MP3STATUS);
427    unlink(MP3STATUS);
428
429    close(ZEPHYR);
430}
431
432# Play an OggVorbis audio stream (doesn't support auth!)
433# play_ogg_stream(URI, userpass)
434sub play_ogg_stream {
435  my $uri = shift(@_);
436  my $userpass = shift(@_);
437
438  system("chmod a+rw /dev/audio");
439  system("ps -aef | grep ogg123 | awk '{print $2}' | xargs kill -9");
440  system("ps -aef | grep mpg123 | awk '{print $2}' | xargs kill -9");
441
442  # Prepare to write status:
443  open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c sipb-auto -i sipbmp3@xcb -s "SIPB LPR music spooler"');
444
445  # fork for ogg123
446  my $pid = open(OGGSTATUS, "-|");
447  unless (defined $pid) {
448      print ZEPHYR "Couldn't fork: $!\n";
449      close(ZEPHYR);
450      return;
451  }
452  if ($pid) { # parent
453      # Check if there were any errors
454      if ($_ = <OGGSTATUS>) {
455          print ZEPHYR "Playback completed with the following errors:\n";
456          print ZEPHYR $_;
457          while (<OGGSTATUS>) {
458              print ZEPHYR $_;
459          }
460      } else {
461          print ZEPHYR "Playback completed successfully.\n";
462      }
463      close(OGGSTATUS) || print ZEPHYR "ogg123 exited $?\n";
464           
465      close(ZEPHYR);
466  }
467  else { #child
468      # redirect STDERR to STDOUT
469      open STDERR, '>&STDOUT';
470      exec("/mit/sipb/bin/ogg123","-b40000","-dau","-q","-f","-",$uri) ||
471          die "Couldn't exec";
472  }
473}
474
475# Play an OggVorbis audio file
476sub play_ogg_audio {
477  # Retrieve those command line opts.
478  my %opts = %{shift(@_)};
479
480  # Get ogginfo stuff
481  open(OGGINFO, "/mit/sipb/bin/ogginfo $opts{'d'}/$opts{'e'}|");
482  while (<OGGINFO>) {
483    if (/(.*)=(.*)/) {
484      $ogginfo{lc("$1")} = $2;
485    }
486  }
487  close(OGGINFO);
488
489  # If there's no title, print the filename
490  if (!$ogginfo{'title'}) {
491    print ZEPHYR "No ogginfo data found\n";
492    print ZEPHYR "Filename: $opts{'J'}\n";
493  } else {
494    print ZEPHYR "Title          : $ogginfo{'title'}\n";
495    print ZEPHYR "Artist         : $ogginfo{'artist'}\n";
496    print ZEPHYR "Album          : $ogginfo{'album'}\n";
497    print ZEPHYR "Track #        : $ogginfo{'tracknumber'}\n";
498
499    # others
500    %lessinfo = %ogginfo;
501    delete $lessinfo{'filename'};
502    delete $lessinfo{'title'};
503    delete $lessinfo{'artist'};
504    delete $lessinfo{'album'};
505    delete $lessinfo{'tracknumber'};
506    delete $lessinfo{'header_integrity'};
507    delete $lessinfo{'stream_integrity'};
508    delete $lessinfo{'file_truncated'};
509    delete $lessinfo{'version'};
510    delete $lessinfo{'channels'};
511    delete $lessinfo{'bitrate_upper'};
512    delete $lessinfo{'bitrate_nominal'};
513    delete $lessinfo{'bitrate_lower'};
514    delete $lessinfo{'bitrate_average'};
515    delete $lessinfo{'length'};
516    delete $lessinfo{'playtime'};
517    delete $lessinfo{'kbps'};
518    delete $lessinfo{'time'};
519    delete $lessinfo{'rg_radio'};
520    delete $lessinfo{'rg_audiophile'};
521    delete $lessinfo{'rg_peak'};
522    delete $lessinfo{'replaygain_album_peak'};
523    delete $lessinfo{'replaygain_track_peak'};
524    delete $lessinfo{'replaygain_album_gain'};
525    delete $lessinfo{'replaygain_track_gain'};
526
527    while (($key,$val) = each %lessinfo) {
528      printf ZEPHYR "%-15s: %s\n", $key, $val;
529    }
530  }
531
532  printf ZEPHYR "\nOgg Vorbis, average %.1f kbit/s, %d channels\n\n",
533    $ogginfo{'bitrate_average'}/1024, $ogginfo{'channels'};
534  print ZEPHYR "Track length: $ogginfo{'playtime'}s\n";
535  close(ZEPHYR);
536
537  # Play the file
538
539  system("chmod a+rw /dev/audio");
540  system("ps -aef | grep ogg123 | awk '{print $2}' | xargs kill -9");
541  system("ps -aef | grep mpg123 | awk '{print $2}' | xargs kill -9");
542  system("/mit/sipb/bin/ogg123 -b 40000 -dau -q -f - - 2> /tmp/ogg123.out | audioplay");
543
544  # Done. Status:
545  open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c sipb-auto -i sipbmp3@xcb -s "SIPB LPR music spooler"');
546
547  # Check if there were any errors
548  open(OGGSTATUS, "/tmp/ogg123.out");
549  if ($_ = <OGGSTATUS>) {
550    print ZEPHYR "Playback completed with the following errors:\n";
551    print ZEPHYR $_;
552    while (<OGGSTATUS>) {
553      print ZEPHYR $_;
554    }
555  } else {
556    print ZEPHYR "Playback completed successfully.\n";
557    # Repeat tag data for those playing at home
558    if (!$ogginfo{'title'}) {
559      print ZEPHYR "No ogginfo data found\n";
560      print ZEPHYR "Filename: $opts{'J'}\n\n";
561    } else {
562      print ZEPHYR "Title          : $ogginfo{'title'}\n";
563      print ZEPHYR "Artist         : $ogginfo{'artist'}\n";
564      print ZEPHYR "Album          : $ogginfo{'album'}\n";
565      print ZEPHYR "Track #        : $ogginfo{'tracknumber'}\n";
566
567      # others
568      while (($key,$val) = each %lessinfo) {
569        printf ZEPHYR "%-15s: %s\n", $key, $val;
570      }
571    }
572  }
573
574  close(OGGSTATUS);
575  unlink(OGGSTATUS);
576
577  close(ZEPHYR);
578}
Note: See TracBrowser for help on using the repository browser.