(* QLogger: see Description tab or theExplanation variable
v0.9: 22/03/18 Rich "STOP logger"
v0.9.5: 17/08/18 Rich Added mdls query to capture further information about stopped files
v1.0: 23/09/18 Ben "QLogger" v1 FMP
v1.1: 10/11/21 Ben Standalone script, continue on null / error
v1.2: 29/06/22 Rich General tidying & retesting
-- ###FIXME### This has not been tested much beyond proving that it works in principal; there is no real error protection
-- ###FIXME### Notifications don't work well when multiple cues stop at the same time
-- ###FIXME### Action elapsed is affected by scrubbing, so is not a true reflection in general of how long a cue _played_ for
-- ###FIXME### v1.2: Can take a long time to actually quit after announcing that intention; hanging occasionally too?
-- ###TODO### mdls output seems to be alphabetical; find a way to control metadata order
<<< Last run with QLab 4.6.12, macOS 12.4 >>> *)
(*
/// HOW DOES IT WORK? ///
-- Essentially, this script repeatedly polls QLab for a list of running Audio Cues and then compares that list with the last result
-- Any cues no longer in the list must have stopped so the script writes information about them to a text file on the Desktop
-- A command line tool ("mdls") gathers metadata from the audio files that QLab can't itself report
*)
-- Explanations
property theExplanation : "Run this app alongside QLab to write a text file to the Desktop of how long Audio Cues had played for when they stop" & ¬
"(rounded down to the nearest second)
TO QUIT: close all workspaces (the app won't run if there aren't any)"
-- User-defined variables
set userBypassExplanation to false -- Set this to true if you don't want to see the explanation dialog each time you run the application
set userLogFilenameSuffix to " | QLogger Logs" -- Text to append to log file names
set userFileTimestampFormat to "+%d/%m/%y %H:%M:%S" -- Set the format for timestamps;
(* the structure of this string follows the conversion specifiers of strftime: run "man strftime" in Terminal to see more *)
set userNotificationsOn to true -- Set this to false if you don't want notifications
set userPollingInterval to 1 -- Set how many seconds between iterations (accuracy will be roughly ±userPollingInterval)
-- Declarations
property dialogTitle : "QLogger"
global currentTIDs
-- Preamble
set currentTIDs to AppleScript's text item delimiters
if userBypassExplanation is false then
display dialog theExplanation with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
end if
-- Check QLab is running
tell application "System Events"
set qLabIsRunning to exists (processes whose bundle identifier is "com.figure53.QLab.4")
end tell
if qLabIsRunning is false then
my exitStrategy("Quitting: QLab is not running.", 5)
return
end if
-- Test for a workspace
tell application id "com.figure53.QLab.4"
try
set workspaceName to q number of front workspace
on error
set workspaceName to false
end try
end tell
if workspaceName is false then
my exitStrategy("Quitting: no workspaces open in QLab.", 5)
return
end if
-- Get setup details
tell application id "com.figure53.QLab.4" to activate
set theFilePath to "" & (POSIX path of (path to desktop)) & "QLab | " & workspaceName & userLogFilenameSuffix & ".txt"
set capturedAudioCueIDs to {}
set capturedAudioCueElapses to {}
set AppleScript's text item delimiters to tab
-- Run the loop
repeat
-- Get the latest news
try
tell application id "com.figure53.QLab.4" to tell front workspace
set latestAudioCueIDs to uniqueID of cues whose running is true and q type is "Audio"
set latestAudioCueElapses to action elapsed of cues whose running is true and q type is "Audio"
end tell
on error
activate
my exitStrategy("Quitting: no workspaces open in QLab.", 5)
return -- Quit if no workspace open
end try
-- Compare against the last capture and log accordingly
if latestAudioCueIDs ≠ capturedAudioCueIDs then
set logTime to do shell script "date " & quoted form of userFileTimestampFormat
repeat with i from 1 to count capturedAudioCueIDs
if item i of capturedAudioCueIDs is not in latestAudioCueIDs then
set elapsedTime to makeMSS(item i of capturedAudioCueElapses)
tell application id "com.figure53.QLab.4" to tell front workspace
set stoppedCue to q list name of cue id (item i of capturedAudioCueIDs)
set stoppedFile to file target of cue id (item i of capturedAudioCueIDs)
end tell
try
set fileInfo to paragraphs of ¬
(do shell script "mdls -name kMDItemTitle -name kMDItemAlbum " & ¬
"-name kMDItemAudioTrackNumber -name kMDItemComposer -name kMDItemCopyright " & ¬
quoted form of POSIX path of stoppedFile & " | awk '{n=index($0,\"=\");print substr($0,n+2)}'")
(* ###FIXME### output is NOT in this order!!!! *)
set cleanInfo to {} -- Remove surrounding quotes from mdls output
repeat with eachItem in fileInfo
set end of cleanInfo to my stripSurroundingQuotes(contents of eachItem)
end repeat
on error
set cleanInfo to {}
repeat 5 times -- Change this to match the number of kMDItems above
set end of cleanInfo to "(null)" -- This matches the string returned by mdls for missing metadata
end repeat
end try
set theMessage to ({logTime, stoppedCue, elapsedTime} & cleanInfo & POSIX path of stoppedFile) as text
do shell script "echo " & quoted form of theMessage & " >> " & quoted form of theFilePath
if userNotificationsOn then display notification stoppedCue & " stopped at " & elapsedTime with title dialogTitle subtitle "Event logged"
end if
end repeat
end if
-- Update the cache
set capturedAudioCueIDs to latestAudioCueIDs
set capturedAudioCueElapses to latestAudioCueElapses
-- Wait…
delay userPollingInterval
end repeat
-- Subroutines
(* === OUTPUT === *)
on exitStrategy(theMessage, givingUp) -- [Shared subroutine]
if theMessage is not false then
if givingUp is not false then
display dialog theMessage with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after givingUp
else
display dialog theMessage with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
end if
end if
set AppleScript's text item delimiters to currentTIDs
end exitStrategy
(* === TIME === *)
on makeMSS(howLong) -- [Shared subroutine]
set howManyMinutes to howLong div 60
set howManySeconds to howLong mod 60 div 1
return (howManyMinutes as text) & ":" & my padNumber(howManySeconds, 2)
end makeMSS
(* === TEXT WRANGLING === *)
on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
set paddedNumber to theNumber as text
repeat while (count paddedNumber) < minimumDigits
set paddedNumber to "0" & paddedNumber
end repeat
return paddedNumber
end padNumber
on stripSurroundingQuotes(theText)
set quoteMark to "\""
if first character of theText is quoteMark and last character of theText is quoteMark then
return text 2 thru -2 of theText
else
return theText
end if
end stripSurroundingQuotes
(* END: QLogger *)