Audio.pm 6.5 KB

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