source: gutenbach/debian/lib/sipbmp3-filter @ 148111e

debianmacno-cupsnodebathenaweb
Last change on this file since 148111e was 148111e, checked in by arolfe <arolfe>, 21 years ago

-rwx------ 1 arolfe mit 14958 Dec 20 2002 sipbmp3-filter

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