Thursday, December 14, 2017

#1 2015-05-03 07:24:17 pm

tneison
Member
Registered: 2015-04-03
Posts: 66

A script for adding hotkeys to Final Cut Pro X

In addition to being able to add overlays and transitions with hotkeys, the script also allows you to time-scale the overlay to match the duration of the selected clip on the timeline!

Instructions:

1) Place the script bundle scripts in "~/library/script libraries/". See below for further instructions on script bundles.
2) Download and install FastScripts http://www.red-sweater.com/fastscripts/
3) Place the following script in "~/library/scripts/"

Applescript:

set thisPath to "~/the/path/to/a/folder"
tell script "[the name you gave your 1st script bundle without the extension]"
its createClipboardDefinitionFiles:thisPath
end tell

4) In the above script, Substitute "~/the/path/to/a/folder" for a folder path of your choosing (the directory need not exist), also replace the text within [brackets] with the name you gave your 1st script bundle. Don't forget to delete the [brackets]
5) In FastScripts, go to Preferences → Script Shortcuts and choose a shortcut for your script
6) In FCPX place an overlay element (title, generator, etc) on the timeline as a Connected Clip (not as the Primary Storyline) - or - add a transition to a clip.
7) Making sure that the FCPX element is selected, invoke the hotkey you created in step 5. Wait until the process finishes.
8) Go to the folder you chose in step 4 and note the name of the newly created folder
9) Place the following script in "~/library/scripts/"  and save it using the name of the newly created folder. Replace the items within [brackets] with the appropriate path and name respectively (~tildes are okay). Don't forget to delete the [brackets]

Applescript:

set thisPath to "[the path to the newly created folder]"
tell script "[the name you gave your 2nd script bundle without the extension]"
its implementSimpleElement:thisPath
end tell

10) Place another script in "~/library/scripts/" and give it an identical name except append "-Time-Scaled" to the end. Replace the two items within [brackets] with the appropriate path and name respectively (~tildes are okay). Don't forget to delete the [brackets]

Applescript:

set thisPath to "[the path to the newly created folder]"
tell script "[the name you gave your 3rd script bundle without the extension]"
its implementTimeScaledElement:thisPath
end tell

11) In FastScripts, choose hotkeys for the two new scripts. The hotkey for the script you created in step 9 places a 5 second element on the timeline. The hotkey for the script you created in step 10 only works if you have a clip on the timeline selected. When one is selected and the hotkey is invoked, it will place an overlay on the timeline matching the duration of the selected clip.
12) Repeat steps 6 thru 11 to create additional hotkeys.

Script Bundles
Below are the script bundles (save each script as script-bundle. If you are using Mavericks click the 'Bundle Contents' button and check the box next to 'AppleScript/Objective-C')

Don't forget to save them in "~/library/script libraries/"

Script Bundle 1 of 3: A script for creating definition files

Applescript:

use framework "Foundation"
use framework "AppKit"
use scripting additions


its createClipboardDefinitionFiles:thisPath

############################ (primary function) ############################

on createClipboardDefinitionFiles:thePath
   
   -- ↓ copy FCPX element to clipboard, read it as a clipboard item, and unpack the plist ↓
   set {theKey, firstValueIndex, theFCPXobject} to getFCPXitemsFromClipboard()
   
   if firstValueIndex ≠ current application's NSNotFound then
       
       -- ↓ within this conditional is everything that can be time-scaled (titles, generators, etc.) ↓
       set theFullPathObjC to createDefinitionFilesForTimescaleableElements(thePath)
   else
       
       -- ↓ within this conditional is transistions b/c they cannot be time-scaled ↓
       set theNameLocation to createDefinitionFileForSimpleElement(theKey, thePath, theFCPXobject)
       set theFullPathObjC to createDefinitionFileForSimpleElement(theKey, thePath, theFCPXobject)
   end if
   -- ↓ creates a separate file using original input from the first handler. This will be used in the simple element script ↓
   its createDefaultDurationElement(theFullPathObjC, theFCPXobject)
   
end createClipboardDefinitionFiles:

####################################################################



############################ Get FCPX Items From Clipboard ############################

