Audio.pm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. # Execute the provided string with osascript
  20. sub runApplescript($) {
  21. my ($script) = @_;
  22. if ($DEBUG) {
  23. print STDERR 'AppleScript: ' . $script . "\n";
  24. }
  25. my $retval = IPC::System::Simple::capture('osascript', '-e', $script);
  26. $retval =~ s/[\r\n]$//;
  27. if ($DEBUG) {
  28. print STDERR "\tAppleScript result: " . $retval . "\n";
  29. }
  30. return $retval;
  31. }
  32. # Set or get the system output volume
  33. sub systemVolume($) {
  34. my ($new) = @_;
  35. if ($DEBUG) {
  36. print STDERR "Audio::systemVolume()\n";
  37. }
  38. if ($new) {
  39. runApplescript('set volume output volume ' . $new);
  40. } else {
  41. return runApplescript('get output volume of (get volume settings)');
  42. }
  43. }
  44. # Set or get a document's volume
  45. sub volume($$) {
  46. my ($name, $new) = @_;
  47. if ($DEBUG) {
  48. print STDERR 'Audio::volume(): ' . $name . "\n";
  49. }
  50. if (!loaded($name)) {
  51. die('Invalid QT document: ' . $name . "\n");
  52. }
  53. if ($new) {
  54. runApplescript('tell application "QuickTime Player" to set audio volume of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  55. } else {
  56. return runApplescript('tell application "QuickTime Player" to get audio volume of document ' . $FILES{$name}->{'name'});
  57. }
  58. }
  59. # Add an load a document
  60. sub addLoad($$) {
  61. my ($name, $path) = @_;
  62. if ($DEBUG) {
  63. print STDERR 'Audio::addLoad(): ' . $name . "\n";
  64. }
  65. add($name, $path);
  66. load($name);
  67. }
  68. # Add a document to the available list
  69. sub add($$) {
  70. my ($name, $path) = @_;
  71. if ($DEBUG) {
  72. print STDERR 'Audio::add(): ' . $name . ', ' . $path . "\n";
  73. }
  74. # Construct an absolute file path
  75. if (!($path =~ /^\//)) {
  76. $path = $MEDIA_PATH . '/' . $path;
  77. }
  78. # Validate
  79. if (!-r $path) {
  80. die('Invalid audio path: ' . $path . "\n");
  81. }
  82. # We only support one document per name
  83. drop($name);
  84. # Append the array
  85. my %tmp = ('path' => $path);
  86. $FILES{$name} = \%tmp;
  87. }
  88. # Drop a document from the available list, unloaded as necessary
  89. sub drop($) {
  90. my ($name) = @_;
  91. if ($DEBUG) {
  92. print STDERR 'Audio::drop(): ' . $name . "\n";
  93. }
  94. # Stop and unload as necessary
  95. if (loaded($name)) {
  96. stop($name);
  97. unload($name);
  98. }
  99. # Drop as necessary
  100. if (available($name)) {
  101. delete($FILES{$name});
  102. }
  103. }
  104. # Is the named document loaded?
  105. sub loaded($) {
  106. my ($name) = @_;
  107. if ($DEBUG) {
  108. print STDERR 'Audio::loaded(): ' . $name . "\n";
  109. }
  110. if (available($name) && defined($FILES{$name}->{'name'})) {
  111. return 1;
  112. }
  113. return 0;
  114. }
  115. # Is the named document configured?
  116. sub available($) {
  117. my ($name) = @_;
  118. if ($DEBUG) {
  119. print STDERR 'Audio::available(): ' . $name . "\n";
  120. }
  121. if (defined($name) && defined($FILES{$name})) {
  122. return 1;
  123. }
  124. return 0;
  125. }
  126. # Play the named document, waiting for completion
  127. sub play($) {
  128. my ($name) = @_;
  129. if ($DEBUG) {
  130. print STDERR 'Audio::play(): ' . $name . "\n";
  131. }
  132. # Start playback
  133. background($name);
  134. # Wait for it to complete
  135. my @cmd = ('tell application "QuickTime Player"');
  136. push(@cmd, 'repeat while playing of document ' . $FILES{$name}->{'name'} . ' = true');
  137. push(@cmd, 'delay 0.1');
  138. push(@cmd, 'end repeat');
  139. push(@cmd, 'end tell');
  140. runApplescript(join("\n", @cmd));
  141. }
  142. # Play the named document and immediately return
  143. sub background($) {
  144. my ($name) = @_;
  145. if ($DEBUG) {
  146. print STDERR 'Audio::background(): ' . $name . "\n";
  147. }
  148. if (!loaded($name)) {
  149. die('Invalid QT document: ' . $name . "\n");
  150. }
  151. # Start playback
  152. runApplescript('tell application "QuickTime Player" to play document ' . $FILES{$name}->{'name'});
  153. }
  154. # Is the named document playing?
  155. sub playing($) {
  156. my ($name) = @_;
  157. if ($DEBUG) {
  158. print STDERR 'Audio::playing(): ' . $name . "\n";
  159. }
  160. if (!loaded($name)) {
  161. die('Invalid QT document: ' . $name . "\n");
  162. }
  163. # Ask QT if the document is playing
  164. my $result = runApplescript('tell application "QuickTime Player" to get playing of document ' . $FILES{$name}->{'name'});
  165. if ($result =~ /true/i) {
  166. return 1;
  167. }
  168. return 0;
  169. }
  170. # Set or get the audio position
  171. sub position($$) {
  172. my ($name, $new) = @_;
  173. if ($DEBUG) {
  174. print STDERR 'Audio::position(): ' . $name . "\n";
  175. }
  176. if (!loaded($name)) {
  177. die('Invalid QT document: ' . $name . "\n");
  178. }
  179. if ($new) {
  180. runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  181. } else {
  182. return runApplescript('tell application "QuickTime Player" to get current time of document ' . $FILES{$name}->{'name'});
  183. }
  184. }
  185. # Set or get the playback rate
  186. sub rate($$) {
  187. my ($name, $new) = @_;
  188. if ($DEBUG) {
  189. print STDERR 'Audio::rate(): ' . $name . "\n";
  190. }
  191. if (!loaded($name)) {
  192. die('Invalid QT document: ' . $name . "\n");
  193. }
  194. if ($new) {
  195. runApplescript('tell application "QuickTime Player" to set rate of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  196. } else {
  197. return runApplescript('tell application "QuickTime Player" to get rate of document ' . $FILES{$name}->{'name'});
  198. }
  199. }
  200. # Pause the named document
  201. sub pause($) {
  202. my ($name) = @_;
  203. if ($DEBUG) {
  204. print STDERR 'Audio::pause(): ' . $name . "\n";
  205. }
  206. if (!loaded($name)) {
  207. die('Invalid QT document: ' . $name . "\n");
  208. }
  209. # Pause playback
  210. runApplescript('tell application "QuickTime Player" to pause document ' . $FILES{$name}->{'name'});
  211. }
  212. # Stop playback of the named document or all documents
  213. sub stop($) {
  214. my ($name) = @_;
  215. if ($DEBUG) {
  216. print STDERR "Audio::stopAudio()\n";
  217. }
  218. # Allow control of all documents or a specific document
  219. my $doc = 'every document';
  220. if (loaded($name)) {
  221. $doc = 'document ' . $FILES{$name}->{'name'};
  222. }
  223. # Stop and rewind all QT documents
  224. runApplescript('tell application "QuickTime Player" to stop ' . $doc);
  225. runApplescript('tell application "QuickTime Player" to set current time of ' . $doc . ' to 0');
  226. # Bring Plex back to the front
  227. runApplescript('tell application "Plex" to activate');
  228. }
  229. # Unload the named document
  230. sub unload($) {
  231. my ($name) = @_;
  232. if ($DEBUG) {
  233. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  234. }
  235. if (!available($name)) {
  236. die('Invalid QT document: ' . $name . "\n");
  237. }
  238. # Nothing to do if the file isn't loaded
  239. if (!loaded($name)) {
  240. return;
  241. }
  242. # Close to document
  243. runApplescript('tell application "QuickTime Player" to close document ' . $FILES{$name}->{'name'});
  244. # Delete our local handle
  245. delete($FILES{$name}->{'name'});
  246. }
  247. # Load the named document
  248. sub load($) {
  249. my ($name) = @_;
  250. if ($DEBUG) {
  251. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  252. }
  253. if (!available($name)) {
  254. die('Invalid QT document: ' . $name . "\n");
  255. }
  256. # Unload first, if we're already loaded
  257. if (loaded($name)) {
  258. unload($name);
  259. }
  260. # Find out how many files are already open
  261. my $count = runApplescript('tell application "QuickTime Player" to count items of every document');
  262. # Open the file
  263. IPC::System::Simple::system('open', '-a', 'QuickTime Player', $FILES{$name}->{'path'});
  264. # Wait for QT to load the new file
  265. my @cmd = ('tell application "QuickTime Player"');
  266. push(@cmd, 'set endDate to current date + (3/60 * minutes)');
  267. push(@cmd, 'repeat while (count items of every document) <= ' . $count . ' and current date < endDate');
  268. push(@cmd, 'delay 0.1');
  269. push(@cmd, 'end repeat');
  270. push(@cmd, 'get document 1');
  271. push(@cmd, 'end tell');
  272. my $doc = runApplescript(join("\n", @cmd));
  273. # Clean up the document name
  274. $doc =~ s/^\s*document //;
  275. $doc =~ s/\s+$//;
  276. $doc =~ s/\"/\\\"/g;
  277. $doc = '"' . $doc . '"';
  278. # Save the document handle
  279. $FILES{$name}->{'name'} = $doc;
  280. # Bring Plex back to the front
  281. runApplescript('tell application "Plex" to activate');
  282. }
  283. # Get things into a reasonable state
  284. sub init() {
  285. if ($DEBUG) {
  286. print STDERR "Audio::init()\n";
  287. }
  288. # Close all QT documents and relaunch QT Player
  289. runApplescript('tell application "QuickTime Player" to close every document');
  290. runApplescript('tell application "QuickTime Player" to quit');
  291. sleep(1);
  292. IPC::System::Simple::system('open', '-a', 'QuickTime Player');
  293. # Always load and play the SILENCE file (for sanity and general init)
  294. add('SILENCE', 'DMX/Silence.wav');
  295. load('SILENCE');
  296. play('SILENCE');
  297. }
  298. # Always return true
  299. 1;