| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- #!/usr/bin/perl
- use strict;
- use warnings;
- use File::Basename;
- use IPC::System::Simple;
- use Time::Out qw( timeout );
- # Local modules
- use Cwd qw(abs_path);
- use File::Basename qw(dirname);
- use lib dirname(abs_path($0));
- use DMX;
- use Audio;
- # User config
- my $MEDIA_PATH = `~/bin/video/mediaPath`;
- # App config
- my $DATA_DIR = DMX::dataDir();
- my $STATE_SOCK = 'QT_PLAYER';
- my $OUTPUT_FILE = $DATA_DIR . $STATE_SOCK;
- my $DELAY = 5;
- my $MAX_CMD_LEN = 16384;
- my $TIMEOUT_OPEN = 10;
- # Globals
- my %FILES = ();
- # Prototypes
- sub init();
- sub load($$);
- sub unload($);
- sub stop();
- sub printFiles();
- sub cmdApp($$);
- sub openQT($);
- # Debug
- my $DEBUG = 0;
- if ($ENV{'DEBUG'}) {
- $DEBUG = 1;
- }
- # Ensure the media is ready before we do anything else
- if (!$MEDIA_PATH || !-d $MEDIA_PATH) {
- sleep($TIMEOUT_OPEN);
- exit(0);
- }
- # Clear our output file
- if (-e $OUTPUT_FILE) {
- unlink($OUTPUT_FILE);
- }
- # Reset QT Player
- init();
- # Sockets
- my $SELECT = DMX::selectSock($STATE_SOCK);
- # Loop forever
- while (1) {
- # Wait for commands
- {
- my @ready_clients = $SELECT->can_read($DELAY);
- foreach my $fh (@ready_clients) {
- # Ensure we won't block on recv()
- $fh->blocking(0);
- # Read
- while (defined($fh->recv(my $text, $MAX_CMD_LEN))) {
- # Parse
- my @argv = split(/\|/, $text);
- # Ignore empty commands
- if (scalar(@argv) < 1) {
- next;
- }
- # Dispatch commands
- my $cmd = shift(@argv);
- if ($cmd eq 'LOAD' && scalar(@argv) == 2) {
- load($argv[0], $argv[1]);
- } elsif ($cmd eq 'UNLOAD' && scalar(@argv) == 1) {
- unload($argv[0]);
- } elsif ($cmd eq 'STOP') {
- stop();
- } elsif ($cmd eq 'RESET') {
- init();
- } else {
- warn('Ignored invalid command: ' . $text . "\n");
- }
- }
- }
- }
- # Keep our list of files in-sync with reality
- foreach my $name (keys(%FILES)) {
- eval { Audio::runApplescript('tell application "QuickTime Player" to get document ' . $FILES{$name}); };
- if ($@) {
- warn('File went missing: ' . $name . "\n");
- delete($FILES{$name});
- printFiles();
- }
- }
- # Ensure the output file always exists
- if (!-r $OUTPUT_FILE) {
- printFiles();
- }
- # Ensure "silence" is always available -- reset if it goes away
- if (!exists($FILES{'SILENCE'})) {
- die("We need only the SILENCE\n");
- }
- }
- # Get things into a reasonable state
- sub init() {
- if ($DEBUG) {
- print STDERR "init()\n";
- }
- # Reset our local state
- %FILES = ();
- # Close all QT documents and relaunch QT Player
- cmdApp('QuickTime Player', 'close every document');
- cmdApp('QuickTime Player', 'quit');
- sleep(1);
- openQT(undef());
- # Always load and play the SILENCE file (for sanity and general init)
- load('SILENCE', 'DMX/Silence.wav');
- # Bring OpenPHT back to the front
- cmdApp('OpenPHT', 'activate');
- }
- # Open the named file and record the handle
- sub load($$) {
- my ($name, $path) = @_;
- if ($DEBUG) {
- print STDERR 'load(): ' . $name . ', ' . $path . "\n";
- }
- # Build a reasonable path
- if (!($path =~ /^\//)) {
- $path = $MEDIA_PATH . '/' . $path;
- }
- # Ensure the file exists
- if (!-r $path) {
- warn('Invalid audio file path: ' . $path . "\n");
- return;
- }
- # Ignore duplicate loads
- if (exists($FILES{$name})) {
- warn('Ignoring load request for existing file: ' . $name . "\n");
- return;
- }
- # Find out how many files are already open
- my $count = Audio::runApplescript('tell application "QuickTime Player" to count items of every document');
- # Open the file
- openQT($path);
- # Wait for QT to load the new file
- # To avoid indefinate hangs this will give up after 5 seconds
- my @cmd = ('tell application "QuickTime Player"');
- push(@cmd, 'set endDate to current date + (5/60 * minutes)');
- push(@cmd, 'repeat while (count items of every document) <= ' . $count . ' and current date < endDate');
- push(@cmd, 'delay 0.1');
- push(@cmd, 'end repeat');
- push(@cmd, 'if (count items of every document) > ' . $count . ' then');
- push(@cmd, 'get document 1');
- push(@cmd, 'end if');
- push(@cmd, 'end tell');
- my $doc = Audio::runApplescript(join("\n", @cmd));
- # Reset if we cannot open -- something is out-of-sync
- if (!$doc) {
- init();
- return;
- }
- # Clean up the document name
- $doc =~ s/^\s*document //;
- $doc =~ s/\s+$//;
- $doc =~ s/\"/\\\"/g;
- $doc = '"' . $doc . '"';
- # Save the document handle
- $FILES{$name} = $doc;
- printFiles();
- # Bring OpenPHT back to the front
- cmdApp('OpenPHT', 'activate');
- }
- # Close the named file
- sub unload($) {
- my ($name) = @_;
- if ($DEBUG) {
- print STDERR 'unload(): ' . $name . "\n";
- }
- # Ignore files that don't exist
- if (!exists($FILES{$name})) {
- warn('No such file loaded: ' . $name . "\n");
- return;
- }
- # Close to document
- Audio::runApplescript('tell application "QuickTime Player" to close document ' . $FILES{$name});
- # Delete our handle
- delete($FILES{$name});
- printFiles();
- }
- # Stop and rewind all QT documents
- sub stop() {
- Audio::runApplescript('tell application "QuickTime Player" to stop every document');
- Audio::runApplescript('tell application "QuickTime Player" to set current time of every document to 0');
- }
- # Update the output file list
- sub printFiles() {
- my ($fh, $tmp) = File::Temp::tempfile($OUTPUT_FILE . '.XXXXXXXX', 'UNLINK' => 0);
- foreach my $file (keys(%FILES)) {
- print $fh $file . '|' . $FILES{$file} . "\n";
- }
- close($fh);
- rename($tmp, $OUTPUT_FILE);
- }
- # Request the specified action from the provided app, if it's already running
- sub cmdApp($$) {
- my ($app, $cmd) = @_;
- # Ask AppleScript to execute the provided command only if the specified app is already running
- my @cmd = ('tell application "System Events"');
- push(@cmd, 'if exists process "' . $app . '" then');
- push(@cmd, 'tell application "' . $app . '" to ' . $cmd);
- push(@cmd, 'end if');
- push(@cmd, 'end tell');
- # Execute
- Audio::runApplescript(join("\n", @cmd));
- }
- # Activate QT player, optionally loading a document
- # It would be nice to use AS here to get a more definitive document name, but the open() call is QT player is borked
- sub openQT($) {
- my ($doc) = @_;
- # Build the open command
- my @cmd = ('open', '-a', 'QuickTime Player');
- if ($doc) {
- push(@cmd, $doc);
- }
- # Execute, dying on timeout
- timeout $TIMEOUT_OPEN => sub {
- IPC::System::Simple::system(@cmd);
- };
- if ($@) {
- die("Open command timed out\n");
- }
- }
|