Audio.pm 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use POSIX;
  5. use File::Touch;
  6. use File::Basename;
  7. use Time::HiRes qw( usleep sleep time );
  8. use IPC::System::Simple;
  9. # Package name
  10. package Audio;
  11. # Config
  12. my $MEDIA_PATH = `~/bin/video/mediaPath`;
  13. my %FILES = ();
  14. # Debug
  15. my $DEBUG = 0;
  16. if ($ENV{'DEBUG'}) {
  17. $DEBUG = 1;
  18. }
  19. sub runApplescript($) {
  20. my ($script) = @_;
  21. if ($DEBUG) {
  22. print STDERR 'AppleScript: ' . $script . "\n";
  23. }
  24. my $retval = IPC::System::Simple::capture('osascript', '-e', $script);
  25. $retval =~ s/[\r\n]$//;
  26. if ($DEBUG) {
  27. print STDERR "\tAppleScript result: " . $retval . "\n";
  28. }
  29. return $retval;
  30. }
  31. sub addLoad($$) {
  32. my ($name, $path) = @_;
  33. if ($DEBUG) {
  34. print STDERR 'Audio::addLoad(): ' . $name . "\n";
  35. }
  36. add($name, $path);
  37. load($name);
  38. }
  39. sub add($$) {
  40. my ($name, $path) = @_;
  41. if ($DEBUG) {
  42. print STDERR 'Audio::add(): ' . $name . "\n";
  43. }
  44. # Construct an absolute file path
  45. if (!($path =~ /^\//)) {
  46. $path = $MEDIA_PATH . '/' . $path;
  47. }
  48. # Validate
  49. if (!-r $path) {
  50. die('Invalid audio path: ' . $path . "\n");
  51. }
  52. # We only support one document per name
  53. drop($name);
  54. # Append the array
  55. my %tmp = ('path' => $path);
  56. $FILES{$name} = \%tmp;
  57. }
  58. sub drop($) {
  59. my ($name) = @_;
  60. if ($DEBUG) {
  61. print STDERR 'Audio::drop(): ' . $name . "\n";
  62. }
  63. # Stop and unload as necessary
  64. if (loaded($name)) {
  65. stop($name);
  66. unload($name);
  67. }
  68. # Drop as necessary
  69. if (available($name)) {
  70. delete($FILES{$name});
  71. }
  72. }
  73. sub loaded($) {
  74. my ($name) = @_;
  75. if ($DEBUG) {
  76. print STDERR 'Audio::loaded(): ' . $name . "\n";
  77. }
  78. if (available($name) && defined($FILES{$name}->{'name'})) {
  79. return 1;
  80. }
  81. return 0;
  82. }
  83. sub available($) {
  84. my ($name) = @_;
  85. if ($DEBUG) {
  86. print STDERR 'Audio::available(): ' . $name . "\n";
  87. }
  88. if (defined($name) && defined($FILES{$name})) {
  89. return 1;
  90. }
  91. return 0;
  92. }
  93. sub play($) {
  94. my ($name) = @_;
  95. if ($DEBUG) {
  96. print STDERR 'Audio::play(): ' . $name . "\n";
  97. }
  98. # Start playback
  99. background($name);
  100. # Wait for it to complete
  101. my @cmd = ('tell application "QuickTime Player"');
  102. push(@cmd, 'repeat while playing of document ' . $FILES{$name}->{'name'} . ' = true');
  103. push(@cmd, 'delay 0.1');
  104. push(@cmd, 'end repeat');
  105. push(@cmd, 'end tell');
  106. runApplescript(join("\n", @cmd));
  107. }
  108. sub background($) {
  109. my ($name) = @_;
  110. if ($DEBUG) {
  111. print STDERR 'Audio::background(): ' . $name . "\n";
  112. }
  113. if (!loaded($name)) {
  114. die('Invalid QT document: ' . $name . "\n");
  115. }
  116. # Start playback
  117. runApplescript('tell application "QuickTime Player" to play document ' . $FILES{$name}->{'name'});
  118. }
  119. sub playing($) {
  120. my ($name) = @_;
  121. if ($DEBUG) {
  122. print STDERR 'Audio::playing(): ' . $name . "\n";
  123. }
  124. if (!loaded($name)) {
  125. die('Invalid QT document: ' . $name . "\n");
  126. }
  127. # Ask QT if the document is playing
  128. my $result = runApplescript('tell application "QuickTime Player" to get playing of document ' . $FILES{$name}->{'name'});
  129. if ($result =~ /true/i) {
  130. return 1;
  131. }
  132. return 0;
  133. }
  134. sub position($$) {
  135. my ($name, $new) = @_;
  136. if ($DEBUG) {
  137. print STDERR 'Audio::position(): ' . $name . "\n";
  138. }
  139. if (!loaded($name)) {
  140. die('Invalid QT document: ' . $name . "\n");
  141. }
  142. # Optionally set the audio position
  143. if ($new) {
  144. runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  145. }
  146. # Get the current playback position from QT
  147. return runApplescript('tell application "QuickTime Player" to get current time of document ' . $FILES{$name}->{'name'});
  148. }
  149. sub rate($$) {
  150. my ($name, $new) = @_;
  151. if ($DEBUG) {
  152. print STDERR 'Audio::rate(): ' . $name . "\n";
  153. }
  154. if (!loaded($name)) {
  155. die('Invalid QT document: ' . $name . "\n");
  156. }
  157. # Optionally set the playback rate
  158. if ($new) {
  159. runApplescript('tell application "QuickTime Player" to set rate of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  160. }
  161. # Get the current playback position from QT
  162. return runApplescript('tell application "QuickTime Player" to get rate of document ' . $FILES{$name}->{'name'});
  163. }
  164. sub pause($) {
  165. my ($name) = @_;
  166. if ($DEBUG) {
  167. print STDERR 'Audio::pause(): ' . $name . "\n";
  168. }
  169. if (!loaded($name)) {
  170. die('Invalid QT document: ' . $name . "\n");
  171. }
  172. # Pause playback
  173. runApplescript('tell application "QuickTime Player" to pause document ' . $FILES{$name}->{'name'});
  174. }
  175. sub stop($) {
  176. my ($name) = @_;
  177. if ($DEBUG) {
  178. print STDERR "Audio::stopAudio()\n";
  179. }
  180. # Allow control of all documents or a specific document
  181. my $doc = 'every document';
  182. if (!loaded($name)) {
  183. $doc = 'document ' . $FILES{$name}->{'name'};
  184. }
  185. # Stop and rewind all QT documents
  186. runApplescript('tell application "QuickTime Player" to stop ' . $doc);
  187. runApplescript('tell application "QuickTime Player" to set current time of ' . $doc . ' to 0');
  188. # Bring Plex back to the front
  189. runApplescript('tell application "Plex" to activate');
  190. }
  191. sub unload($) {
  192. my ($name) = @_;
  193. if ($DEBUG) {
  194. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  195. }
  196. if (!available($name)) {
  197. die('Invalid QT document: ' . $name . "\n");
  198. }
  199. # Nothing to do if the file isn't loaded
  200. if (!loaded($name)) {
  201. return;
  202. }
  203. # Close to document
  204. runApplescript('tell application "QuickTime Player" to close document ' . $FILES{$name}->{'name'});
  205. # Delete our local handle
  206. delete($FILES{$name}->{'name'});
  207. }
  208. sub load($) {
  209. my ($name) = @_;
  210. if ($DEBUG) {
  211. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  212. }
  213. if (!available($name)) {
  214. die('Invalid QT document: ' . $name . "\n");
  215. }
  216. # Unload first, if we're already loaded
  217. if (loaded($name)) {
  218. unload($name);
  219. }
  220. # Find out how many files are already open
  221. my $count = runApplescript('tell application "QuickTime Player" to count items of every document');
  222. # Open the file
  223. IPC::System::Simple::system('open', '-a', 'QuickTime Player', $FILES{$name}->{'path'});
  224. # Wait for QT to load the new file
  225. my @cmd = ('tell application "QuickTime Player"');
  226. push(@cmd, 'set endDate to current date + (3/60 * minutes)');
  227. push(@cmd, 'repeat while (count items of every document) <= ' . $count . ' and current date < endDate');
  228. push(@cmd, 'delay 0.1');
  229. push(@cmd, 'end repeat');
  230. push(@cmd, 'get document 1');
  231. push(@cmd, 'end tell');
  232. my $doc = runApplescript(join("\n", @cmd));
  233. # Clean up the document name
  234. $doc =~ s/^\s*document //;
  235. $doc =~ s/\s+$//;
  236. $doc =~ s/\"/\\\"/g;
  237. $doc = '"' . $doc . '"';
  238. # Save the document handle
  239. $FILES{$name}->{'name'} = $doc;
  240. # Bring Plex back to the front
  241. runApplescript('tell application "Plex" to activate');
  242. }
  243. sub init() {
  244. if ($DEBUG) {
  245. print STDERR "Audio::init()\n";
  246. }
  247. # Close all QT documents and relaunch QT Player
  248. runApplescript('tell application "QuickTime Player" to close every document');
  249. runApplescript('tell application "QuickTime Player" to quit');
  250. sleep(1);
  251. IPC::System::Simple::system('open', '-a', 'QuickTime Player');
  252. # Always load and play the SILENCE file (for sanity and general init)
  253. add('SILENCE', 'DMX/Silence.wav');
  254. load('SILENCE');
  255. play('SILENCE');
  256. }
  257. # Always return true
  258. 1;