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

debianmacno-cupsnodebathenaweb
Last change on this file since f6c9736 was f6c9736, checked in by Geoffrey Thomas <geofft@…>, 15 years ago

Support YouTube? videos.

Requires youtube-dl to be installed.

  • Property mode set to 100755
File size: 8.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.26 2009/02/20 00:27:17 geofft Exp root $
5# $Source: /usr/local/bin/RCS/sipbmp3-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 Image::ExifTool qw(ImageInfo);
18use File::Spec::Functions;
19use File::Temp qw{tempdir};
20use File::Basename qw(basename);
21use LWP::UserAgent;
22use Data::Dumper;
23use IPC::Open2;
24
25my $zephyr_class = "sipb-auto";
26my $host = "zsr";
27
28# Configuration
29my $config_file = "/etc/sipbmp3-filter-config.pl";
30if (-r $config_file) {
31    # Inline the configuration file
32    local $/;
33    my $fh;
34    open $fh, $config_file;
35    eval <$fh>;
36}
37
38my $ua = new LWP::UserAgent;
39
40close(STDERR);
41open(STDERR, ">>", "/tmp/sipbmp3.log") or warn "Couldn't open log: $!";
42
43$ENV{"TERM"}="vt100";
44
45print STDERR "STDERR FROM SPOOL FILTER\n";
46
47# set real uid to be effective uid
48$< = $>;
49
50# Select the correct output device and set the volume
51#system("amixer -q set Headphone 100\% unmute");
52
53# The command line we get from lpd is (no spaces between options and args):
54#  -C lpr -C class
55#  -A LPRng internal identifier
56#  -H originating host
57#  -J lpr -J jobname (default: list of files)
58#  -L lpr -U username
59#  -P logname
60#  -Q queuename (lpr -Q)
61#  -Z random user-specified options
62#  -a printcap af (accounting file name)
63#  -d printcap sd entry (spool dir)
64#  -e print job data file name (currently being processed)
65#  -h print job originiating host (same as -H)
66#  -j job number in spool queue
67#  -k print job control file name
68#  -l printcap pl (page length)
69#  -n user name (same as -L)
70#  -s printcap sf (status file)
71#  -w printcap pw (page width)
72#  -x printcap px (page x dimension)
73#  -y printcap py (page y dimension)
74# accounting file name
75
76printf(STDERR "Got \@ARGV: %s\n", Dumper(\@ARGV));
77
78my %opts;
79
80my @NEWARGV;
81
82foreach my $arg (@ARGV) {
83  if ($arg =~ m/^-([a-zA-Z])(.*)$/) {
84    $opts{$1} = $2;
85  } else {
86    push @NEWARGV, @ARGV;
87  }
88}
89
90@ARGV = @NEWARGV;
91
92printf(STDERR Dumper(\%opts));
93
94# Status messages at start of playback
95open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c '. $zephyr_class .' -i ' .
96  'sipbmp3@'.$host.' -s "SIPB LPR music spooler"');
97
98# For the Now Playing remctl command
99open(STATUS, '>', '/var/run/sipbmp3/status') or die("Can't open status file /var/run/sipbmp3/status");
100
101print(ZEPHYR "$opts{'n'}\@$opts{'H'} is playing:\n");
102print(STATUS "User: $opts{'n'}\@$opts{'H'}\n");
103
104# SIGHUP handler
105sub clear_status {
106    # Possible race condition if the previous status is still going
107    open(STA, '>', '/var/run/sipbmp3/status');
108    close(STA);
109    open(ZEPH, '|/usr/athena/bin/zwrite -d -n -c '. $zephyr_class .' -i '.
110        'sipbmp3@'.$host.' -s "SIPB LPR music spooler"');
111    print(ZEPH "Playback aborted.\n");
112    close(ZEPH);
113    die;
114}
115$SIG{HUP} = \&clear_status;
116
117# So, the file we're currently processing is "-d/-e".
118
119# Read the metadata information from the file.
120my ($filepath) = catfile($opts{'d'}, $opts{'e'});
121my ($fileinfo) = ImageInfo($filepath);
122my ($magic) = $fileinfo->{FileType};
123
124if ($magic) {
125    printf(ZEPHYR "%s file %s\n", $magic, $opts{'J'});
126    printf(STATUS "Filetype: %s\n", $magic);
127    printf(STATUS "Filename: %s\n", $opts{'J'});
128    if (exists $fileinfo->{'Title'}) {
129        printf(ZEPHYR "\@b{%s}\n", $fileinfo->{'Title'}) if exists $fileinfo->{'Title'};
130        printf(STATUS "Title: %s\n", $fileinfo->{'Title'});
131    }
132    foreach my $key (qw/Artist Album AlbumArtist/) {
133        if (exists $fileinfo->{$key}) {
134            printf(ZEPHYR "%s\n", $fileinfo->{$key}) if exists $fileinfo->{$key};
135            printf(STATUS "%s: %s\n", $key, $fileinfo->{$key});
136        }
137    }
138    my $tempdir = tempdir();
139    $opts{'J'} =~ s/_mp3/.mp3/; #awful hack -- geofft
140    my $newpath = $tempdir . '/' . basename($opts{'J'});
141    symlink($filepath, $newpath);
142    $filepath = $newpath;
143}
144elsif ($opts{'C'} eq 'Z') {
145    $filepath = resolve_external_reference($filepath, \%opts);
146    if ($filepath =~ m|http://www\.youtube\.com/watch\?v=|) {
147        $pid = open2($out, $in, qw{youtube-dl -g2}, $filepath);
148        $title = <$out>;
149        print ZEPHYR "YouTube video $filepath\n$title";
150        print STATUS "YouTube video $filepath\n$title";
151        $filepath = <$out>;
152        chomp $filepath;
153        waitpid $pid, 0;
154    } else {
155        print STDERR "Resolved external reference to $filepath\n";
156        printf(ZEPHYR "%s\n", $filepath);
157        printf(STATUS "External: %s\n", $filepath);
158    }
159}
160elsif (-T $filepath) {
161    split_playlist($filepath, \%opts);
162    close(ZEPHYR);
163    close(STATUS);
164    exit 0;
165}
166
167#printf(STDERR "Job priority %s\n", $opts{'C'}) if $opts{'C'} eq 'Z';
168#printf(ZEPHYR "Job priority %s\n", $opts{'C'}) if ($opts{'C'} && ($opts{'C'} ne 'A'));
169close(ZEPHYR);
170close(STATUS);
171play_mplayer_audio($filepath, \%opts);
172
173if ($magic) {
174    unlink($newpath);
175    rmdir($tempdir);
176}
177
178# Play an external stream reference
179sub resolve_external_reference {
180    # Retrieve those command line opts.
181    my ($filepath, $opts) = @_;
182
183    my $format, $uri, $userpass;
184
185    if (<STDIN> =~ /^(\S+)/) {
186        $uri=$1;
187
188        if ($uri =~ m|http://www\.youtube\.com/watch\?v=|) {
189            return $uri;
190        }
191
192        my $response = $ua->head($uri);
193       
194        $contenttype=($response->content_type() or "unknown");
195       
196        if ($contenttype eq "audio/mpeg") { $format="MP3" }
197        elsif ($contenttype eq "application/x-ogg") { $format="OGG" }
198        elsif ($contenttype eq "application/ogg") { $format="OGG" }
199        elsif ($contenttype eq "audio/x-scpls") { $format="SHOUTCAST" }
200        else {
201            print ZEPHYR
202                "Unknown Content-Type $contenttype for URI $uri\n";
203        }
204    } else {
205        print ZEPHYR "Couldn't read URI for external reference\n";
206        return $filepath;
207    }
208
209    if ($format eq "SHOUTCAST") {
210        print ZEPHYR "Shoutcast playlist...\n";
211        #Don't close ZEPHYR yet, will print the name of the stream if available
212        return &get_shoutcast($uri);
213    } elsif ($format eq "MP3") {
214    } elsif ($format eq "OGG") {
215    } else {
216      print ZEPHYR "Unrecognized stream format: $format\n";
217    }
218    return $uri;
219}
220
221sub split_playlist {
222    my ($file, $opts) = @_;
223
224    my $i = 0;
225   
226    while (<STDIN>) {
227        chomp;
228        if (/^([^#]\S+)/) {
229            printf (STDERR "Found line: %s\n", $_);
230            open(LPR, "|-", qw/mit-lpr -Psipbmp3@localhost -CZ/, '-J'.$opts->{J});
231            print LPR $1;
232            close(LPR);
233        $i++;
234        }
235    }
236    printf(ZEPHYR "Playlist containing %d valid entries, split into separate jobs.\n", $i);
237}
238
239# Process a Shoutcast playlist
240# get_shoutcast(URI)
241sub get_shoutcast {
242  my $uri = shift(@_);
243 
244  my $response = $ua->get($uri);
245
246  foreach (split("\n", $response->content())) {
247      if (/^File\d+=(\S+)/) {
248          push(@uris, $1);
249      }
250      if (/^Title\d+=(.+)$/) {
251          push(@titles, $1);
252      }
253  }
254 
255  # choose a random server
256  $server = int(rand scalar(@uris));
257  # print the name of the stream if available
258  print ZEPHYR "$titles[$server]\n";
259  return $uris[$server];
260}
261
262sub play_mplayer_audio {
263    my ($filepath, $opts) = @_;
264
265    # Prepare to write status:
266    open(ZEPHYR, '|/usr/athena/bin/zwrite -d -n -c '.$zephyr_class.' -i ' .
267         'sipbmp3@'.$host.' -s "SIPB LPR music spooler"');
268   
269    # fork for mpg123
270    my $pid = open(MP3STATUS, "-|");
271    unless (defined $pid) {
272        print ZEPHYR "Couldn't fork: $!\n";
273        close(ZEPHYR);
274        return;
275    }
276   
277    if ($pid) { #parent
278        # Check if there were any errors
279        if ($_ = <MP3STATUS>) {
280            print ZEPHYR "Playback completed with the following errors:\n";
281            print ZEPHYR $_;
282            while (<MP3STATUS>) {
283                print ZEPHYR $_;
284            }
285        } else {
286            print ZEPHYR "Playback completed successfully.\n";
287        }
288        close(MP3STATUS) || print ZEPHYR "mplayer exited $?\n";
289       
290        close(ZEPHYR);
291        open(STATUS, '>', '/var/run/sipbmp3/status');
292        close(STATUS);
293    }
294  else { # child
295      # redirect STDERR to STDOUT
296      open STDERR, '>&STDOUT';
297      # make sure that mplayer doesn't try to intepret the file as keyboard input
298      close(STDIN);
299      open(STDIN, "/dev/null");
300      #print STDERR Dumper([qw|/usr/bin/mplayer -nolirc -ao alsa -quiet|, $filepath]);
301      my @args = (qw|/usr/bin/mplayer -novideo -vo null -nolirc -ao alsa -cache 512 |, $filepath);
302      #print STDERR "About to exec: ", Dumper([@args]);
303      exec(@args) ||
304          die "Couldn't exec";
305  }
306}
307
308# ID3 comments often have useless crap because tools like iTunes were
309# written by drooling idiots
310sub filter_comment {
311  my $comment = shift(@_);
312
313  if ($comment =~ /^engiTunes_CDDB/) {
314    return undef;
315  }
316  return $comment;
317}
318
319
Note: See TracBrowser for help on using the repository browser.