on getFCPXitemsFromClipboard()
   tell application "Final Cut Pro"
       activate
   end tell
   delay 0.5
   tell application "System Events"
       key code 19 using command down -- selects the timeline
       delay 0.5
       keystroke "c" using command down
   end tell
   delay 0.5
   set theNSPasteboard to current application's NSPasteboard's generalPasteboard() -- get all clipboard data
   if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then -- is there FCPX data?
       set theFCPXobject to theNSPasteboard's propertyListForType:"com.apple.flexo.proFFPasteboardUTI" -- the FCPX type
       set thePlist to current application's NSDictionary's dictionaryWithDictionary:theFCPXobject -- read it as a property list
       set theObj to thePlist's objectForKey:"ffpasteboardobject" -- this is a NSArchive object
       set {theDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:theObj options:0 format:(missing value) |error|:(reference) -- unarchive it
       if theDict = missing value then
           error (theError's localizedDescription() as text) -- tell it to display error if one is present
       end if
       set theKey to theDict's objectForKey:"$objects" -- this key contains all the timecode values
   else
       set theKey to missing value
   end if
   if theKey = missing value then
       display dialog "FCPX Clipboard Data is not present"
       quit
   end if
   set firstValueIndex to theKey's indexOfObject:"__timelineContainerClip" -- this is the value immediately before the timecode value
   return {theKey, firstValueIndex, theFCPXobject}
end getFCPXitemsFromClipboard

############################ Create definition files for timescalable FCPX elements ############################

on createDefinitionFilesForTimescaleableElements(thePath)
   
   -- ↓ This will create several FCPX element files in a user-specified folder. Each file is timescaleable within a specified time-range ↓
   set {theCtrRecord, theOutputFilePath, theDirectory} to createMultibleDefinitionFiles(thePath)
   
   -- ↓ This file tells the implementation script where the placeholder timecodes are located within each corresponding element file ↓
   set theTCLocationDictionaryObjC to current application's NSDictionary's dictionaryWithDictionary:theCtrRecord
   set theOutputFilePathObjC to current application's NSString's stringWithString:theOutputFilePath
   theTCLocationDictionaryObjC's writeToFile:theOutputFilePathObjC atomically:true
   return theDirectory
end createDefinitionFilesForTimescaleableElements



on createMultibleDefinitionFiles(thePath)
   set TimeCodeArray to theTCarray()
   repeat with x from 1 to count of TimeCodeArray
       
       
       tell application "Final Cut Pro"
           activate
       end tell
       
       tell application "System Events"
           key code 19 using command down -- selects the timeline
           delay 0.1
           keystroke "d" using control down
           delay 0.1
           set FirstKeyCode to item 4 of (item x of TimeCodeArray)
           key code FirstKeyCode
           if 4 < x then
               set SecondKeyCode to item 5 of (item x of TimeCodeArray)
               key code SecondKeyCode
           end if
           delay 0.1
           if 8 < x then
               set ThirdKeyCode to item 6 of (item x of TimeCodeArray)
               key code ThirdKeyCode
           end if
           delay 0.1
           if 10 < x then
               set FourthKeyCode to item 7 of (item x of TimeCodeArray)
               key code FourthKeyCode
           end if
           delay 0.1
           key code 36
           delay 0.1
           keystroke "c" using command down
           delay 0.1
       end tell
       
       set theNSPasteboard to current application's NSPasteboard's generalPasteboard() -- get all clipboard data
       if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then -- is there FCPX data?
           set theFCPXobject to (theNSPasteboard's propertyListForType:"com.apple.flexo.proFFPasteboardUTI") -- the FCPX type
           set thePlist to (current application's NSDictionary's dictionaryWithDictionary:theFCPXobject) -- read it as a property list
           set theObj to (thePlist's objectForKey:"ffpasteboardobject") -- this is a NSArchive object
           set {theDict, theError} to (current application's NSPropertyListSerialization's propertyListWithData:theObj options:0 format:(missing value) |error|:(reference)) -- unarchive it
           if theDict = missing value then
               error (theError's localizedDescription() as text) -- tell it to display error if one is present
           end if
           
           set theKey to (theDict's objectForKey:"$objects") -- this key contains all the timecode values
       else
           set theKey to missing value
       end if
       if theKey = missing value then
           display dialog "FCPX Clipboard Data is not present"
       end if
       current application's NSLog("%@", theKey)
       
       set firstValueIndex to (theKey's indexOfObject:"__timelineContainerClip")
       set theMasterTimeCode to (theKey's objectAtIndex:(firstValueIndex + 1)) -- get timecode value
       set firstTC to (getPlaceholderTCatIndex(firstValueIndex, theMasterTimeCode)) as text
       set tcInSeconds to convertToSeconds(firstTC) -- convert it from a fraction to seconds
       
       
       repeat with TheTCrange in TimeCodeArray
           set inpointInSeconds to item 1 of TheTCrange
           set outpointInSeconds to item 2 of TheTCrange
           set ElementFile to item 3 of TheTCrange
           if inpointInSeconds < tcInSeconds and tcInSeconds < outpointInSeconds then
               set theElementFile to ElementFile & ".plist"
               exit repeat
           end if
       end repeat
       
       set theFirstTClocation to (firstValueIndex + 1) as integer
       
       set TCin120000ths to (convertFraction(firstTC, 120000) as text)
       
       
       
       
       set ASkey to theKey as list
       
       set theFirstTC to (item (theFirstTClocation + 1) of ASkey) as text
       
       repeat with i from (theFirstTClocation + 3) to count of ASkey
           if (item i of ASkey) contains TCin120000ths then
               set theSecondTClocation to (i - 1)
               exit repeat
           end if
       end repeat
       
       set theSecondTC to (item (theSecondTClocation + 1) of ASkey) as text
       
       repeat with i from (theSecondTClocation + 3) to count of ASkey
           if (item i of ASkey) contains TCin120000ths then
               set theThirdTClocation to (i - 1)
               exit repeat
           end if
       end repeat
       
       set theThirdTC to (item (theThirdTClocation + 1) of ASkey) as text
       
       repeat with i from 1 to count of ASkey
           if (item i of ASkey) contains ".localized" then
               set theNameLocation to (i - 1)
               exit repeat
           end if
       end repeat
       
       set theobjectPath to (theKey's objectAtIndex:theNameLocation)
       set theobjectFile to theobjectPath's lastPathComponent()
       set thisObjectName to (theobjectFile's stringByReplacingOccurrencesOfString:".moti" withString:"")
       set thisObjectName to (thisObjectName's stringByReplacingOccurrencesOfString:".motr" withString:"")
       set thisObjectName to (thisObjectName's stringByReplacingOccurrencesOfString:".motn" withString:"")
       set pathWithTild to (current application's NSString's stringWithString:thePath)
       set extendedPath to (current application's NSString's stringWithString:theElementFile)
       set fullPath to pathWithTild's stringByExpandingTildeInPath()
       set theDirectory to (fullPath's stringByAppendingPathComponent:thisObjectName)
       set fileManager to current application's NSFileManager's defaultManager()
       if (fileManager's fileExistsAtPath:theDirectory) as boolean then
           set goodToGo to missing value
       else
           (fileManager's createDirectoryAtPath:theDirectory withIntermediateDirectories:true attributes:(missing value) |error|:(missing value))
       end if
       set theOutputFile to (theDirectory's stringByAppendingPathComponent:extendedPath)
       (theFCPXobject's writeToFile:theOutputFile atomically:true) -- write it to the appropriate folder
       
       
       ### WRITE PLIST CONTROL FILE ###
       -- set labelList to {item 3 of (item x of TimeCodeArray)}
       
       if x = 1 then
           set ValueLabel to {|6 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 2 then
           set ValueLabel to {|7 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 3 then
           set ValueLabel to {|8 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 4 then
           set ValueLabel to {|9 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 5 then
           set ValueLabel to {|10 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 6 then
           set ValueLabel to {|11 thru 24 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 7 then
           set ValueLabel to {|25 Thru 99 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 8 then
           set ValueLabel to {|100 Thru 249 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 9 then
           set ValueLabel to {|250 Thru 999 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 10 then
           set ValueLabel to {|1000 Thru 2497 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 11 then
           set ValueLabel to {|2498 Thru 299700 Frames|:{|1st TC in 30000ths of a second|:theFirstTClocation, |The first TC value|:theFirstTC, |2nd TC in 120000ths of a second|:theSecondTClocation, |The second TC value|:theSecondTC, |3rd TC in 120000ths of a second|:theThirdTClocation, |The third TC value|:theThirdTC, |The name location|:theNameLocation}}
       end if
       if x = 1 then
           set theMasterArray to ValueLabel
       end if
       
       if x > 1 then
           set theMasterArray to theMasterArray & ValueLabel
       end if
       
       if x = 11 then -- once all elements are added to the record, output to plist file
           set thisObjectName to thisObjectName as text
           set thisObjectName to thisObjectName & " - TC Location Dictionary.plist"
           set thisObjectName to (current application's NSString's stringWithString:thisObjectName)
           set theOutputFilePath to (theDirectory's stringByAppendingPathComponent:thisObjectName)
           set theOutputFilePath to theOutputFilePath as text
       end if
   end repeat
   return {theMasterArray, theOutputFilePath, theDirectory}
end createMultibleDefinitionFiles

on theTCarray()
   set TimeCodeArray to ¬
       {{0.1, 0.232566666666, "6 Frames", 22}, ¬
           {0.233566666666, 0.265933333332, "7 Frames", 26}, ¬
           {0.266933333332, 0.266933333334, "8 Frames", 28}, ¬
           {0.3002, 0.3004, "9 Frames", 25}, ¬
           {0.333666666666, 0.333666666669, "10 Frames", 18, 29}, ¬
           {0.333666666668, 0.82, "11 Thru 24 Frames", 18, 23}, ¬
           {0.834166666666, 3.3034, "25 Thru 99 Frames", 19, 47}, ¬
           {3.3033, 8.3083, "100 Thru 249 Frames", 23, 47}, ¬
           {8.3083, 33.3333, "250 Thru 999 Frames", 19, 29, 47}, ¬
           {33.3333, 83.316566666667, "1000 Thru 2497 Frames", 23, 29, 47}, ¬
           {83.316566666667, 10000, "2498 Thru 299700 Frames", 18, 20, 29, 47}} -- each of these range in time require that a separate file be placed on the clipboard
   (* As you can see the above array increases in a exponential manner. The last value is incorect b/c the upper range is too high. One could add to the array for greater durations. *)
   return TimeCodeArray
end theTCarray

on getPlaceholderTCatIndex(theTClocation, thisObject)
   set theCharacterSet to current application's NSCharacterSet's characterSetWithCharactersInString:"(){}"
   set theObjectDelimitedByPunctuation to thisObject's componentsSeparatedByCharactersInSet:theCharacterSet
   set theMutableObjectDelimitedByPunctuation to current application's NSMutableArray's arrayWithArray:theObjectDelimitedByPunctuation
   theMutableObjectDelimitedByPunctuation's removeObjectIdenticalTo:""
   theMutableObjectDelimitedByPunctuation's removeObjectIdenticalTo:","
   set thePlaceholderTC to theMutableObjectDelimitedByPunctuation's lastObject()
   return thePlaceholderTC
end getPlaceholderTCatIndex

on convertToSeconds(theTC)
   set THEslash to the offset of "/" in theTC
   set numeratorEND to THEslash - 1
   set denominatorSTART to THEslash + 1
   set the_numerator to text 1 thru numeratorEND of theTC as integer
   set fraction_length to length of theTC
   set fraction_length to fraction_length
   set the_denominator to text denominatorSTART thru fraction_length of theTC as integer
   set decimal_number to (the_numerator / the_denominator)
end convertToSeconds

on convertFraction(theFraction, newDenominator)
   set theFraction to current application's NSString's stringWithString:theFraction
   set theCharacterSet to current application's NSCharacterSet's characterSetWithCharactersInString:"/"
   set theNumeratorAndDenominatorArray to theFraction's componentsSeparatedByCharactersInSet:theCharacterSet
   set theNumerator to theNumeratorAndDenominatorArray's firstObject()
   set theDenominator to theNumeratorAndDenominatorArray's lastObject()
   set newNumber to ((((theNumerator as text) as integer) / ((theDenominator as text) as integer)) * newDenominator)
   set theFormatter to current application's NSNumberFormatter's new()
   theFormatter's setNumberStyle:(current application's NSNumberFormatterNoStyle)
   set theResult to theFormatter's stringFromNumber:newNumber
   set theResult to current application's NSString's stringWithFormat_("%@/%@", theResult, newDenominator)
   return theResult
end convertFraction

############################ Create definition file for simple, non-timescaleable FCPX elements (i.e. transisitons) ############################

on createDefinitionFileForSimpleElement(theKey, thePath, theFCPXobject)
   set ASkey to theKey as list
   repeat with i from 1 to count of ASkey
       if (item i of ASkey) contains ".localized" then
           set theNameLocation to i
       else
           set theNameLocation to missing value
       end if
   end repeat
   if theNameLocation = missing value then
       set theNameLocation to 5
   end if
   set theobjectPath to theKey's objectAtIndex:(theNameLocation - 1) -- get the item containing the name of the object
   set theobjectFile to theobjectPath's lastPathComponent() -- use only the last part of the path
   set thisObjectName to theobjectFile's stringByReplacingOccurrencesOfString:".moti" withString:"" -- remove the extentions
   set thisObjectName to thisObjectName's stringByReplacingOccurrencesOfString:".motr" withString:""
   set thisObjectName to thisObjectName's stringByReplacingOccurrencesOfString:".motn" withString:""
   set pathWithTild to current application's NSString's stringWithString:thePath -- bring in our application support directory
   set fullPath to pathWithTild's stringByExpandingTildeInPath() -- expand the tild
   set theFullPath to fullPath's stringByAppendingPathComponent:thisObjectName -- create a filepath including a folder named after the object
   set fileManager to current application's NSFileManager's defaultManager()
   if (fileManager's fileExistsAtPath:theFullPath) as boolean then -- look to see if the folder already exists
       set goodToGo to missing value
   else
       fileManager's createDirectoryAtPath:theFullPath withIntermediateDirectories:false attributes:(missing value) |error|:(missing value) -- if not create one
   end if
   set plistName to current application's NSString's stringWithString:"Default duration.plist"
   set theFullPath to theFullPath's stringByAppendingPathComponent:plistName -- append the name of the object to the end of our path
   theFCPXobject's writeToFile:theFullPath atomically:true -- write it to the appropriate folder
   return theFullPath
end createDefinitionFileForSimpleElement



on createDefaultDurationElement(theFullPath, theFCPXobject)
   set plistName to current application's NSString's stringWithString:"Default duration.plist"
   set theFullPath to theFullPath's stringByAppendingPathComponent:plistName -- append the name of the object to the end of our path
   theFCPXobject's writeToFile:theFullPath atomically:true -- write it to the appropriate folder
end createDefaultDurationElement

Script Bundle 2 of 3: A script for placing simple (non-time scaled) elements on the timeline

Applescript:

use framework "Foundation"
use framework "AppKit"
use scripting additions


on implementSimpleElement:thePath
   set thePathObjC to expandTild(thePath)
   set theFullPathToFileObjC to thePathObjC's stringByAppendingPathComponent:"Default duration.plist"
   set OBJcDict to current application's NSDictionary's dictionaryWithContentsOfFile:theFullPathToFileObjC
   set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
   theNSPasteboard's clearContents()
   theNSPasteboard's setPropertyList:OBJcDict forType:"com.apple.flexo.proFFPasteboardUTI"
   theNSPasteboard's writeObjects:OBJcDict
   tell application "Final Cut Pro"
       activate
   end tell
   tell application "System Events"
       key code 19 using command down -- select the timeline
       delay 0.1
       keystroke "v" using command down
   end tell
end implementSimpleElement:


on expandTild(thePathWithTild)
   set pathWithTild to current application's NSString's stringWithString:thePathWithTild -- bring in our application support directory
   set fullPathObjC to pathWithTild's stringByExpandingTildeInPath() -- expand the tild
   return fullPathObjC
end expandTild

Script Bundle 3 of 3: A script for placing time-scaled elements on the time-line

Applescript:

use framework "Foundation"
use framework "AppKit"
use scripting additions



############################ (primary function) ############################

on implementTimeScaledElement:thePath
   
   -- ↓ copy FCPX element to clipboard, read it as a clipboard item, and unpack the plist ↓
   set theKeyFromPbObjC to getFCPXitemsFromClipboard()
   
   -- ↓ read the timecode (TC) that is embeded in the plist ↓
   set {theTCinSeconds, theTCFraction} to readFCPXtcFromKey(theKeyFromPbObjC)
   
   -- ↓ Return the appropriate, time-scaleable FCPX element (another plist) for further processing ↓
   set {theElementKeyObjC, theElementTCName, thePathObjC, theUnmodifiedffpasteboardobject} to findTheCorrectTimeScalableFileFromElementFolder(theTCinSeconds, thePath, theTCFraction)
   
   -- ↓ Overwrite the TC values using the TC from the clipboard item ↓
   set theElementWithModifiedTC to modifyTheTCvaluesWithinTheElement(theElementKeyObjC, theElementTCName, thePathObjC, theTCFraction)
   
   -- ↓ write the modified FCPX element to the clipboard and paste it onto the timeline ↓
   its writeElementWithModifiedTCtoClipboardAndPasteIntoFCPX(theElementWithModifiedTC, theUnmodifiedffpasteboardobject)
   
end implementTimeScaledElement:

####################################################################



############################ Get FCPX Items From Clipboard ############################

on getFCPXitemsFromClipboard()
   set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
   theNSPasteboard's clearContents()
   delay 0.05
   tell application "Final Cut Pro"
       activate
   end tell
   tell application "System Events"
       key code 19 using command down -- selects the timeline
       delay 0.1
       keystroke "c" using command down
   end tell
   repeat -- grab the FCPX data from the clipboard as soon as it become available
       if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then
           exit repeat
       end if
   end repeat
   set theFCPXobject to theNSPasteboard's propertyListForType:"com.apple.flexo.proFFPasteboardUTI" -- the FCPX type
   set thePlist to current application's NSDictionary's dictionaryWithDictionary:theFCPXobject -- read it as a property list
   set theObj to thePlist's objectForKey:"ffpasteboardobject" -- this is a NSArchive object
   set {theDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:theObj options:0 format:(missing value) |error|:(reference) -- unarchive it
   if theDict = missing value then
       error (theError's localizedDescription() as text) -- tell it to display error if one is present
   end if
   
   set theKeyFromPbObjC to theDict's objectForKey:"$objects" -- this array contains all the timecode values    
   return theKeyFromPbObjC
end getFCPXitemsFromClipboard

############################ Read the timecode from the FCPX clipboard item ############################

on readFCPXtcFromKey(theKey)
   set firstValueIndex to theKey's indexOfObject:"__timelineContainerClip" -- this is the value immediately before the timecode value
   if firstValueIndex = current application's NSNotFound then
       display dialog "The timecode cannot be read from this item"
       return 0
   else
       set theMasterTimeCode to (theKey's objectAtIndex:(firstValueIndex + 1)) -- get timecode value
       set firstValueIndex to (firstValueIndex + 1)
       set firstTC to (getPlaceholderTCatIndex(firstValueIndex, theMasterTimeCode)) as text
       set tcInSeconds to convertToSeconds(firstTC) -- convert it from a fraction to seconds
       return {tcInSeconds, firstTC}
   end if
end readFCPXtcFromKey

on convertToSeconds(theTC)
   set THEslash to the offset of "/" in theTC
   set numeratorEND to THEslash - 1
   set denominatorSTART to THEslash + 1
   set the_numerator to text 1 thru numeratorEND of theTC as integer
   set fraction_length to length of theTC
   set fraction_length to fraction_length
   set the_denominator to text denominatorSTART thru fraction_length of theTC as integer
   set decimal_number to (the_numerator / the_denominator)
end convertToSeconds

############################ (shared function) ############################

on getPlaceholderTCatIndex(theTClocation, thisObject)
   set theCharacterSet to current application's NSCharacterSet's characterSetWithCharactersInString:"(){}"
   set theObjectDelimitedByPunctuation to thisObject's componentsSeparatedByCharactersInSet:theCharacterSet
   set theMutableObjectDelimitedByPunctuation to current application's NSMutableArray's arrayWithArray:theObjectDelimitedByPunctuation
   theMutableObjectDelimitedByPunctuation's removeObjectIdenticalTo:""
   theMutableObjectDelimitedByPunctuation's removeObjectIdenticalTo:","
   set thePlaceholderTC to theMutableObjectDelimitedByPunctuation's lastObject()
   return thePlaceholderTC
end getPlaceholderTCatIndex

############################ Return a suitable time-scaleable FCPX element ############################

on findTheCorrectTimeScalableFileFromElementFolder(theTCinSeconds, thePath)
   set timeCodeArray to theTCarray()
   repeat with TheTCrange in timeCodeArray
       set inpointInSeconds to item 1 of TheTCrange
       set outpointInSeconds to item 2 of TheTCrange
       set theElementTCName to item 3 of TheTCrange
       if inpointInSeconds < theTCinSeconds and theTCinSeconds < outpointInSeconds then
           set theElementFile to theElementTCName & ".plist"
           exit repeat
       end if
   end repeat
   set theElementFile to current application's NSString's stringWithString:theElementFile
   set thePathObjC to expandTild(thePath)
   set theFullPathToFile to thePathObjC's stringByAppendingPathComponent:theElementFile
   set thePlistObjC to current application's NSDictionary's dictionaryWithContentsOfFile:theFullPathToFile
   set {theKeyFromFileObjC, theObj} to getFCPXclipboardItemFromFile(thePlistObjC)
   return {theKeyFromFileObjC, theElementTCName, thePathObjC, theObj}
end findTheCorrectTimeScalableFileFromElementFolder

on theTCarray()
   set timeCodeArray to ¬
       {{0.1, 0.232566666666, "6 Frames", 22}, ¬
           {0.233566666666, 0.265933333332, "7 Frames", 26}, ¬
           {0.266933333332, 0.266933333334, "8 Frames", 28}, ¬
           {0.3002, 0.3004, "9 Frames", 25}, ¬
           {0.333666666666, 0.333666666669, "10 Frames", 18, 29}, ¬
           {0.333666666668, 0.82, "11 Thru 24 Frames", 18, 23}, ¬
           {0.834166666666, 3.3034, "25 Thru 99 Frames", 19, 47}, ¬
           {3.3033, 8.3083, "100 Thru 249 Frames", 23, 47}, ¬
           {8.3083, 33.3333, "250 Thru 999 Frames", 19, 29, 47}, ¬
           {33.3333, 83.316566666667, "1000 Thru 2497 Frames", 23, 29, 47}, ¬
           {83.316566666667, 10000, "2498 Thru 299700 Frames", 18, 20, 29, 47}} -- each of these range in time require that a separate file be placed on the clipboard
   (* As you can see the above array increases in a exponential manner. The last value is incorect b/c the upper range is too high. One could add to the array for greater durations. *)
   return timeCodeArray
end theTCarray

on getFCPXclipboardItemFromFile(thePlist)
   set theObj to thePlist's objectForKey:"ffpasteboardobject"
   set {theDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:theObj options:0 format:(missing value) |error|:(reference)
   if theDict = missing value then
       error (theError's localizedDescription() as text)
   end if
   set theKeyObjC to theDict's valueForKey:"$objects"
   return {theKeyObjC, theDict}
end getFCPXclipboardItemFromFile

############################ (shared function) ############################

on expandTild(thePathWithTild)
   set pathWithTild to current application's NSString's stringWithString:thePathWithTild -- bring in our application support directory
   set fullPathObjC to pathWithTild's stringByExpandingTildeInPath() -- expand the tild
   return fullPathObjC
end expandTild

############################ Modify the FCPX element's timecode ############################

on modifyTheTCvaluesWithinTheElement(theElementKeyObjC, theElementTCName, thePathObjC, theTCFraction)
   set theSecondPlaceholderObjectObjC to theElementKeyObjC's objectAtIndex:10
   set theThirdPlaceholderObjectObjC to theElementKeyObjC's objectAtIndex:23
   set theSecondPlaceholderTCobjC to getPlaceholderTC(theSecondPlaceholderObjectObjC)
   set theThirdPlaceholderTCobjC to getPlaceholderTC(theThirdPlaceholderObjectObjC)
   set theFirstTimeScaledObject to current application's NSString's stringWithFormat_("{(0/1),(%@)}", theTCFraction)
   set theSecondTimeScaledObject to current application's NSString's stringWithFormat_("{(%@),(%@)}", theSecondPlaceholderTCobjC, theTCFraction)
   set theThirdTimeScaledObject to current application's NSString's stringWithFormat_("{(%@),(%@)}", theThirdPlaceholderTCobjC, theTCFraction)
   set theElementMutableKeyObjC to current application's NSMutableArray's arrayWithArray:theElementKeyObjC -- Convert the generic element to a mutable element
   theElementMutableKeyObjC's replaceObjectAtIndex:5 withObject:theFirstTimeScaledObject -- Replace all the TC objects with the new ones
   theElementMutableKeyObjC's replaceObjectAtIndex:10 withObject:theSecondTimeScaledObject
   theElementMutableKeyObjC's replaceObjectAtIndex:23 withObject:theThirdTimeScaledObject
   return theElementMutableKeyObjC -- return the object with modified TCs
end modifyTheTCvaluesWithinTheElement

on getPlaceholderTC(thisObject)
   set theCharacterSet to current application's NSCharacterSet's characterSetWithCharactersInString:"(){}"
   set theObjectDelimitedByPunctuation to thisObject's componentsSeparatedByCharactersInSet:theCharacterSet
   set theMutableObjectDelimitedByPunctuation to current application's NSMutableArray's arrayWithArray:theObjectDelimitedByPunctuation
   theMutableObjectDelimitedByPunctuation's removeObjectIdenticalTo:""
   theMutableObjectDelimitedByPunctuation's removeObjectIdenticalTo:","
   set thePlaceholderTC to theMutableObjectDelimitedByPunctuation's firstObject()
   return thePlaceholderTC
end getPlaceholderTC

on convertFraction(theFraction, newDenominator)
   set theFraction to current application's NSString's stringWithString:theFraction
   set theCharacterSet to current application's NSCharacterSet's characterSetWithCharactersInString:"/"
   set theNumeratorAndDenominatorArray to theFraction's componentsSeparatedByCharactersInSet:theCharacterSet
   set theNumerator to theNumeratorAndDenominatorArray's firstObject()
   set theDenominator to theNumeratorAndDenominatorArray's lastObject()
   set newNumber to ((((theNumerator as text) as integer) / ((theDenominator as text) as integer)) * newDenominator)
   set theFormatter to current application's NSNumberFormatter's new()
   theFormatter's setNumberStyle:(current application's NSNumberFormatterNoStyle)
   set theResult to theFormatter's stringFromNumber:newNumber
   set theResult to current application's NSString's stringWithFormat_("%@/%@", theResult, newDenominator)
   return theResult
end convertFraction

############################ Write the modifed FCPX element back to the clipboard & paste it onto the timeline ############################

on writeElementWithModifiedTCtoClipboardAndPasteIntoFCPX(theElementWithModifiedTC, theUnmodifiedffpasteboardobject)
   set |$versionValue| to theUnmodifiedffpasteboardobject's valueForKey:"$version"
   set |$archiverValue| to theUnmodifiedffpasteboardobject's valueForKey:"$archiver"
   set |$topValue| to theUnmodifiedffpasteboardobject's valueForKey:"$top"
   set newSubRecord to {|$version|:|$versionValue|, |$objects|:theElementWithModifiedTC, |$archiver|:|$archiverValue|, |$top|:|$topValue|}
   set newSubRecordObjC to current application's NSDictionary's dictionaryWithDictionary:newSubRecord
   set {theData, theError} to current application's NSPropertyListSerialization's dataWithPropertyList:newSubRecordObjC format:(current application's NSPropertyListBinaryFormat_v1_0) options:0 |error|:(reference)
   if theData = missing value then
       error (theError's localizedDescription() as text)
   end if
   set newRecord to {ffpasteboardcopiedtypes:{pb_anchoredObject:{|count|:1}}} & {ffpasteboardobject:theData} & {kffmodelobjectIDs:{}}
   set OBJcDict to current application's NSDictionary's dictionaryWithDictionary:newRecord
   set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
   theNSPasteboard's clearContents()
   theNSPasteboard's setPropertyList:OBJcDict forType:"com.apple.flexo.proFFPasteboardUTI"
   theNSPasteboard's writeObjects:OBJcDict
   tell application "Final Cut Pro"
       activate
   end tell
   tell application "System Events"
       key code 19 using command down
       delay 0.1
       keystroke "v" using command down
       delay 0.5
   end tell
   
   
end writeElementWithModifiedTCtoClipboardAndPasteIntoFCPX

Last edited by tneison (2015-06-24 10:59:22 pm)

Offline

 

#2 2017-08-08 08:56:52 pm

Chris Hocking
Member
From:: Australia
Registered: 2016-09-17
Posts: 9
Website

Re: A script for adding hotkeys to Final Cut Pro X

AMAZING. Thank you for sharing!

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)