source: gutenbach/debian/lib/sipbmp3-filter @ b0d9dda

debianmacno-cupsnodebathenaweb
Last change on this file since b0d9dda was b0d9dda, checked in by arolfe <arolfe>, 22 years ago

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