Captain Slog

This is a simple workspace which logs times into a text file on the Desktop called “SHOW LOGGER”. You can copy and paste the example cues into your own workspaces.

How it works

The workspace uses four scripts which only vary in the words they add to the log. Shown below is the intermission script. The script is adapted from one found on http://www.macosxautomation.com/.

set themessage to "Intermission"
set this_data to ((current date) as string) & space & themessage & return
set target_file to (((path to desktop folder) as string) & "SHOW LOGGER")
set append_data to true

try
  set the target_file to the target_file as string
  set the open_target_file to open for access file target_file with write permission
  if append_data is false then set eof of the open_target_file to 0
  write this_data to the open_target_file starting at eof
  close access the open_target_file
  return true
  on error
    try
      close access file target_file
    end try
  return false
end try

Logging with calculations

We can build on the above workspace to add calculated running times to the log.

You could try to log running times by using Script cues to start timers at the beginning of each act and stop them at the end, but this solution would fail if the workspace were ever panicked during the act; not an altogether unlikely situation.

A more resilient solution is to record the epoch time at each significant moment (top of each act, end of each act, etc.), and refer back to these recorded times when we’re done logging.

On Unix systems, of which macOS is one variety, the Epoch time is defined as the number of seconds since midnight at the start of January 1, 1970. While this is quite esoteric, it turns out to be a pretty convenient way to keep track of time.

We’re going to record times into the Notes field of various cues, so we’ll need to have a numbering convention, e.g. LOG1, LOG2, etc., so that we can retrieve each value when it’s time to do the calculations.

When we want to calculate the total running time, or the running time of any section of the show, we’ll simply subtract the time stored in the log cue at the beginning of the section from the time stored in the log cue at the end of the section. For example, if we put cue LOG1 at the top of act one, and LOG2 at the end of act one, then

((notes **of** _cue_ "LOG2") - (notes **of** _cue_ "LOG1"))

will give us the duration of act one measured in seconds. Following that logic,

((notes **of** _cue_ "LOG2") - (notes **of** _cue_ "LOG1")) + ((notes **of** _cue_ "LOG4") - (notes **of** _cue_ "LOG3"))

will give us the running time of a two-act play without intermission.

All we need to do then is convert from epoch seconds to a HH:MM:SS string.

Here’s the workspace:

Logging workspace

And here’s the log output from that workspace:

Show log

Let’s break that down, script by script.

Show file name

The show file name is taken from the actual file name of the QLab workspace.

using terms from application "Finder"
  set themessage to "-----------------------------------------------------------" & return & "SHOW FILE: " & name of front document of application id "com.figure53.qlab.3" as string
end using terms from

Then the data is written to the logging file:

set this_data to themessage & return & return
set target_file to (((path to desktop folder) as string) & "SHOW LOGGER")
set append_data to true
try
  set the target_file to the target_file as string
  set the open_target_file to open for access file target_file with write permission
  if append_data is false then set eof of the open_target_file to 0
  write this_data to the open_target_file starting at eof
  close access the open_target_file
  return true
on error
  try
    close access file target_file
  end try
  return false
end try

Logging cues

The logging cue scripts are all identical with the exception of the text which they log and cues which they reference. Here’s the script which logs the curtain up time:

set themessage to "Curtain Up"
set thedate to current date
set this_data to (thedate as string) & space & themessage & return
set target_file to (((path to desktop folder) as string) & "SHOW LOGGER")
set append_data to true
try
  set the target_file to the target_file as string
  set the open_target_file to open for access file target_file with write permission
  if append_data is false then set eof of the open_target_file to 0
  write this_data to the open_target_file starting at eof
  close access the open_target_file
  tell application id "com.figure53.qlab.3" to tell front workspace
    set notes of cue "LOG1" to (do shell script "date +%s") as integer
  end tell
  return true
on error
  try
    close access file target_file
  end try
  return false
end try

In the above example, themessage is set to “Curtain up”. The time as a string is appended to this and the whole string is written to the log file. Then the epoch seconds are obtained by running a shell script:

set notes of cue "LOG1" to (do shell script "date +%s") as integer

