Changeset d2fa590


Ignore:
Timestamp:
Jun 24, 2010, 1:28:09 AM (14 years ago)
Author:
Jessica B. Hamrick <jhamrick@…>
Branches:
master, debian, mac, no-cups, nodebathena, web
Children:
cab423c
Parents:
edce45f
git-author:
Quentin Smith <quentin@…> (03/20/10 19:08:49)
git-committer:
Jessica B. Hamrick <jhamrick@…> (06/24/10 01:28:09)
Message:

Convert gutenbach-filter to CUPS (I hope!)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • gutenbach/debian/lib/gutenbach-filter

    r80bbeea rd2fa590  
    1515# apparently neither does Quentin Smith <quentin@mit.edu>
    1616
     17use strict;
     18use warnings;
    1719use Image::ExifTool qw(ImageInfo);
    1820use File::Spec::Functions;
    19 use File::Temp qw{tempdir};
     21use File::Temp qw{tempfile tempdir};
    2022use File::Basename qw(basename);
    2123use LWP::UserAgent;
    2224use Data::Dumper;
    2325use IPC::Open2;
    24 
    25 my $zephyr_class = `hostname`;
    26 chomp($zephyr_class);
    27 my $host = `hostname`;
    28 chomp($host);
    29 my $queue = "gutenbach";
    30 
    31 # Configuration
    32 my $config_file = "/usr/lib/gutenbach/config/gutenbach-filter-config.pl";
    33 if (-r $config_file) {
    34     # Inline the configuration file
    35     local $/;
    36     my $fh;
    37     open $fh, $config_file;
    38     eval <$fh>;
    39 }
     26use English;
     27
     28require "/usr/lib/gutenbach/config/gutenbach-filter-config.pl" or die "Unable to load configuration";
    4029
    4130my $ua = new LWP::UserAgent;
    4231
     32# Replace STDERR with a log file in /tmp.
     33open(CUPS, ">&STDERR") or die "Unable to copy CUPS filehandle";
    4334close(STDERR);
    4435open(STDERR, ">>", "/tmp/gutenbach.log") or warn "Couldn't open log: $!";
    4536
     37# Set the TERM environment (for the benefit of mplayer?)
     38# I don't know why we do this --quentin
    4639$ENV{"TERM"}="vt100";
    4740
    4841print STDERR "STDERR FROM SPOOL FILTER\n";
    4942
    50 # set real uid to be effective uid
    51 $< = $>;
    52 
    53 # Select the correct output device and set the volume
    54 #system("amixer -q set Headphone 100\% unmute");
    55 
    56 # The command line we get from lpd is (no spaces between options and args):
    57 #  -C lpr -C class
    58 #  -A LPRng internal identifier
    59 #  -H originating host
    60 #  -J lpr -J jobname (default: list of files)
    61 #  -L lpr -U username
    62 #  -P logname
    63 #  -Q queuename (lpr -Q)
    64 #  -Z random user-specified options
    65 #  -a printcap af (accounting file name)
    66 #  -d printcap sd entry (spool dir)
    67 #  -e print job data file name (currently being processed)
    68 #  -h print job originiating host (same as -H)
    69 #  -j job number in spool queue
    70 #  -k print job control file name
    71 #  -l printcap pl (page length)
    72 #  -n user name (same as -L)
    73 #  -s printcap sf (status file)
    74 #  -w printcap pw (page width)
    75 #  -x printcap px (page x dimension)
    76 #  -y printcap py (page y dimension)
    77 # accounting file name
     43# CUPS provides us with these arguments:
     44#
     45# argv[1]
     46# The job ID
     47# argv[2]
     48# The user printing the job
     49# argv[3]
     50# The job name/title
     51# argv[4]
     52# The number of copies to print
     53# argv[5]
     54# The options that were provided when the job was submitted
     55# argv[6]
     56# The file to print (first program only)
     57#
     58# The scheduler runs one or more of these programs to print any given
     59# job. The first filter reads from the print file and writes to the
     60# standard output, while the remaining filters read from the standard
     61# input and write to the standard output. The backend is the last
     62# filter in the chain and writes to the device.
    7863
    7964printf(STDERR "Got \@ARGV: %s\n", Dumper(\@ARGV));
    8065
    81 my %opts;
    82 
    83 my @NEWARGV;
    84 
    85 foreach my $arg (@ARGV) {
    86   if ($arg =~ m/^-([a-zA-Z])(.*)$/) {
    87     $opts{$1} = $2;
    88   } else {
    89     push @NEWARGV, @ARGV;
    90   }
    91 }
    92 
    93 @ARGV = @NEWARGV;
    94 
    95 printf(STDERR Dumper(\%opts));
    96 
    97 print(STDERR '/usr/bin/zwrite -d -n -c '.$zephyr_class.' -i '.$queue.'@'.$host.' -s "Gutenbach Music Spooler"');
    98 
    99 # Status messages at start of playback
    100 open(ZEPHYR, '|/usr/bin/zwrite -d -n -c '.$zephyr_class.' -i '.$queue.'@'.$host.' -s "Gutenbach Music Spooler"');
    101 
    102 # For the Now Playing remctl command
    103 open(STATUS, '>', '/var/run/gutenbach/status') or die("Can't open status file /var/run/gutenbach/status");
    104 
    105 
    106 print(ZEPHYR "$opts{'n'}\@$opts{'H'} is playing:\n");
    107 print(STATUS "User: $opts{'n'}\@$opts{'H'}\n");
    108 
    109 # SIGHUP handler
     66my %arguments = (
     67                 job-id => $ARGV[0],
     68                 user => $ARGV[1],
     69                 job-title => $ARGV[2],
     70                 copies => $ARGV[3],
     71                 options => {split(/[= ]/, $ARGV[4])},
     72                 file => $ARGV[5],
     73                );
     74
     75# If we weren't given a filename, we need to read from stdin. Since
     76# mplayer really wants a file, let's write out to a temporary file
     77# first.
     78if (!$arguments{"file"}) {
     79  my ($fh, $file) = tempfile("gutenbachXXXXX", UNLINK => 1); # Ask File::Temp for a safe temporary file
     80  my $buf;
     81  while (read(STDIN, $buf, 1024*1024)) { # Read 1M at a time and put it in the temporary file
     82    print $fh $buf;
     83  }
     84  close($fh);
     85}
     86
     87printf(STDERR "Got \%arguments: %s\n", Dumper(\%arguments));
     88
     89# Open up a zwrite command to announce the current track.
     90my @zwrite_command = (qw(/usr/bin/zwrite -d -n -c), $zephyr_class, "-i", $queue.'@'.$host, "-s", "Gutenbach Music Spooler");
     91
     92print STDERR "Invoking @zwrite_command\n";
     93open(ZEPHYR, "|-", @zwrite_command) or die "Couldn't launch zwrite: $!";
     94
     95print(ZEPHYR "$arguments{user}\@$arguments{options}{job-originating-host-name} is playing:\n");
     96my $status = "User: $arguments{user}\@$arguments{options}{job-originating-host-name}.");
     97
     98# SIGHUP handler, in case we were aborted
    11099sub clear_status {
    111     # Possible race condition if the previous status is still going
    112     open(STA, '>', '/var/run/gutenbach/status');
    113     close(STA);
    114     open(ZEPH, '|/usr/bin//zwrite -d -n -c '.$zephyr_class.' -i '.$queue.'@'.$host.' -s "Gutenbach Music Spooler"');
    115     print(ZEPH "Playback aborted.\n");
    116     close(ZEPH);
    117     die;
     100  my @zwrite_command = (qw(/usr/bin/zwrite -d -n -c), $zephyr_class, "-i", $queue.'@'.$host, "-s", "Gutenbach Music Spooler");
     101  open(ZEPH, "|-", @zwrite_command);
     102  print(ZEPH "Playback aborted.\n");
     103  close(ZEPH);
     104  die;
    118105}
    119106$SIG{HUP} = \&clear_status;
    120107
    121 # So, the file we're currently processing is "-d/-e".
    122 
    123108# Read the metadata information from the file.
    124 my ($filepath) = catfile($opts{'d'}, $opts{'e'});
     109my ($filepath) = $options{"file"};
    125110my ($fileinfo) = ImageInfo($filepath);
    126111my ($magic) = $fileinfo->{FileType};
    127112
    128113if ($magic) {
    129     printf(ZEPHYR "%s file %s\n", $magic, $opts{'J'});
    130     printf(STATUS "Filetype: %s\n", $magic);
    131     printf(STATUS "Filename: %s\n", $opts{'J'});
    132     if (exists $fileinfo->{'Title'}) {
    133         printf(ZEPHYR "\@b{%s}\n", $fileinfo->{'Title'}) if exists $fileinfo->{'Title'};
    134         printf(STATUS "Title: %s\n", $fileinfo->{'Title'});
    135     }
    136     foreach my $key (qw/Artist Album AlbumArtist/) {
    137         if (exists $fileinfo->{$key}) {
    138             printf(ZEPHYR "%s\n", $fileinfo->{$key}) if exists $fileinfo->{$key};
    139             printf(STATUS "%s: %s\n", $key, $fileinfo->{$key});
    140         }
    141     }
    142     my $tempdir = tempdir();
    143     $opts{'J'} =~ s/_mp3/.mp3/; #awful hack -- geofft
    144     my $newpath = $tempdir . '/' . basename($opts{'J'});
    145     symlink($filepath, $newpath);
    146     $filepath = $newpath;
    147 }
    148 elsif ($opts{'C'} eq 'Z') {
    149     $filepath = resolve_external_reference($filepath, \%opts);
    150     if ($filepath =~ m|http://www\.youtube\.com/watch\?v=|) {
    151         $pid = open2($out, $in, qw{youtube-dl -g}, $filepath);
    152         #$title = <$out>;
    153         $title = "";
    154         print ZEPHYR "YouTube video $filepath\n$title";
    155         print STATUS "YouTube video $filepath\n$title";
    156         $filepath = <$out>;
    157         chomp $filepath;
    158         waitpid $pid, 0;
    159     } else {
    160         print STDERR "Resolved external reference to $filepath\n";
    161         printf(ZEPHYR "%s\n", $filepath);
    162         printf(STATUS "External: %s\n", $filepath);
    163     }
    164 }
    165 elsif (-T $filepath) {
    166     split_playlist($filepath, \%opts);
    167     close(ZEPHYR);
    168     close(STATUS);
    169     exit 0;
    170 }
    171 
    172 #printf(STDERR "Job priority %s\n", $opts{'C'}) if $opts{'C'} eq 'Z';
    173 #printf(ZEPHYR "Job priority %s\n", $opts{'C'}) if ($opts{'C'} && ($opts{'C'} ne 'A'));
     114  # $magic means that Image::ExifTool was able to identify the type of file
     115  printf(ZEPHYR "%s file %s\n", $magic, $arguments{"job-title"});
     116  $status .= sprintf(" Filetype: %s.", $magic);
     117  $status .= sprintf(" Filename: %s.", $arguments{"job-title"});
     118
     119  if (exists $fileinfo->{'Title'}) {
     120    printf(ZEPHYR "\@b{%s}\n", $fileinfo->{'Title'}) if exists $fileinfo->{'Title'};
     121    $status .= sprintf(" Title: %s.", $fileinfo->{'Title'});
     122  }
     123  foreach my $key (qw/Artist Album AlbumArtist/) {
     124    if (exists $fileinfo->{$key}) {
     125      printf(ZEPHYR "%s\n", $fileinfo->{$key}) if exists $fileinfo->{$key};
     126      $status .= sprintf(" %s: %s\n", $key, $fileinfo->{$key});
     127    }
     128  }
     129
     130  my $tempdir = tempdir();
     131  #awful hack -- geofft
     132  #== -- quentin
     133  # This code appears to create a new temporary directory and symlink
     134  # the job file into the temporary directory under the original
     135  # filename. I think this is because mplayer sometimes uses the file
     136  # extension to identify a filetype.
     137  my $newpath = $tempdir . '/' . basename($arguments{"job-title"});
     138  symlink($filepath, $newpath);
     139  $filepath = $newpath;
     140}
     141elsif ($arguments{copies} == 42) {
     142  # This is a flag that is set by jobs queued by split_playlist(); it tells us to not try to split the playlist again.
     143  # Call resolve_external_reference to apply some heuristics to determine the filetype.
     144  $filepath = resolve_external_reference($filepath, \%arguments);
     145  if ($filepath =~ m|http://www\.youtube\.com/watch\?v=|) {
     146    # YouTube URLs are resolved by the youtube-dl command.
     147    # Launch youtube-dl
     148    open(YTDL, "-|", "youtube-dl", "-g", $filepath) or die "Unable to invoke youtube-dl";
     149    # Read the title (currently not doing so because youtube-dl doesn't know how to get the title.
     150    my $title = ""; # <YTDL>
     151    # Print the title to zephyr and the status string.
     152    print ZEPHYR "YouTube video $filepath\n$title";
     153    $status .= " YouTube video $filepath. $title.";
     154    # youtube-dl prints the URL of the flash video, which we pass to mplayer as a filename.
     155    $filepath = <YTDL>;
     156    chomp $filepath;
     157  } else { # Doesn't appear to be a YouTube URL.
     158    print STDERR "Resolved external reference to $filepath\n";
     159    printf(ZEPHYR "%s\n", $filepath);
     160    $status .= sprintf(" External: %s\n", $filepath);
     161  }
     162}
     163elsif (-T $filepath) { # If the file appears to be a text file, treat it as a playlist.
     164  split_playlist($filepath, \%arguments);
     165  close(ZEPHYR);
     166  # See http://www.cups.org/documentation.php/api-filter.html#MESSAGES
     167  print CUPS "NOTICE: $status\n";
     168  exit 0;
     169}
     170
    174171close(ZEPHYR);
    175 close(STATUS);
    176 play_mplayer_audio($filepath, \%opts);
    177 
     172print CUPS "NOTICE: $status\n";
     173play_mplayer_audio($filepath, \%arguments);
     174
     175# Remove the symlink we made earlier for the filetype.
    178176if ($magic) {
    179     unlink($newpath);
    180     rmdir($tempdir);
     177  unlink($newpath);
     178  rmdir($tempdir);
    181179}
    182180
    183181# Play an external stream reference
    184182sub resolve_external_reference {
    185     # Retrieve those command line opts.
    186     my ($filepath, $opts) = @_;
    187 
    188     my $format, $uri, $userpass;
    189 
    190     if (<STDIN> =~ /^(\S+)/) {
    191         $uri=$1;
    192 
    193         if ($uri =~ m|http://www\.youtube\.com/watch\?v=|) {
    194             return $uri;
    195         }
    196 
    197         my $response = $ua->head($uri);
    198        
    199         $contenttype=($response->content_type() or "unknown");
    200        
    201         if ($contenttype eq "audio/mpeg") { $format="MP3" }
    202         elsif ($contenttype eq "application/x-ogg") { $format="OGG" }
    203         elsif ($contenttype eq "application/ogg") { $format="OGG" }
    204         elsif ($contenttype eq "audio/x-scpls") { $format="SHOUTCAST" }
    205         else {
    206             print ZEPHYR
    207                 "Unknown Content-Type $contenttype for URI $uri\n";
    208         }
    209     } else {
    210         print ZEPHYR "Couldn't read URI for external reference\n";
    211         return $filepath;
    212     }
    213 
    214     if ($format eq "SHOUTCAST") {
    215         print ZEPHYR "Shoutcast playlist...\n";
    216         #Don't close ZEPHYR yet, will print the name of the stream if available
    217         return &get_shoutcast($uri);
    218     } elsif ($format eq "MP3") {
    219     } elsif ($format eq "OGG") {
    220     } else {
    221       print ZEPHYR "Unrecognized stream format: $format\n";
    222     }
    223     return $uri;
     183  # Retrieve those command line opts.
     184  my ($filepath, $arguments) = @_;
     185
     186  my $format, $uri, $userpass;
     187
     188  open(FILE, "<", $filepath) or die "Couldn't open spool file";
     189  if (<FILE> =~ /^(\S+)/) {
     190    # Take the leading non-whitespace as a URL
     191    $uri=$1;
     192
     193    if ($uri =~ m|http://www\.youtube\.com/watch\?v=|) {
     194      return $uri;
     195    }
     196
     197    # Fetch the URL with a HEAD request to get the content type
     198    my $response = $ua->head($uri);
     199
     200    my $contenttype=($response->content_type() or "unknown");
     201
     202    if ($contenttype eq "audio/mpeg") { $format="MP3" }
     203    elsif ($contenttype eq "application/x-ogg") { $format="OGG" }
     204    elsif ($contenttype eq "application/ogg") { $format="OGG" }
     205    elsif ($contenttype eq "audio/x-scpls") { $format="SHOUTCAST" }
     206    else {
     207      print ZEPHYR
     208        "Unknown Content-Type $contenttype for URI $uri\n";
     209    }
     210  } else { # Unable to match the URL regex
     211    print ZEPHYR "Couldn't read URI for external reference\n";
     212    # Return the existing path, in the hopes that mplayer knows what to do with it.
     213    return $filepath;
     214  }
     215
     216  if ($format eq "SHOUTCAST") {
     217    print ZEPHYR "Shoutcast playlist...\n";
     218    return get_shoutcast($uri);
     219  } elsif ($format eq "MP3") {
     220  } elsif ($format eq "OGG") {
     221  } else {
     222    print ZEPHYR "Unrecognized stream format: $format\n";
     223  }
     224  return $uri;
    224225}
    225226
    226227sub split_playlist {
    227     my ($file, $opts) = @_;
    228 
    229     my $i = 0;
    230    
    231     while (<STDIN>) {
    232         chomp;
    233         if (/^([^#]\S+)/) {
    234             printf (STDERR "Found line: %s\n", $_);
    235             open(LPR, "|-", 'mit-lpr', '-P'.$queue.'@localhost', '-CZ', '-J'.$opts->{J});
    236             print LPR $1;
    237             close(LPR);
    238         $i++;
    239         }
    240     }
    241     printf(ZEPHYR "Playlist containing %d valid entries, split into separate jobs.\n", $i);
     228  my ($file, $arguments) = @_;
     229
     230  my $i = 0;
     231
     232  open(FILE, "<", $filepath) or die "Couldn't open spool file";
     233  while (<FILE>) {
     234    chomp;
     235    if (/^([^#]\S+)/) {
     236      printf (STDERR "Found playlist line: %s\n", $_);
     237      open(LPR, "|-", 'lpr', '-P'.$queue.'@localhost', '-#', '42', '-J', $arguments->{"job-title"}, '-o', 'job-priority=100');
     238      print LPR $1;
     239      close(LPR);
     240      $i++;
     241    }
     242  }
     243  printf(ZEPHYR "Playlist containing %d valid entries, split into separate jobs.\n", $i);
    242244}
    243245
     
    246248sub get_shoutcast {
    247249  my $uri = shift(@_);
    248  
     250
    249251  my $response = $ua->get($uri);
    250252
     
    257259      }
    258260  }
    259  
     261
    260262  # choose a random server
    261263  $server = int(rand scalar(@uris));
     
    266268
    267269sub play_mplayer_audio {
    268     my ($filepath, $opts) = @_;
    269 
    270     # Prepare to write status:
    271     open(ZEPHYR, '|/usr/bin//zwrite -d -n -c '.$zephyr_class.' -i '.$queue.'\@'.$host.' -s "Gutenbach Music Spooler"');
    272    
    273     # fork for mpg123
    274     my $pid = open(MP3STATUS, "-|");
    275     unless (defined $pid) {
    276         print ZEPHYR "Couldn't fork: $!\n";
    277         close(ZEPHYR);
    278         return;
    279     }
    280    
    281     if ($pid) { #parent
    282         # Check if there were any errors
    283         if ($_ = <MP3STATUS>) {
    284             print ZEPHYR "Playback completed with the following errors:\n";
    285             print ZEPHYR $_;
    286             while (<MP3STATUS>) {
    287                 print ZEPHYR $_;
    288             }
    289         } else {
    290             print ZEPHYR "Playback completed successfully.\n";
    291         }
    292         close(MP3STATUS) || print ZEPHYR "mplayer exited $?\n";
    293        
    294         close(ZEPHYR);
    295         open(STATUS, '>', '/var/run/gutenbach/status');
    296         close(STATUS);
    297     }
     270  my ($filepath, $opts) = @_;
     271
     272
     273  # Open up a zwrite command to show the mplayer output
     274  my @zwrite_command = (qw(/usr/bin/zwrite -d -n -c), $zephyr_class, "-i", $queue.'@'.$host, "-s", "Gutenbach Music Spooler");
     275
     276  print STDERR "Invoking @zwrite_command\n";
     277  open(ZEPHYR, "|-", @zwrite_command) or die "Couldn't launch zwrite: $!";
     278
     279  # fork for mplayer
     280  my $pid = open(MP3STATUS, "-|");
     281  unless (defined $pid) {
     282    print ZEPHYR "Couldn't fork: $!\n";
     283    close(ZEPHYR);
     284    return;
     285  }
     286
     287  if ($pid) { #parent
     288    # Check if there were any errors
     289    if ($_ = <MP3STATUS>) {
     290      print ZEPHYR "Playback completed with the following errors:\n";
     291      print ZEPHYR $_;
     292      while (<MP3STATUS>) {
     293        print ZEPHYR $_;
     294      }
     295    } else {
     296      print ZEPHYR "Playback completed successfully.\n";
     297    }
     298    close(MP3STATUS) || print ZEPHYR "mplayer exited $?\n";
     299
     300    close(ZEPHYR);
     301  }
    298302  else { # child
    299       # redirect STDERR to STDOUT
    300       open STDERR, '>&STDOUT';
    301       # make sure that mplayer doesn't try to intepret the file as keyboard input
    302       close(STDIN);
    303       open(STDIN, "/dev/null");
    304       #print STDERR Dumper([qw|/usr/bin/mplayer -nolirc -ao alsa -quiet|, $filepath]);
    305       my @args = (qw|/usr/bin/mplayer -vo fbdev2 -zoom -x 1024 -y 768 -framedrop -nolirc -ao alsa -cache 512 -really-quiet |, $filepath);
    306       #print STDERR "About to exec: ", Dumper([@args]);
    307       exec(@args) ||
    308           die "Couldn't exec";
    309   }
    310 }
    311 
    312 # ID3 comments often have useless crap because tools like iTunes were
    313 # written by drooling idiots
    314 sub filter_comment {
    315   my $comment = shift(@_);
    316 
    317   if ($comment =~ /^engiTunes_CDDB/) {
    318     return undef;
    319   }
    320   return $comment;
    321 }
    322 
    323 
     303    # redirect STDERR to STDOUT
     304    open STDERR, '>&STDOUT';
     305    # make sure that mplayer doesn't try to intepret the file as keyboard input
     306    close(STDIN);
     307    open(STDIN, "/dev/null");
     308
     309    my @args = (qw|/usr/bin/mplayer -vo fbdev2 -zoom -x 1024 -y 768 -framedrop -nolirc -ao alsa -cache 512 -really-quiet |, $filepath);
     310    #print STDERR "About to exec: ", Dumper([@args]);
     311    exec(@args) ||
     312      die "Couldn't exec";
     313  }
     314}
Note: See TracChangeset for help on using the changeset viewer.