Audio.pm 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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. # Ask QT if the document is playing
  161. # Note that while QT has a "playing" property it is only true when rate == 1.0
  162. my $rate = rate($name);
  163. if ($rate > 0) {
  164. return 1;
  165. }
  166. return 0;
  167. }
  168. # Set or get the audio position
  169. sub position($$) {
  170. my ($name, $new) = @_;
  171. if ($DEBUG) {
  172. print STDERR 'Audio::position(): ' . $name . "\n";
  173. }
  174. if (!loaded($name)) {
  175. die('Invalid QT document: ' . $name . "\n");
  176. }
  177. if ($new) {
  178. runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  179. } else {
  180. return runApplescript('tell application "QuickTime Player" to get current time of document ' . $FILES{$name}->{'name'});
  181. }
  182. }
  183. # Set or get the playback rate
  184. sub rate($$) {
  185. my ($name, $new) = @_;
  186. if ($DEBUG) {
  187. print STDERR 'Audio::rate(): ' . $name . "\n";
  188. }
  189. if (!loaded($name)) {
  190. die('Invalid QT document: ' . $name . "\n");
  191. }
  192. if ($new) {
  193. runApplescript('tell application "QuickTime Player" to set rate of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
  194. } else {
  195. return runApplescript('tell application "QuickTime Player" to get rate of document ' . $FILES{$name}->{'name'});
  196. }
  197. }
  198. # Pause the named document
  199. sub pause($) {
  200. my ($name) = @_;
  201. if ($DEBUG) {
  202. print STDERR 'Audio::pause(): ' . $name . "\n";
  203. }
  204. if (!loaded($name)) {
  205. die('Invalid QT document: ' . $name . "\n");
  206. }
  207. # Pause playback
  208. runApplescript('tell application "QuickTime Player" to pause document ' . $FILES{$name}->{'name'});
  209. }
  210. # Stop playback of the named document or all documents
  211. sub stop($) {
  212. my ($name) = @_;
  213. if ($DEBUG) {
  214. print STDERR "Audio::stopAudio()\n";
  215. }
  216. # Allow control of all documents or a specific document
  217. my $doc = 'every document';
  218. if (loaded($name)) {
  219. $doc = 'document ' . $FILES{$name}->{'name'};
  220. }
  221. # Stop and rewind all QT documents
  222. runApplescript('tell application "QuickTime Player" to stop ' . $doc);
  223. runApplescript('tell application "QuickTime Player" to set current time of ' . $doc . ' to 0');
  224. # Bring Plex back to the front
  225. runApplescript('tell application "Plex" to activate');
  226. }
  227. # Unload the named document
  228. sub unload($) {
  229. my ($name) = @_;
  230. if ($DEBUG) {
  231. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  232. }
  233. if (!available($name)) {
  234. die('Invalid QT document: ' . $name . "\n");
  235. }
  236. # Nothing to do if the file isn't loaded
  237. if (!loaded($name)) {
  238. return;
  239. }
  240. # Close to document
  241. runApplescript('tell application "QuickTime Player" to close document ' . $FILES{$name}->{'name'});
  242. # Delete our local handle
  243. delete($FILES{$name}->{'name'});
  244. }
  245. # Load the named document
  246. sub load($) {
  247. my ($name) = @_;
  248. if ($DEBUG) {
  249. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  250. }
  251. if (!available($name)) {
  252. die('Invalid QT document: ' . $name . "\n");
  253. }
  254. # Unload first, if we're already loaded
  255. if (loaded($name)) {
  256. unload($name);
  257. }
  258. # Find out how many files are already open
  259. my $count = runApplescript('tell application "QuickTime Player" to count items of every document');
  260. # Open the file
  261. IPC::System::Simple::system('open', '-a', 'QuickTime Player', $FILES{$name}->{'path'});
  262. # Wait for QT to load the new file
  263. my @cmd = ('tell application "QuickTime Player"');
  264. push(@cmd, 'set endDate to current date + (3/60 * minutes)');
  265. push(@cmd, 'repeat while (count items of every document) <= ' . $count . ' and current date < endDate');
  266. push(@cmd, 'delay 0.1');
  267. push(@cmd, 'end repeat');
  268. push(@cmd, 'get document 1');
  269. push(@cmd, 'end tell');
  270. my $doc = runApplescript(join("\n", @cmd));
  271. # Clean up the document name
  272. $doc =~ s/^\s*document //;
  273. $doc =~ s/\s+$//;
  274. $doc =~ s/\"/\\\"/g;
  275. $doc = '"' . $doc . '"';
  276. # Save the document handle
  277. $FILES{$name}->{'name'} = $doc;
  278. # Bring Plex back to the front
  279. runApplescript('tell application "Plex" to activate');
  280. }
  281. # Get things into a reasonable state
  282. sub init() {
  283. if ($DEBUG) {
  284. print STDERR "Audio::init()\n";
  285. }
  286. # Close all QT documents and relaunch QT Player
  287. runApplescript('tell application "QuickTime Player" to close every document');
  288. runApplescript('tell application "QuickTime Player" to quit');
  289. sleep(1);
  290. IPC::System::Simple::system('open', '-a', 'QuickTime Player');
  291. # Always load and play the SILENCE file (for sanity and general init)
  292. add('SILENCE', 'DMX/Silence.wav');
  293. load('SILENCE');
  294. play('SILENCE');
  295. }
  296. # Always return true
  297. 1;