Which gets the epoch seconds and writes them to the Notes field in cue LOG1.

Adding more logging cues

If we want to modify this script to add a log when the house opens, we first copy and paste cue LOG1 to create a new log cue. We’ll number the new cue LOG0. Then, we change the first line of the script to:

set themessage to "House Open"

and the shell script line to:

set notes of cue "LOG0" to (do shell script "date +%s") as integer

Reporting cues

The reporting cues are again, similar:

tell application id "com.figure53.qlab.3" to tell front workspace
  try
    set thesecs to (notes of cue "LOG2") - (notes of cue "LOG1")
    set theHours to (thesecs div 3600)
    set theRemainderSeconds to (thesecs mod 3600) as integer
    set theMinutes to (theRemainderSeconds div 60)
    set theRemainderSeconds to (theRemainderSeconds mod 60)
    if length of (theHours as text) = 1 then
      set theHours to "0" & (theHours as text)
    end if
    if length of (theMinutes as text) = 1 then
      set theMinutes to "0" & (theMinutes as text)
    end if
    if length of (theRemainderSeconds as text) = 1 then
      set theRemainderSeconds to "0" & theRemainderSeconds as text
    end if
    set notes of cue "LOGA" to (theHours & ":" & theMinutes & ":" & theRemainderSeconds)
  end try
  set this_data to return & " ACT1 Running Time: " & notes of cue "LOGA" & return
  set target_file to (((path to desktop folder) as string) & "SHOW LOGGER")
  set append_data to true
  try
    set the target_file to the target_file as string
    set the open_target_file to open for access file target_file with write permission
    if append_data is false then set eof of the open_target_file to 0
    write this_data to the open_target_file starting at eof
    close access the open_target_file
    return true
  on error
    try
      close access file target_file
    end try
    return false
  end try
end tell

A variable, thesecs, is set to the value of the calculation. In the above example, (notes of cue "LOG2") - (notes of cue "LOG1"), gets the running time of act one in seconds by subtracting the value of the notes field of cue LOG1 from the value of the notes of cue LOG2. The result, which is in seconds, is then converted to a string to give the duration in a more readable format, HH:MM:SS. This is then put into a data string with another string identifying what the duration refers to, and written to the log file.

Adding reporting cues

Again, these scripts are easily modified to report other durations. For example, to create a cue which logs the length of the pre-show (house open to curtain up), first copy and paste cue LOGA to create a new cue which we’ll number LOGZ. Then change the calculation line to:

set thesecs to (notes of cue "LOG1") - (notes of cue "LOG0")

Also, replace the two instances of cue “LOGA” in the script to cue “LOGZ”. Then change the text “ACT1 Running Time: ” to “PRESHOW Running Time: ” (note the trailing spaces inside the quotation marks. Those are important.)

Wait times

To enable the logging and reporting cues to be cut and pasted anywhere in the cue list, every cue contains the full script instructions to open the log file, write the data, and close the log file. Because of this, log cues run back to back need time between them to allow the file to fully close before the second log cue re-opens it. If you want to run multiple log cues back to back, put then in a Group cue set to start all children simultaneously, and use pre-waits to separate them by a second or so.

Refining the scripts

The flexibility that comes from making each log cue self-contained and capable of doing everything it needs to do is useful. On the other hand, if we have a lot of log cues and we’re making complex reports, there would be quite a lot of duplicated code. If we wanted to make an adjustment, like changing the path or name of the log file, or add a feature like sending email notifications with durations as soon as they are logged, it would be a considerable amount of work to change all the duplicate scripts.

If we were writing a conventional computer program, we would modularize our code by separating each task into a function or subroutine. One function might handle writing to a file, another would convert seconds to an HH:MM:SS string, etc, and we’d pass data between these chunks of code using variables. Script cues in QLab, however, only preserve variables within themselves; there is no straightforward way to have blocks of AppleScript shared between Script cues.

However, much as we used the Notes field of cues to hold time data, we can also use Notes fields to pass variables between Script cues. This lets us treat individual Script cues as reusable blocks of code. The main disadvantage of working this way is that these Script cues become interdependent and cannot function without each other, and moving them between workspaces can be cumbersome. This isn’t hard to get around, and it comes with enough benefit that it’s worth the effort.

