Changeset d2fa590
- Timestamp:
- Jun 24, 2010, 1:28:09 AM (14 years ago)
- 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)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
gutenbach/debian/lib/gutenbach-filter
r80bbeea rd2fa590 15 15 # apparently neither does Quentin Smith <quentin@mit.edu> 16 16 17 use strict; 18 use warnings; 17 19 use Image::ExifTool qw(ImageInfo); 18 20 use File::Spec::Functions; 19 use File::Temp qw{temp dir};21 use File::Temp qw{tempfile tempdir}; 20 22 use File::Basename qw(basename); 21 23 use LWP::UserAgent; 22 24 use Data::Dumper; 23 25 use 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 } 26 use English; 27 28 require "/usr/lib/gutenbach/config/gutenbach-filter-config.pl" or die "Unable to load configuration"; 40 29 41 30 my $ua = new LWP::UserAgent; 42 31 32 # Replace STDERR with a log file in /tmp. 33 open(CUPS, ">&STDERR") or die "Unable to copy CUPS filehandle"; 43 34 close(STDERR); 44 35 open(STDERR, ">>", "/tmp/gutenbach.log") or warn "Couldn't open log: $!"; 45 36 37 # Set the TERM environment (for the benefit of mplayer?) 38 # I don't know why we do this --quentin 46 39 $ENV{"TERM"}="vt100"; 47 40 48 41 print STDERR "STDERR FROM SPOOL FILTER\n"; 49 42 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. 78 63 79 64 printf(STDERR "Got \@ARGV: %s\n", Dumper(\@ARGV)); 80 65 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 66 my %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. 78 if (!$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 87 printf(STDERR "Got \%arguments: %s\n", Dumper(\%arguments)); 88 89 # Open up a zwrite command to announce the current track. 90 my @zwrite_command = (qw(/usr/bin/zwrite -d -n -c), $zephyr_class, "-i", $queue.'@'.$host, "-s", "Gutenbach Music Spooler"); 91 92 print STDERR "Invoking @zwrite_command\n"; 93 open(ZEPHYR, "|-", @zwrite_command) or die "Couldn't launch zwrite: $!"; 94 95 print(ZEPHYR "$arguments{user}\@$arguments{options}{job-originating-host-name} is playing:\n"); 96 my $status = "User: $arguments{user}\@$arguments{options}{job-originating-host-name}."); 97 98 # SIGHUP handler, in case we were aborted 110 99 sub 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; 118 105 } 119 106 $SIG{HUP} = \&clear_status; 120 107 121 # So, the file we're currently processing is "-d/-e".122 123 108 # Read the metadata information from the file. 124 my ($filepath) = catfile($opts{'d'}, $opts{'e'});109 my ($filepath) = $options{"file"}; 125 110 my ($fileinfo) = ImageInfo($filepath); 126 111 my ($magic) = $fileinfo->{FileType}; 127 112 128 113 if ($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 } 141 elsif ($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 } 163 elsif (-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 174 171 close(ZEPHYR); 175 close(STATUS); 176 play_mplayer_audio($filepath, \%opts); 177 172 print CUPS "NOTICE: $status\n"; 173 play_mplayer_audio($filepath, \%arguments); 174 175 # Remove the symlink we made earlier for the filetype. 178 176 if ($magic) { 179 180 177 unlink($newpath); 178 rmdir($tempdir); 181 179 } 182 180 183 181 # Play an external stream reference 184 182 sub 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; 224 225 } 225 226 226 227 sub 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); 242 244 } 243 245 … … 246 248 sub get_shoutcast { 247 249 my $uri = shift(@_); 248 250 249 251 my $response = $ua->get($uri); 250 252 … … 257 259 } 258 260 } 259 261 260 262 # choose a random server 261 263 $server = int(rand scalar(@uris)); … … 266 268 267 269 sub 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 } 298 302 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.