-- TroupeIT QLab Cue Importer -- Creates QLab cues from TroupeIT metadata download -- (c) 2015-2026 TroupeIT -- -- Revision History -- ================ -- 01/19/2026 Major rewrite - Added TroupeIT bridge network cues -- 01/07/2024 Add SMPTE groupings and timecode -- 02/05/2021 Add Video extensions -- 12/01/2015 Initial Code -- -- Metadata Format (TSV): -- seq show_item_id type color title notes filename -- -- Types: Audio, Video, Memo -- show_item_id: TroupeIT ID for OSC bridge integration -- -- TroupeIT Bridge Setup: -- Configure a Network Patch in QLab Workspace Settings pointing to -- the machine running qlab_bridge.py (default port 9000) use scripting additions -- ============================================================================ -- Configuration -- ============================================================================ property kDialogTitle : "TroupeIT QLab Importer" property kAcceptableTypes : {"Audio", "Video", "Memo"} property kVideoExtensions : {".mkv", ".mp4", ".mov", ".m4v", ".avi", ".webm"} -- Network patch number for TroupeIT bridge (user should configure this in QLab) property kBridgePatchNumber : 1 -- Column indices for metadata.tsv (1-based) property kColSeq : 1 property kColShowItemId : 2 property kColType : 3 property kColColor : 4 property kColTitle : 5 property kColNotes : 6 property kColFilename : 7 -- ============================================================================ -- Color Mapping -- ============================================================================ on mapColorToQLab(tiColor) if tiColor is "red_bg" then return "red" if tiColor is "blue_bg" then return "blue" if tiColor is "cyan_bg" then return "blue" if tiColor is "green_bg" then return "green" if tiColor is "brown_bg" then return "yellow" if tiColor is "yellow_bg" then return "yellow" if tiColor is "orange_bg" then return "orange" if tiColor is "purple_bg" then return "purple" return "none" end mapColorToQLab -- ============================================================================ -- File Utilities -- ============================================================================ on getParentFolder(filePath) if class of filePath is text and filePath contains ":" then set filePath to filePath as alias else if class of filePath is not alias then set filePath to (filePath as POSIX file) as alias end if tell application "Finder" return POSIX path of ((container of filePath) as alias) end tell end getParentFolder on isVideoFile(filename) set lowercaseName to do shell script "echo " & quoted form of filename & " | tr '[:upper:]' '[:lower:]'" repeat with ext in kVideoExtensions if lowercaseName ends with ext then return true end repeat return false end isVideoFile -- ============================================================================ -- QLab Helpers -- ============================================================================ on setQLabCueColor(theCue, qlabColor) -- QLab 5 supports setting q color directly tell application "QLab" set q color of theCue to qlabColor end tell end setQLabCueColor -- ============================================================================ -- Cue Creation -- ============================================================================ on createBridgeNetworkCue(theShowItemId) -- Creates a Network Cue that sends OSC to TroupeIT bridge set oscMsg to "/troupeit/cue " & theShowItemId tell application "QLab" tell front workspace make type "Network" set networkCue to last item of (selected as list) set q name of networkCue to "TroupeIT Bridge" set continue mode of networkCue to auto_continue -- Set custom OSC message set custom string of networkCue to oscMsg end tell end tell return networkCue end createBridgeNetworkCue on createTimecodeStartCue() tell application "QLab" tell front workspace make type "Timecode" set startSMPTE to last item of (selected as list) set q name of startSMPTE to "Start Timecode" set audio output patch number of startSMPTE to 2 set smpte format of startSMPTE to fps_30_non_drop set type of startSMPTE to ltc set output channel of startSMPTE to 1 set continue mode of startSMPTE to auto_continue end tell end tell return startSMPTE end createTimecodeStartCue on createTimecodeStopCue(startSMPTE) tell application "QLab" tell front workspace make type "Stop" set stopSMPTE to last item of (selected as list) set q name of stopSMPTE to "Stop Timecode" set cue target of stopSMPTE to startSMPTE end tell end tell return stopSMPTE end createTimecodeStopCue on createMemoCue(cueData, generateBridgeCues, generateTimecode) set theTitle to theTitle of cueData set theNotes to theNotes of cueData set theColor to theColor of cueData set theShowItemId to theShowItemId of cueData tell application "QLab" tell front workspace -- Create group container make type "Group" set newGroup to last item of (selected as list) set groupId to uniqueID of newGroup set q name of newGroup to theTitle set mode of newGroup to start_first -- Track cue list for moving cues set cueList to parent of newGroup end tell end tell -- Set group color my setQLabCueColor(newGroup, my mapColorToQLab(theColor)) -- Add bridge network cue if enabled (fires first) if generateBridgeCues then set networkCue to my createBridgeNetworkCue(theShowItemId) tell application "QLab" tell front workspace move cue id (uniqueID of networkCue) of cueList to end of cue id groupId end tell end tell end if -- Create the memo cue tell application "QLab" tell front workspace make type "Memo" set memoCue to last item of (selected as list) set q name of memoCue to theTitle set notes of memoCue to theNotes move cue id (uniqueID of memoCue) of cueList to end of cue id groupId -- Collapse the group collapse cue id groupId end tell end tell return newGroup end createMemoCue on createMediaCue(cueData, pathPrefix, generateBridgeCues, generateTimecode) set theType to theType of cueData set theTitle to theTitle of cueData set theNotes to theNotes of cueData set theColor to theColor of cueData set theFilename to theFilename of cueData set theShowItemId to theShowItemId of cueData tell application "QLab" tell front workspace -- Create group container make type "Group" set newGroup to last item of (selected as list) set groupId to uniqueID of newGroup set q name of newGroup to theTitle set notes of newGroup to theNotes set mode of newGroup to start_first -- Track cue list for moving cues set cueList to parent of newGroup end tell end tell -- Set group color my setQLabCueColor(newGroup, my mapColorToQLab(theColor)) -- Add timecode start if enabled set startSMPTE to missing value if generateTimecode then set startSMPTE to my createTimecodeStartCue() tell application "QLab" tell front workspace move cue id (uniqueID of startSMPTE) of cueList to end of cue id groupId end tell end tell end if -- Add bridge network cue if enabled if generateBridgeCues then set networkCue to my createBridgeNetworkCue(theShowItemId) tell application "QLab" tell front workspace move cue id (uniqueID of networkCue) of cueList to end of cue id groupId end tell end tell end if -- Create the media cue tell application "QLab" tell front workspace make type theType set mediaCue to last item of (selected as list) set q name of mediaCue to theTitle set notes of mediaCue to theNotes if generateTimecode then set continue mode of mediaCue to auto_follow end if -- Set file target try set fullPath to pathPrefix & theFilename set file target of mediaCue to fullPath on error errMsg display dialog "File not found: " & fullPath buttons {"Continue"} default button "Continue" end try move cue id (uniqueID of mediaCue) of cueList to end of cue id groupId end tell end tell -- Add timecode stop if enabled if generateTimecode then set stopSMPTE to my createTimecodeStopCue(startSMPTE) tell application "QLab" tell front workspace move cue id (uniqueID of stopSMPTE) of cueList to end of cue id groupId end tell end tell end if -- Collapse the group tell application "QLab" tell front workspace collapse cue id groupId end tell end tell return newGroup end createMediaCue on createUnprocessedCue(lineNumber, lineContent, theType) tell application "QLab" tell front workspace make type "Memo" set newCue to last item of (selected as list) set q name of newCue to "WARNING: Unprocessed line " & lineNumber set notes of newCue to "Type: " & theType & return & "Content: " & lineContent end tell end tell log "Unprocessed line " & lineNumber & ": " & lineContent end createUnprocessedCue -- ============================================================================ -- Record Parsing -- ============================================================================ on parseMetadataLine(lineText) set AppleScript's text item delimiters to tab set columns to text items of lineText set AppleScript's text item delimiters to "" if (count of columns) < kColFilename then return missing value set parsedType to item kColType of columns -- Handle Unknown type with video extension if parsedType is "Unknown" then set parsedFilename to item kColFilename of columns if my isVideoFile(parsedFilename) then set parsedType to "Video" end if return {theSeq:item kColSeq of columns, theShowItemId:item kColShowItemId of columns, theType:parsedType, theColor:item kColColor of columns, theTitle:item kColTitle of columns, theNotes:item kColNotes of columns, theFilename:item kColFilename of columns} end parseMetadataLine -- ============================================================================ -- Validation -- ============================================================================ on validateQLab() tell application "System Events" if (count of (every process whose name is "QLab")) is 0 then display dialog "Please start QLab before running this script." buttons {"OK"} default button "OK" return false end if end tell tell application "QLab" try get selected of front workspace on error display dialog "Please open a QLab workspace before running this script." buttons {"OK"} default button "OK" return false end try end tell return true end validateQLab on validateMetadataFile(theFile) tell application "System Events" set filename to name of theFile end tell if filename is not "metadata.tsv" then display dialog "Please select the file named metadata.tsv from your TroupeIT download." buttons {"OK"} default button "OK" return false end if return true end validateMetadataFile -- ============================================================================ -- Main Script -- ============================================================================ on run -- Validate QLab is ready if not my validateQLab() then return -- Select metadata file set metadataFile to choose file with prompt "Select metadata.tsv from your TroupeIT download:" default location (path to downloads folder) without invisibles if not my validateMetadataFile(metadataFile) then return -- Ask about TroupeIT bridge cues set generateBridgeCues to false set bridgeDialogMsg to "Generate Network Cues for TroupeIT bridge?" & return & return & "(Requires qlab_bridge.py running and Network Patch " & kBridgePatchNumber & " configured)" try display dialog bridgeDialogMsg buttons {"No", "Yes"} default button "Yes" cancel button "No" set generateBridgeCues to true on error set generateBridgeCues to false end try -- Ask about timecode generation set generateTimecode to false try display dialog "Generate SMPTE timecode cues for each act?" buttons {"No", "Yes"} default button "No" cancel button "No" set generateTimecode to true on error set generateTimecode to false end try -- Read the file try set fileContents to read metadataFile as text on error errMsg display dialog "Could not read metadata file: " & errMsg buttons {"OK"} default button "OK" return end try set pathPrefix to my getParentFolder(metadataFile) set lineList to paragraphs of fileContents set totalLines to count of lineList -- Setup progress bar set progress total steps to totalLines set progress completed steps to 0 set progress description to "Importing cues to QLab..." -- Process each line tell application "QLab" activate end tell repeat with lineNum from 1 to totalLines set progress additional description to "Processing cue " & lineNum & " of " & totalLines set currentLine to item lineNum of lineList -- Skip empty lines if currentLine is not "" then set cueData to my parseMetadataLine(currentLine) if cueData is missing value then my createUnprocessedCue(lineNum, currentLine, "Parse Error") else if theType of cueData is not in kAcceptableTypes then my createUnprocessedCue(lineNum, currentLine, theType of cueData) else if theType of cueData is "Memo" then my createMemoCue(cueData, generateBridgeCues, generateTimecode) else my createMediaCue(cueData, pathPrefix, generateBridgeCues, generateTimecode) end if end if set progress completed steps to lineNum end repeat -- Reset progress set progress total steps to 0 set progress completed steps to 0 set progress description to "" set progress additional description to "" -- Build summary message set summaryMsg to "Successfully imported " & totalLines & " cues." if generateBridgeCues then set summaryMsg to summaryMsg & return & return & "TroupeIT bridge Network Cues were created." & return & "Make sure Network Patch " & kBridgePatchNumber & " is configured in QLab Workspace Settings." end if display dialog summaryMsg buttons {"OK"} default button "OK" giving up after 10 end run