source: debian/lib/gutenbach @ 53e9374

debianmacno-cupsnodebathenaweb
Last change on this file since 53e9374 was 53e9374, checked in by Jessica B. Hamrick <jhamrick@…>, 14 years ago

Remove other submodules and move some things around

  • Property mode set to 100755
File size: 9.8 KB
Line 
1#!/usr/bin/perl
2# Play the data on STDIN as an audio file
3#
4# $Id: gutenbach-filter,v 1.26 2009/02/20 00:27:17 geofft Exp root $
5# $Source: /usr/local/bin/RCS/gutenbach-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# apparently neither does Quentin Smith <quentin@mit.edu>
16
17use strict;
18use warnings;
19use Image::ExifTool qw(ImageInfo);
20use File::Spec::Functions;
21use File::Temp qw{tempfile tempdir};
22use File::Basename qw(basename);
23use LWP::UserAgent;
24use Data::Dumper;
25use IPC::Open2;
26use English;
27
28require "/usr/lib/gutenbach/config/gutenbach-filter-config.pl" or die "Unable to load configuration";
29
30my $ua = new LWP::UserAgent;
31
32# Replace STDERR with a log file in /tmp.
33open(CUPS, ">&STDERR") or die "Unable to copy CUPS filehandle";
34close(STDERR);
35open(STDERR, ">>", "/tmp/gutenbach.log") or warn "Couldn't open log: $!";
36
37# Set the TERM environment (for the benefit of mplayer?)
38# I don't know why we do this --quentin
39$ENV{"TERM"}="vt100";
40
41print STDERR "STDERR FROM SPOOL FILTER\n";
42
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.
63
64printf(STDERR "Got \@ARGV: %s\n", Dumper(\@ARGV));
65
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
99sub clear_status {
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;
105}
106$SIG{HUP} = \&clear_status;
107
108# Read the metadata information from the file.
109my ($filepath) = $options{"file"};
110my ($fileinfo) = ImageInfo($filepath);
111my ($magic) = $fileinfo->{FileType};
112
113if ($magic) {
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
171close(ZEPHYR);
172print CUPS "NOTICE: $status\n";
173play_mplayer_audio($filepath, \%arguments);
174
175# Remove the symlink we made earlier for the filetype.
176if ($magic) {
177  unlink($newpath);
178  rmdir($tempdir);
179}
180
181# Play an external stream reference
182sub resolve_external_reference {
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;
225}
226
227sub split_playlist {
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);
244}
245
246# Process a Shoutcast playlist
247# get_shoutcast(URI)
248sub get_shoutcast {
249  my $uri = shift(@_);
250
251  my $response = $ua->get($uri);
252
253  foreach (split("\n", $response->content())) {
254      if (/^File\d+=(\S+)/) {
255          push(@uris, $1);
256      }
257      if (/^Title\d+=(.+)$/) {
258          push(@titles, $1);
259      }
260  }
261
262  # choose a random server
263  $server = int(rand scalar(@uris));
264  # print the name of the stream if available
265  print ZEPHYR "$titles[$server]\n";
266  return $uris[$server];
267}
268
269sub play_mplayer_audio {
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  }
302  else { # child
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 TracBrowser for help on using the repository browser.