This third pass at logging is built on the idea of modular, interdependent Script cues. It also refines the logging and reporting cues so that any information that needs to be changed in duplicated cues is put at the top of the scripts, clearly annotated, to make it as straightforward as possible.

The new cue list with the added subroutine Script cues looks like this:

Subroutine cues

The new cues, CALC1 and WRITE, can be anywhere in the workspace. You could create a new cue list called something like “logging helpers” if you didn’t want them in your main cue list. If you were going to use this extensively, you could put the whole of this list in a separate logging cue list in your template workspace, and then just cut and paste the LOG cues into the main cue list as needed. Alternately, you could use Start cues in the main cue list to trigger the Script cues as needed.

The cue CALC1 contains the script to convert epoch seconds to a string in HH:MM:SS format:

set mycue to "CALC1"
tell application id "com.figure53.qlab.3" to tell front workspace
  try
    set thesecs to notes of cue mycue
    set theHours to (thesecs div 3600)
    set theRemainderSeconds to (thesecs mod 3600) as integer
    set theMinutes to (theRemainderSeconds div 60)
    set theRemainderSeconds to (theRemainderSeconds mod 60)
    if length of (theHours as text) = 1 then
      set theHours to "0" & (theHours as text)
    end if
    if length of (theMinutes as text) = 1 then
      set theMinutes to "0" & (theMinutes as text)
    end if
    if length of (theRemainderSeconds as text) = 1 then
      set theRemainderSeconds to "0" & theRemainderSeconds as text
    end if
    set notes of cue mycue to (theHours & ":" & theMinutes & ":" & theRemainderSeconds)
  end try
end tell

It uses its own Notes field to receive the value from the cue which triggers it, and then places the calculated result back into its own Notes field.

The cue WRITE works similarly; it writes the contents of its own Notes field to the log file:

set mycue to "WRITE"
tell application id "com.figure53.qlab.3" to tell front workspace
  set this_data to notes of cue mycue & return
  set target_file to (((path to desktop folder) as string) & "SHOW LOGGER")
  set append_data to true
  try
    set the target_file to the target_file as string
    set the open_target_file to open for access file target_file with write permission
    if append_data is false then set eof of the open_target_file to 0
    write this_data to the open_target_file starting at eof
    close access the open_target_file
    return true
  on error
    try
      close access file target_file
    end try
    return false
  end try
end tell

Here’s the much shorter script for one of the logging cues:

set mycue to "LOG1"
set themessage to "Curtain Up"
tell application id "com.figure53.qlab.3" to tell front workspace
  set notes of cue mycue to (do shell script "date +%s") as integer
  set thedate to current date
  set this_data to (thedate as string) & space & themessage & return
  set the notes of cue "WRITE" to this_data
  start cue "WRITE"
end tell

It formats the data it wants to write into a string, and sets the notes of cue WRITE to that string. It then starts cue WRITE.

The reporting scripts look like this:

set mycue to "LOGA"
tell application id "com.figure53.qlab.3" to tell front workspace
  try
    set mystring to q name of cue mycue
    repeat while the length of mystring is less than 50
      set mystring to " " & mystring
    end repeat
    set thesecs to (notes of cue "LOG2") - (notes of cue "LOG1")
    set the notes of cue "CALC1" to thesecs
    start cue "CALC1"
    delay 0.2
    set the notes of cue mycue to the notes of cue "CALC1"
    set the notes of cue "WRITE" to return & mystring & ": " & notes of cue mycue & return
    start cue "WRITE"
  end try
end tell

This script calculates duration just like the previous examples, and sets the notes field of cue CALC1 to the result. Cue CALC1 is then started, which converts the duration into an HH:MM:SS string then sets its own notes field to that string.

The reporting cue then waits two tenths of a second to let CALC1 complete, then grabs the result, formats it nicely with the leading spaces produced by the repeat loop, and stores the result in the Notes field of the WRITE cue. Then it starts the WRITE cue, which does the actual writing.