Skip to content

Instantly share code, notes, and snippets.

@in03
Last active January 27, 2024 19:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save in03/0a6399311dbeea32c89dce49454b42be to your computer and use it in GitHub Desktop.
Save in03/0a6399311dbeea32c89dce49454b42be to your computer and use it in GitHub Desktop.
DaVinci Resolve AutoHotkey scripts - not exhaustive! Examples for others.
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn, All
#Warn, Unreachable, Off ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%\ResolveHelper ; Ensures a consistent starting directory.
SetDefaultMouseSpeed 1
#SingleInstance, Force
#InstallMouseHook
#InstallKeybdHook
#If WinActive("ahk_exe Resolve.exe")
I_Icon = %A_ScriptDir%\Icons\resolve2.ico
Menu, Tray, Icon, %I_Icon%
; -----INCLUDES-----
#Include <Python>
#Include <Acc>
#Include <Resolve>
#Include <JSON>
; ------------------
; Run hidden background shell, forward commands
dllcall("allocconsole")
winhide % "ahk_id " dllcall("getconsolewindow", "ptr")
; -----GLOBALS---------------------------------
Process, Priority, , R ; Set script's priority to Realtime
SysGet, SM_CXDOUBLECLK, 36
SysGet, SM_CYDOUBLECLK, 37
Global SM_CXDOUBLECLK = SM_CXDOUBLECLK
Global SM_CYDOUBLECLK = SM_CYDOUBLECLK
Global DoubleClickTime := DllCall("User32\GetDoubleClickTime")
Global InspectorXOffset = A_ScreenWidth - 500
Global PriorX = False
Global PriorY = False
Global ClipColourCount = 0
Dissolve_Count = 0
Temp_Count = 0
; ---------------------------------------------
; Force run as admin
; Needed for BlockInput
;if not (RegExMatch(DllCall("GetCommandLine", "str"), " /restart(?!\S)")) {
; try Run *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%",, UseErrorLevel
; ExitApp
;}
; Wait for Resolve to be open and to retrieve version before loading other scripts
;GetResolveVersion:
;ver := Python.Run("get_resolve_version.py")
;While (InStr(ver, "Exception"))
;{
; Sleep, 5000
;}
;if (ver != 17){
; MsgBox, This instance of Resolve Helper does not support version %ver% of Resolve.
;}
;Global ResolveVersion = ver
; ###################################
; RESOLVE HELPER READY ;
; ###################################
RandomSound("Positive/Magic*")
TempToolTip("Resolve Helper is ready!", 2000)
; RESOLVE SCRIPTS
; Double middle mouse click reset viewer zoom---------------------------
~MButton::
MouseGetPos, X, Y
If (A_ThisHotkey = A_PriorHotkey)
{
OutputDebug, [%A_ScriptName%] Time since previous left click: %A_TimeSincePriorHotkey% ms
If (A_TimeSincePriorHotkey <= DoubleClickTime
&& Abs(X - PriorX) <= SM_CXDOUBLECLK
&& Abs(Y - PriorY) <= SM_CYDOUBLECLK)
{
Send, k ; Pause if playing
Send, ^4 ; Give timeline viewer focus
Send, ^+= ; Reset viewer zoom
}
}
PriorX := X
PriorY := Y
Return
;---------------------------------------------------------------------------------------------------------------------------
; MANUAL BACKUP RESOLVE
^!+NumpadAdd::
{
Python.Run("export_current_project.py")
Return
}
^+NumpadAdd::
{
Python.Run("new_timeline_revision.py")
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; GET CLIP LIST
F23::
{
Python.Run("save_proxy_clip_list.py")
Return
}
; LINK PROXIES
F24::
{
Python.Run("link_proxies.py")
Return
}
; QUICK DEFAULT RIGHT CLICK MENU
; If right trim edit tool, add 24 frame dissolve right
; If left trim edit tool, add 24 frame dissolve left
; If slide edit tool, delete through edit if through.
; If not, add 24 frame dissolve centred
!MButton::
{
If (A_Cursor != "Unknown") ; If not standard cursor, assume edit cursor above edit
Return
Click, Right
Sleep 100
Send, {Down 4}
Send, {Enter}
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; Click and hold position param for syncing
~[::
{
BlockInput, MouseMove
MouseGetPos, Xxx, Yyy
Keyframe("PositionX", "Hover")
BlockInput, MouseMoveOff
If ErrorLevel {
MouseMove, %Xxx%, %Yyy%
Return
}
Send, {Alt Down}
Click Down
KeyWait, [, L
Send {Alt Up}
Click up
Sleep, 50
MouseMove, %Xxx%, %Yyy%
Return
}
~]::
{
BlockInput, MouseMove
MouseGetPos, Xxxx, Yyyy
Keyframe("PositionY", "Hover")
BlockInput, MouseMoveOff
If ErrorLevel {
MouseMove, %Xxx%, %Yyy%
Return
}
Send, {Alt Down}
Click Down
KeyWait, ], L
Send {Alt Up}
Click up
Sleep, 50
MouseMove, %Xxxx%, %Yyyy%
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; TRANSITION ALIGNMENT
F17::
{
TransitionAlign("Left")
Send, d
TempToolTip("Align Left")
Sleep, 300
SwitchInspectorTab("Video")
Return
}
F19::
{
TransitionAlign("Right")
Send, d
TempToolTip("Align Right")
Sleep, 300
SwitchInspectorTab("Video")
Return
}
F18::
{
TransitionAlign("Center")
Send, d
TempToolTip("Align Center")
Sleep, 300
SwitchInspectorTab("Video")
Return
}
;------------------------------------------------------------------------------------------------------------------------------
; Quickly bring up speed change menu
^+c::
{
MouseGetPos, xpos, ypos
TempToolTip("Add Speed Point", 800)
BlockInput, MouseMove
Send, d
;------------------------------------------------------------------------------------------------------------------------
; -------------------------------- CLICK CLOSEST SPEED TRIANGLE ---------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------
;SpeedTriangle1 = *20 %A_WorkingDir%\ImageSearch\Timeline\SpeedTriangleAltered.bmp
SpeedTriangle1 = *TransN0x4376A1 *50 %A_WorkingDir%\ImageSearch\Timeline\SpeedTriangle.bmp
SpeedTriangle2 = *TransN0x4376A1 *50 %A_WorkingDir%\ImageSearch\Timeline\SpeedTriangleAlteredSelected.bmp
MouseToClosest(SpeedTriangle1)
If ErrorLevel = 1
{
MouseToClosest(SpeedTriangle2)
If ErrorLevel = 1
{
RandomSound("\Negative\*")
Goto End
}
}
MouseMove, 3, 1, , R
Click
Sleep, 50
MouseGetPos, MouseX, MouseY
MouseMove, 0, 0
Sleep, 200
; Verify that menu items are visible/clickable
ImageSearch, X, Y, MouseX -= 20, MouseY -= 20, MouseX += 200, MouseY += 200, *20 %A_WorkingDir%\ImageSearch\Timeline\Point.bmp
If ErrorLevel = 2
{
MsgBox, Missing AddSpeed.bmp
Goto End
}
Else If ErrorLevel = 1
{
RandomSound("\Negative\*")
Goto End
}
MouseMove, %MouseX%, %MouseY%
;------------------------------------------------------------------------------------------------------------------------
; -------------------------------- ADD EASE TO LAST MADE KEYFRAME -------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------
Send, {Down}
Send, {Enter}
Send, {Left 10}
Sleep, 200
; Verify that retime curve is open
ImageSearch, X, Y, 0, 880, A_screenWidth, A_ScreenHeight, *20 %A_WorkingDir%\ImageSearch\Timeline\RetimeFrame.bmp
If ErrorLevel = 2
{
MsgBox, Missing RetimeFrame.bmp
Goto End
}
; Open if not
Else If ErrorLevel = 1
Goto End
; Select closest keyframe
keyframe = *20 %A_WorkingDir%\ImageSearch\CurveEditor\keyframe.bmp
MousetoClosest(keyframe)
if !(ErrorLevel)
{
Click
Sleep, 75
; Select closest ease button on "active" selected clip
SelectedEase = *20 %A_WorkingDir%\ImageSearch\CurveEditor\SelectedEase.bmp
MousetoClosest(SelectedEase)
if !(ErrorLevel)
{
MouseMove, 8, 12, , R
Click
}
}
End:
{
MouseMove, %xpos%, %ypos%
BlockInput, MouseMoveOff
Return
}
}
^+Space::
{
;------------------------------------------------------------------------------------------------------------------------
; -------------------------------- CLICK CLOSEST SPEED TRIANGLE ---------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------
; BlockInput, MouseMove
MouseGetPos, xpos, ypos
Send, d
SpeedTriangle1 = *TransN0x4376A1 *30 %A_WorkingDir%\ImageSearch\Timeline\SpeedTriangleCompromised.bmp
SpeedTriangle2 = *TransN0x4376A1 *30 %A_WorkingDir%\ImageSearch\Timeline\SpeedTriangleAlteredSelected.bmp
MouseToClosest(SpeedTriangle1)
If ErrorLevel = 1
{
MouseToClosest(SpeedTriangle2)
If ErrorLevel = 1
{
BlockInput, MouseMoveOff
RandomSound("\Negative\*")
Return
}
}
; Click Triangle
MouseMove, 3, 1, , R
Click
Send, b
MouseGetPos, MouseX, MouseY
Sleep, 200
; Verify that menu items are visible/clickable
ImageSearch, X, Y, MouseX -= 20, MouseY -= 20, MouseX += 200, MouseY += 200, *20 %A_WorkingDir%\ImageSearch\Timeline\Point.bmp
If ErrorLevel = 2
{
MsgBox, Missing AddSpeed.bmp
Exit
}
Else If ErrorLevel = 1
{
RandomSound("\Negative\*")
Exit
}
Send, {Down 3}
Send, {Right}
Send, {Down 5}
MouseGetPos, X, Y
MouseMove, X+=190, Y+=215
BlockInput, MouseMoveOff
Sleep, 100
Keywait, LButton, D, T10
If ErrorLevel = 1
Return
MouseGetPos, X, Y
PixelSearch, PxX, PxY, X, Y, X+=20, Y+=5, 0x1F1F1F
If ErrorLevel = 1
Return
Click
MouseMove,%xpos%, %ypos%
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; Tap tilde to jump to keyframe near mouse in curve editor
; Tap again to toggle its easing
`::
{
; Deselect anything first by clicking grey space
PixelSearch, Px, Py, 0, 0, A_ScreenWidth, A_ScreenHeight, 0x28282E, 3, Fast
if ErrorLevel = 0
{
MouseGetPos, xpos, ypos
Click, %Px%, %Py%
MouseMove, %xpos%, %ypos%
}
; Find Unselected Point
MouseGetPos, xpos, ypos
ImageSearch, x1, y1, % xpos-300, % ypos-300, % xpos+300, % ypos+300, *25 %A_WorkingDir%\ImageSearch\CurveEditor\keyframe.bmp
if ErrorLevel = 2
MsgBox, Reference image missing
; Found, click point
else if ErrorLevel = 0
{
Click, %x1%, %y1%
MouseMove, %xpos%, %ypos%
Return
}
; Not found...
else
{
; Find ease button
ImageSearch, x2, y2, % xpos-1000, % ypos-1000, % xpos+1000, % ypos+1000, *25 %A_WorkingDir%\ImageSearch\CurveEditor\ease.bmp
if ErrorLevel = 2
MsgBox, Reference image missing
else if ErrorLevel = 1
{
; If not found, assume selected. Find linear instead.
ImageSearch, x2, y2, % xpos-1000, % ypos-1000, % xpos+1000, % ypos+1000, *25 %A_WorkingDir%\ImageSearch\CurveEditor\linear.bmp
if ErrorLevel = 2
MsgBox, Reference image missing
; This code should only ever run if curve editor in not visible.
else if ErrorLevel = 1
RandomSound("\Negative\*")
}
Click, %x2%, %y2%
MouseMove, %xpos%, %ypos%
}
Return
}
;---------------------------------------------------------------------------------------------------------------------------
~!s:: ; Add default transition at playhead if on Edit Page, otherwise assume color page and send default key combo for serial node.
{
If (GetKeyState("LButton"))
{
Send, s
Return
}
If (Resolve.Page("Edit") != True)
{
Send, s
Return
}
send, v ; Just in case we're in trim edit mode (doesn't centre dissolve)
sleep, 10
send, x ; Cut at playhead
sleep, 10
send, ^!{up} ; Select edit
sleep, 10
send, ^t ; Add default video transition
Send, ^+a ; Deselect edit (really, deselect all)
Send, d
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; Quickly Cycle Small Selection of Clip Colours
^NumpadMult::
{
Label_1:
hotkey, ^NumpadMult, Label_2
Send ^+{F1}
return
Label_2:
hotkey, ^NumpadMult, Label_3
Send ^!+{F1}
return
Label_3:
hotkey, ^NumpadMult, Label_4
Send ^!+{F8}
return
Label_4:
hotkey, ^NumpadMult, Label_1
Send ^!+{F6}
return
}
;---------------------------------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------------------------------
; Insert freeze frame
^Numpad0::
{
SetTitleMatchMode, 2
Duration = 2 ; In seconds
TempTooltip("Checking if optimized", 4000)
; Proxy := Python.Get("GetClipProperties.py", "Proxy")
; If (Proxy = "None")
; {
; MsgBox, 4, , This clip has no proxy media. Continuing may cause the script to execute improperly. Recommend optimizing first. Continue?
; IfMsgBox, No
; Return
; }
If (!WinActive("Resolve"))
WinActivate, Resolve
TempTooltip("Creating stop point", 4000)
Send, ^2
Send, d ; Select clip beneath playhead
Sleep, 50
Send, x ; Add edit at playhead
Sleep, 50
Send, i ; Mark in point
Sleep, 200
Loop, %Duration%
{
Send, +{Right} ; Playhead forward one frame
}
Send, o ; Mark out point
Sleep, 50
Send, d
Sleep, 50
Send, ^c ; Copy selected clip
Sleep, 50
Loop, %Duration%
{
Send, +{Left} ; Playhead backward two seconds
}
Sleep, 200
Send, ^+v ; Ripple paste
Send, d
Send, {Up} ; Jump to previous edit
Sleep, 200
Send, d
Sleep, 200
Send, ^+{f1} ; Change clip colour to PINK
Send, ^r
WinWait, Edit Speed Change, , 4000
If ErrorLevel
MsgBox, % "Change Speed Duration window never appeared"
Sleep, 100
ImageSearch, X, Y, 0, 0, %A_ScreenWidth%, %A_ScreenHeight%, ImageSearch\Timeline\SpeedDuration\FreezeFrame.png
If ErrorLevel = 2
{
MsgBox, Can't find FreezeFrame.png
Exit
}
Else If ErrorLevel = 1
{
TempTooltip("Couldn't find freeze frame checkbox", 4000)
Exit
}
Click, %X%, %Y%
Sleep, 200
Send, {Enter}
Sleep, 500
If WinExist("Message")
{
Send, {Tab 2}
Sleep, 100
Send, {Enter}
If (!WinActive("Resolve"))
WinActivate, Resolve
}
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; Reverse Selected Clip
!r::
{
; BlockInput, MouseMove
MouseGetPos, MouseX, MouseY
; MouseMove, 0, 0
Sendlevel, 1
Send, ^r
Count=0
Sleep 150
ReverseSpeedSearch:
ImageSearch, X, Y, 0, 0, %A_ScreenWidth%, %A_ScreenHeight%, *20 %A_WorkingDir%\ImageSearch\Timeline\SpeedDuration\ReverseSpeed.png
If ErrorLevel = 2
{
MsgBox, Missing ReverseSpeed.png
Return
}
Else If ErrorLevel = 1
{
; Account for slower computers
Count++
If Count<10
Goto ReverseSpeedSearch
Send, {Escape}
RandomSound("\Negative\*")
TempTooltip("Couldn't find checkbox", 2000)
}
Else
{
Click, %X%, %Y%
Sleep, 50
Send, {Enter}
}
; MouseMove, %MouseX%, %MouseY%
; BlockInput, MouseMoveOff
Return
}
; Quick Clip Translate Hotkeys
; Not an ideal method! Involves resetting values, thus changing curves focus and then undoing change.
F1:: SummonCurve("Zoom")
F2:: SummonCurve("Position")
F3:: SummonCurve("RotationAngle")
F5:: Keyframe("Zoom", "ToggleDiamond")
^+1:: SwitchInspectorTab("Video")
^+2:: SwitchInspectorTab("Audio")
^+3:: SwitchInspectorTab("Effects")
^+4:: SwitchInspectorTab("Transition")
^+5:: SwitchInspectorTab("Image")
^+6:: SwitchInspectorTab("File")
;---------------------------------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------------------------------
; Get Clip Properties
F8::
{
Data := Python.Run("GetClipProperties.py")
MsgBox, % Data
If (Data.Proxy == "960x540")
{
MsgBox, Yes proxy!.
}
Return
}
;---------------------------------------------------------------------------------------------------------------------------
; Summon speed interface
F4::
^+r::
{
Send, ^9
Send, ^2 ; TIMELINE MUST BE ACTIVE OR ^!+r WILL NOT WORK
;Send,d
; Is SpeedBar open?
ImageSearch, X, Y, 0, 800, %A_ScreenWidth%, %A_ScreenHeight%, *20 %A_WorkingDir%\ImageSearch\Timeline\SpeedPointBar.bmp
If ErrorLevel = 2
{
MsgBox, Missing SpeedPointBar.bmp
Return
}
Else If ErrorLevel = 1
{
SpeedBar = Closed
; TempTooltip("SpeedBar is closed", 1000)
}
Else
{
SpeedBar = Open
; TempTooltip("SpeedBar is open", 1000)
}
; Sleep, 1000
; Is SpeedCurve Open?
ImageSearch, X, Y, 0, 800, %A_ScreenWidth%, %A_ScreenHeight%, *10 %A_WorkingDir%\ImageSearch\Timeline\CurveMenu4.bmp
If (ErrorLevel = 2)
{
MsgBox, Missing CurveMenu4.bmp
Return
}
Else If (ErrorLevel = 1)
{
SpeedCurve = Closed
;TempTooltip("SpeedCurve is closed", 1000)
}
Else
{
SpeedCurve = Open
;TempTooltip("SpeedCurve is open", 1000)
}
; Sleep, 1000
If SpeedBar = Closed
{
If SpeedCurve = Closed
{
TempToolTip("Opening both")
Send, ^!/
Send, ^!+r
}
Else
{
TempToolTip("Closing curve")
Send, ^+s
}
}
Else
{
If SpeedCurve = Closed
{
TempToolTip("Opening curve")
Send ^!+r
}
Else
{
TempToolTip("Closing Both")
Send, ^!/
Send, ^+s
}
}
Return
}
^Numpad6::
{
MouseGetPos, X, Y
ToolTip, Loading..., %X%, %Y%
Python.Run("prep_last_render.py")
ToolTip
Return
}
;---------------------------------------------------------------------------------------------------------------------------
ChangeKeyValue(Parameter, Value)
{
Send, ^9
Sleep, 50
MouseGetPos, Mmx, Mmy
ImageSearch, TransformX, TransformY, %InspectorXOffset%, 0, A_ScreenWidth, A_ScreenHeight, *20 %A_WorkingDir%\ImageSearch\Inspector\TransformSwitch.bmp
If ErrorLevel = 2
{
MsgBox, MISSING `n "/ImageSearch/Inspector/ZoomX.bmp"
BlockInput, MouseMoveOff
Return false
}
Else If ErrorLevel = 1
{
TempToolTip("Couldn't change keyframe value: " . Parameter . "," . Value, 4000)
BlockInput, MouseMoveOff
Return true
}
DefaultXOffset = 185
If (Parameter = "Position")
Yy = 80
Else If (Parameter = "Zoom")
Yy = 49
Else If (Parameter = "Rotation")
Yy = 112
Else
{
MsgBox, Unsupported parameter %Parameter% in ChangeKeyValue
Return false
}
MouseMove, TransformX += DefaultXOffset, TransformY += Yy
Click
Sleep, % (DoubleClickTime - 50)
Click
Sleep, 20
Send, % Value
Sleep, 200
Send, {Enter}
MouseMove, %Mmx%, %Mmy%
Sleep, 200
Return true
}
check_proxies()
{
; Check proxies are actually turned on for viewing
ImageSearch, Xx, Yy, 0, 0, 1000, 150, *20 %A_WorkingDir%\ImageSearch\ToolBar\PlaybackMenu.png
If ErrorLevel = 0
{
Click, %Xx%, %Yy%
MenuItem := "UseProxyChecked.png"
Attempts = 0
CheckUseProxies:
ImageSearch, Xx, Yy, 0, 0, 1000, 150, *20 %A_WorkingDir%\ImageSearch\ToolBar\PlaybackMenu\UseProxyUnchecked.png
If ErrorLevel = 0
{
Send, {Down 2}
Sleep, 50
Send, {Enter}
TempToolTip("Enabled proxies", 2000)
}
Else If ErrorLevel = 1
{
Send, {Esc}
TempToolTip("Proxies already enabled", 2000)
}
Else
Return 2
}
Else If ErrorLevel = 1
Return 1
Else If ErrorLevel = 2
Return 2
}
from python_get_resolve import GetResolve
import os, sys
import json
import tempfile
import traceback
import tkinter
import tkinter.messagebox
# Get currently open project
args = None
resolve = GetResolve()
projectManager = resolve.GetProjectManager()
project = projectManager.GetCurrentProject()
try:
# Get current timeline. If no current timeline try to load it from timeline list
timeline = project.GetCurrentTimeline()
clip_properties = timeline.GetCurrentVideoItem().GetMediaPoolItem().GetClipProperty()
printable = json.dumps(clip_properties, indent=4)
print(clip_properties)
except Exception as e:
tb = traceback.format_exc()
print(tb)
tkinter.messagebox.showinfo("ERROR", tb)
print("ERROR - " + str(e))
sys.exit(1)
#!/usr/bin/python3.6
# This script works by analysing Resolve's ingested media, taking the most popular parent directory
# and splitting that path into tokens to match against the name of the project using fuzzy matching
from fuzzywuzzy import fuzz
from win10toast import ToastNotifier
import collections
import os
import string
import tkinter
import tkinter.messagebox
import traceback
from colorama import Fore
from python_get_resolve import GetResolve
debug = False
# Get currently open project
resolve = GetResolve()
projectManager = resolve.GetProjectManager()
project = projectManager.GetCurrentProject()
mediaPool = project.GetMediaPool()
toaster = ToastNotifier()
clip_file_paths = []
def toast(message, threaded=True):
"""Show toast notification"""
toaster.show_toast(
"Get Project Directory",
message,
threaded=threaded,
)
def get_clip_paths(folder, amount=10, debug=False):
"""Recursively get clip file paths.
'clip_file_paths' list must exist outside function to recurse
"""
if not folder:
raise Exception("Couldn't get root folder from Media Pool.")
clips = folder.GetClipList()
if clips:
for i, clip in enumerate(clips):
if debug: print(clip)
# Don't grab all the clips from every folder.
# It'll take too long. Grab a sample
if i == amount:
break
if debug: print(f"folder: {folder.GetName()}, clip: {clip.GetName()}")
clip_file_path = clip.GetClipProperty("File Path")
if not clip_file_path:
if debug: print(f"{clip.GetName()} couldn't get path.")
break
clip_file_path = os.path.split(clip_file_path)[0]
if os.path.exists(clip_file_path):
clip_file_paths.append(clip_file_path)
folders = folder.GetSubFolderList()
for folder in folders:
if debug: print(f"Found folder: {folder.GetName()}")
get_clip_paths(folder)
return clip_file_paths
def get_safe_project_name():
"""Remove any punctuation from the returned project name
to prevent any weird path splitting. TODO: Investigate this. This may actually cause some issues.
"""
project_name = project.GetName()
if not project_name:
raise Exception("Couldn't get project name")
project_name_alphanum = project_name.translate(str.maketrans('', '', string.punctuation))
project_name_alphanum = " ".join(project_name_alphanum.split())
return project_name_alphanum
def get_project_dir(debug=True):
"""Get the project directory by splittling clip file paths and
fuzzy matching
"""
clip_file_paths = get_clip_paths(
mediaPool.GetRootFolder(),
amount = 10,
)
if len(clip_file_paths) < 1:
message = "No clips imported into project! Clips are needed to match directory."
print(f"{Fore.RED}{message}")
toast(message)
path_count = collections.Counter(clip_file_paths)
pop_path = max(path_count, key = path_count.get)
pop_path_tokens = pop_path.split(os.path.sep)
project_name = get_safe_project_name()
comp = []
for index, subfolder in enumerate(pop_path_tokens, start=1):
ratio = fuzz.ratio(project_name, subfolder)
comp.append({'name': subfolder, 'values': [index, ratio]})
if debug: print(f"{Fore.MAGENTA}Candidate: {[x for x in comp]}")
winner = sorted(comp, key=lambda dct: dct['values'][1]).pop()
winning_path = "\\".join(pop_path_tokens[:winner['values'][0]])
if debug: print(f"{Fore.GREEN}Winning path: {winning_path}")
return winning_path
if __name__ == "__main__":
try:
project_dir = get_project_dir(debug=False)
if debug: toast(project_dir, threaded=True)
print(project_dir)
except Exception as e:
tb = traceback.format_exc()
tkinter.messagebox.showinfo("ERROR", tb)
print("ERROR - " + str(e))
#Include <Python>
GetProjectDirectory()
{
if WinExist("ahk_exe Resolve.exe"){
Return Python.Run("get_project_dir.py")
} else if WinExist("Premiere"){
MsgBox, Noot
Return FromTitle()
}
}
FromTitle()
{
SetTitleMatchMode, 2
SetTitleMatchMode, Fast
WinGetTitle, TitleName, Premiere
; Remove Program Name
FilePath := []
FilePath := StrSplit(TitleName, "-", A_Space)
FilePath.remove(1)
Filepath := Stringify(FilePath)
; Remove .prpoj and Save File Dir
ProjPath := []
ProjPath := StrSplit(FilePath, "\")
If InStr(FilePath, "Save")
ProjPath.Pop()
ProjPath.Pop()
ProjPath := Stringify(ProjPath, "\")
Return ProjPath
}
Stringify(Array, Join:="")
{
Str := ""
For Index, Value In Array
Str .= Value . Join
Return Str
}
/**
* Lib: JSON.ahk
* JSON lib for AutoHotkey.
* Version:
* v2.1.3 [updated 04/18/2016 (MM/DD/YYYY)]
* License:
* WTFPL [http://wtfpl.net/]
* Requirements:
* Latest version of AutoHotkey (v1.1+ or v2.0-a+)
* Installation:
* Use #Include JSON.ahk or copy into a function library folder and then
* use #Include <JSON>
* Links:
* GitHub: - https://github.com/cocobelgica/AutoHotkey-JSON
* Forum Topic - http://goo.gl/r0zI8t
* Email: - cocobelgica <at> gmail <dot> com
*/
/**
* Class: JSON
* The JSON object contains methods for parsing JSON and converting values
* to JSON. Callable - NO; Instantiable - YES; Subclassable - YES;
* Nestable(via #Include) - NO.
* Methods:
* Load() - see relevant documentation before method definition header
* Dump() - see relevant documentation before method definition header
*/
class JSON
{
/**
* Method: Load
* Parses a JSON string into an AHK value
* Syntax:
* value := JSON.Load( text [, reviver ] )
* Parameter(s):
* value [retval] - parsed value
* text [in, ByRef] - JSON formatted string
* reviver [in, opt] - function object, similar to JavaScript's
* JSON.parse() 'reviver' parameter
*/
class Load extends JSON.Functor
{
Call(self, ByRef text, reviver:="")
{
this.rev := IsObject(reviver) ? reviver : false
; Object keys(and array indices) are temporarily stored in arrays so that
; we can enumerate them in the order they appear in the document/text instead
; of alphabetically. Skip if no reviver function is specified.
this.keys := this.rev ? {} : false
static quot := Chr(34), bashq := "\" . quot
, json_value := quot . "{[01234567890-tfn"
, json_value_or_array_closing := quot . "{[]01234567890-tfn"
, object_key_or_object_closing := quot . "}"
key := ""
is_key := false
root := {}
stack := [root]
next := json_value
pos := 0
while ((ch := SubStr(text, ++pos, 1)) != "") {
if InStr(" `t`r`n", ch)
continue
if !InStr(next, ch, 1)
this.ParseError(next, text, pos)
holder := stack[1]
is_array := holder.IsArray
if InStr(",:", ch) {
next := (is_key := !is_array && ch == ",") ? quot : json_value
} else if InStr("}]", ch) {
ObjRemoveAt(stack, 1)
next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"
} else {
if InStr("{[", ch) {
; Check if Array() is overridden and if its return value has
; the 'IsArray' property. If so, Array() will be called normally,
; otherwise, use a custom base object for arrays
static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
; sacrifice readability for minor(actually negligible) performance gain
(ch == "{")
? ( is_key := true
, value := {}
, next := object_key_or_object_closing )
; ch == "["
: ( value := json_array ? new json_array : []
, next := json_value_or_array_closing )
ObjInsertAt(stack, 1, value)
if (this.keys)
this.keys[value] := []
} else {
if (ch == quot) {
i := pos
while (i := InStr(text, quot,, i+1)) {
value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")
static tail := A_AhkVersion<"2" ? 0 : -1
if (SubStr(value, tail) != "\")
break
}
if (!i)
this.ParseError("'", text, pos)
value := StrReplace(value, "\/", "/")
, value := StrReplace(value, bashq, quot)
, value := StrReplace(value, "\b", "`b")
, value := StrReplace(value, "\f", "`f")
, value := StrReplace(value, "\n", "`n")
, value := StrReplace(value, "\r", "`r")
, value := StrReplace(value, "\t", "`t")
pos := i ; update pos
i := 0
while (i := InStr(value, "\",, i+1)) {
if !(SubStr(value, i+1, 1) == "u")
this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))
uffff := Abs("0x" . SubStr(value, i+2, 4))
if (A_IsUnicode || uffff < 0x100)
value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
}
if (is_key) {
key := value, next := ":"
continue
}
} else {
value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)
static number := "number", integer :="integer"
if value is %number%
{
if value is %integer%
value += 0
}
else if (value == "true" || value == "false")
value := %value% + 0
else if (value == "null")
value := ""
else
; we can do more here to pinpoint the actual culprit
; but that's just too much extra work.
this.ParseError(next, text, pos, i)
pos += i-1
}
next := holder==root ? "" : is_array ? ",]" : ",}"
} ; If InStr("{[", ch) { ... } else
is_array? key := ObjPush(holder, value) : holder[key] := value
if (this.keys && this.keys.HasKey(holder))
this.keys[holder].Push(key)
}
} ; while ( ... )
return this.rev ? this.Walk(root, "") : root[""]
}
ParseError(expect, ByRef text, pos, len:=1)
{
static quot := Chr(34), qurly := quot . "}"
line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}"
, (expect == "") ? "Extra data"
: (expect == "'") ? "Unterminated string starting at"
: (expect == "\") ? "Invalid \escape"
: (expect == ":") ? "Expecting ':' delimiter"
: (expect == quot) ? "Expecting object key enclosed in double quotes"
: (expect == qurly) ? "Expecting object key enclosed in double quotes or object closing '}'"
: (expect == ",}") ? "Expecting ',' delimiter or object closing '}'"
: (expect == ",]") ? "Expecting ',' delimiter or array closing ']'"
: InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
: "Expecting JSON value(string, number, true, false, null, object or array)"
, line, col, pos)
static offset := A_AhkVersion<"2" ? -3 : -4
throw Exception(msg, offset, SubStr(text, pos, len))
}
Walk(holder, key)
{
value := holder[key]
if IsObject(value) {
for i, k in this.keys[value] {
; check if ObjHasKey(value, k) ??
v := this.Walk(value, k)
if (v != JSON.Undefined)
value[k] := v
else
ObjDelete(value, k)
}
}
return this.rev.Call(holder, key, value)
}
}
/**
* Method: Dump
* Converts an AHK value into a JSON string
* Syntax:
* str := JSON.Dump( value [, replacer, space ] )
* Parameter(s):
* str [retval] - JSON representation of an AHK value
* value [in] - any value(object, string, number)
* replacer [in, opt] - function object, similar to JavaScript's
* JSON.stringify() 'replacer' parameter
* space [in, opt] - similar to JavaScript's JSON.stringify()
* 'space' parameter
*/
class Dump extends JSON.Functor
{
Call(self, value, replacer:="", space:="")
{
this.rep := IsObject(replacer) ? replacer : ""
this.gap := ""
if (space) {
static integer := "integer"
if space is %integer%
Loop, % ((n := Abs(space))>10 ? 10 : n)
this.gap .= " "
else
this.gap := SubStr(space, 1, 10)
this.indent := "`n"
}
return this.Str({"": value}, "")
}
Str(holder, key)
{
value := holder[key]
if (this.rep)
value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : JSON.Undefined)
if IsObject(value) {
; Check object type, skip serialization for other object types such as
; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
static type := A_AhkVersion<"2" ? "" : Func("Type")
if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
if (this.gap) {
stepback := this.indent
this.indent .= this.gap
}
is_array := value.IsArray
; Array() is not overridden, rollback to old method of
; identifying array-like objects. Due to the use of a for-loop
; sparse arrays such as '[1,,3]' are detected as objects({}).
if (!is_array) {
for i in value
is_array := i == A_Index
until !is_array
}
str := ""
if (is_array) {
Loop, % value.Length() {
if (this.gap)
str .= this.indent
v := this.Str(value, A_Index)
str .= (v != "") ? v . "," : "null,"
}
} else {
colon := this.gap ? ": " : ":"
for k in value {
v := this.Str(value, k)
if (v != "") {
if (this.gap)
str .= this.indent
str .= this.Quote(k) . colon . v . ","
}
}
}
if (str != "") {
str := RTrim(str, ",")
if (this.gap)
str .= stepback
}
if (this.gap)
this.indent := stepback
return is_array ? "[" . str . "]" : "{" . str . "}"
}
} else ; is_number ? value : "value"
return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
}
Quote(string)
{
static quot := Chr(34), bashq := "\" . quot
if (string != "") {
string := StrReplace(string, "\", "\\")
; , string := StrReplace(string, "/", "\/") ; optional in ECMAScript
, string := StrReplace(string, quot, bashq)
, string := StrReplace(string, "`b", "\b")
, string := StrReplace(string, "`f", "\f")
, string := StrReplace(string, "`n", "\n")
, string := StrReplace(string, "`r", "\r")
, string := StrReplace(string, "`t", "\t")
static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
while RegExMatch(string, rx_escapable, m)
string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
}
return quot . string . quot
}
}
/**
* Property: Undefined
* Proxy for 'undefined' type
* Syntax:
* undefined := JSON.Undefined
* Remarks:
* For use with reviver and replacer functions since AutoHotkey does not
* have an 'undefined' type. Returning blank("") or 0 won't work since these
* can't be distnguished from actual JSON values. This leaves us with objects.
* Replacer() - the caller may return a non-serializable AHK objects such as
* ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to
* mimic the behavior of returning 'undefined' in JavaScript but for the sake
* of code readability and convenience, it's better to do 'return JSON.Undefined'.
* Internally, the property returns a ComObject with the variant type of VT_EMPTY.
*/
Undefined[]
{
get {
static empty := {}, vt_empty := ComObject(0, &empty, 1)
return vt_empty
}
}
class Functor
{
__Call(method, ByRef arg, args*)
{
; When casting to Call(), use a new instance of the "function object"
; so as to avoid directly storing the properties(used across sub-methods)
; into the "function object" itself.
if IsObject(method)
return (new this).Call(method, arg, args*)
else if (method == "")
return (new this).Call(arg, args*)
}
}
}
Keyframe(Type, Mode, Value := 0)
{
ScrollWhereX := 3322 ; Aligned right to avoid scrolling scrollable "Speed Change" wheel that steals panel focus
ScrollWhereY := 180 ; Height of transform panel tab
ZoomFoundOffsetX := 3 ; Horizontal centre of Zoom Text
ZoomFoundOffsetY := 5 ; Vertical centre of Zoom Text
KeyDiamondOffset := 264 ; Offset from Zoom Text to Keyframe toggle diamond
Xx := 205 ; Offset from Zoom Text
If Value is not number
{
MsgBox, % "Unsupported Value parameter in Keyframe function. Must be number: '" . Value . "'"
ErrorLevel = 1
Return
}
; Get Edit field X Offset from axis type
Axis := SubStr(Type, 0)
If (Axis = "X" or Axis = "Y")
{
; Get last character of type
Type:=SubStr(Type,1,StrLen(Type)-1)
; X parameter is 126 pixels to left.
If (Axis = "X")
Xx := 80
}
Switch Type
{
Case "Zoom": Yy = 0
Case "Position": Yy=24
Case "Anchor Point": Yy = 72
Case "RotationAngle": Yy = 48
Case "Pitch": Yy = 96
Case "Yaw": Yy = 120
Default:
{
MsgBox, % "Unsupported Type parameter in Keyframe function: '" . Type . "'"
ErrorLevel = 1
Return
}
}
; Scroll to top of Inspector always
MouseGetPos, LastMouseX, LastMouseY
MouseMove, %ScrollWhereX%, %ScrollWhereY%
Send, {WheelUp 20}
Tries = 0
SearchAgain:
; 50 Shades of variation is important here! Active Inspector parameters have a slight highlight but are too cumbersome to search for independently.
ImageSearch, ZoomX, ZoomY, %InspectorXOffset%, 0, A_ScreenWidth, A_ScreenHeight, *50 %A_WorkingDir%\ImageSearch\Inspector\ZoomX.png
If ErrorLevel = 2
{
MsgBox, % "Zoom.png is missing from ImageSearch folder. Aborting."
Return
}
If ErrorLevel = 1
{
If Tries < 2
{
Tries+=1
SwitchInspectorTab("Video") ; Inspector tab may not be "Video"
Goto SearchAgain
}
If Tries < 4
{
Click, %ScrollWhereX%, %ScrollWhereY%
Sleep, 200
Tries +=1
Goto SearchAgain
}
Else
{
TempTooltip("Keyframe function Couldn't find Zoom text in Inspector", 4000)
Sleep, 2000
ErrorLevel = 1
Return
}
}
ZoomX += ZoomFoundOffsetX
ZoomY += ZoomFoundOffsetY
TempToolTip(Mode)
If (Mode = "Hover")
{
MouseMove, ZoomX += Xx, ZoomY += Yy
Return True
}
Else If (Mode = "ResetValue")
{
If (Value != 0)
{
MsgBox % "Value parameter is not supported for '" . Mode . "' mode in Keyframe function"
ErrorLevel = 1
Return
}
MouseMove, ZoomX, ZoomY += Yy
Click, 2
}
Else If (Mode = "ToggleDiamond")
{
If (Value != 0)
{
MsgBox % "Value parameter is not supported for '" . Mode . "' mode in Keyframe function"
ErrorLevel = 1
Return
}
MouseMove, ZoomX += KeyDiamondOffset, ZoomY += Yy
Click
}
Else If (Mode = "SetValue")
{
MouseMove, ZoomX += Xx, ZoomY += Yy
Click
Sleep, % (DoubleClickTime - 50)
Click
Sleep, 20
Send, % Value
Sleep, 200
Send, {Enter}
Sleep, 200
}
Else
{
MsgBox, % "Unsupported Mode parameter in Keyframe function: '" . Mode . "'"
ErrorLevel = 1
Return
}
Sleep, 30
MouseMove, %LastMouseX%, %LastMouseY%
Return True
}
MouseToClosest(Image)
{
;SoundBeep, 250
; Initial search radius
MouseGetPos, x_mid, y_mid
x1_rad := x_mid - 800
x2_rad := x_mid + 800
y1_rad := y_mid - 300
y2_rad := y_mid + 300
; Max search radius
max_rad = 500
; find one image
ImageSearch, xc, yc, %x1_rad%, %y1_rad%, %x2_rad%, %y2_rad%, %Image%
If ErrorLevel = 2
{
MsgBox, Missing reference file %Image%
Return
}
Else If ErrorLevel = 1
{
;SoundBeep, 275
return
}
; Initial find
dist_min:= sqrt((x_mid - xc)**2 + (y_mid - yc)**2)
x_target:= xc
Y_target:= yc
; Any others to find?
; loop over given radius
; Count := 0
Loop %max_rad%
{
;Count += 1
;ToolTip, %Count%, %x_mid%, %y_mid%
ImageSearch, xc, yc, xc+1, yc, %A_ScreenWidth%, %A_ScreenHeight%, %Image%
If ErrorLevel = 1 ;no more images
{
; ToolTip, No More to find. Looped %Count% times
break
}
; Compare current find to initial
; If better, new closest
If (sqrt((x_mid - xc)**2 + (y_mid - yc)**2) < dist_min)
{
dist_min:= sqrt((x_mid - xc)**2 + (y_mid - yc)**2)
x_target:= xc
y_target:= yc
}
; If worse, settle
Else
{
; ToolTip, Last found was best. Looped %Count% times
break
}
}
MouseMove, %x_target%, %y_target%
ErrorLevel = 0
;SoundBeep, 500
Return
}
; Run hidden background shell, forward commands
DLLCall("AllocConsole")
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")
Class Python
{
Run(PythonScript) {
Res := []
Path = B:\!PipelineAutomation\Python\Resolve Scripts
PythonPath = "%Path%\%PythonScript%"
if !(FileExist(Path . "\" . PythonScript))
{
MsgBox, %PythonPath% does not exist...
Return "error"
}
; MsgBox, %PythonPath%
Shell := ComObjCreate("WScript.Shell").Exec("cmd.exe /k py -3.6 " PythonPath).StdOut.ReadAll()
Res := StrSplit(Shell, "`n")
RawResult := Res[1]
; Trim whitespace (otherwise will return blank results)
Result := RegexReplace(RawResult, "^\s+|\s+$")
If InStr(Result, "error")
{
ErrorLevel = 1
TempTooltip("ResolveAPI: " . Result, 2000)
Return Result
}
Else If (Result="")
{
ErrorLevel = 2
TempTooltip("ResolveAPI: NO RESPONSE", 2000)
Return "null"
}
Return Result
}
Get(PythonScript, MatchKey:="") {
Res := []
Path = B:\!PipelineAutomation\Python\Resolve Scripts\
PythonPath = "%Path%\%PythonScript%"
if !(FileExist(Path . "\" . PythonScript))
{
MsgBox, %PythonPath% does not exist...
Return "error"
}
Shell := ComObjCreate("WScript.Shell").Exec("cmd.exe /k py -3.6 " PythonPath).StdOut.ReadAll()
Res := StrSplit(Shell, "`n")
RawResult := Res[1]
; Trim whitespace (otherwise will return blank results)
Result := RegexReplace(RawResult, "^\s+|\s+$")
If InStr(Result, "ERROR")
{
ErrorLevel = 1
TempTooltip("ResolveAPI: " . Result, 2000)
Return Result
}
Else If !(Result)
{
ErrorLevel = 2
TempTooltip("ResolveAPI: NO RESPONSE", 2000)
Return "null"
}
Result := StrReplace(Result, "'", """")
try {
Result := JSON.Load(Result)
} catch e {
TempTooltip("Couldn't convert to JSON. Sending anyway", 4000)
}
s := ""
if (MatchKey ="") ; If unprovided, return all
{
for k, v in Result
{
s .= k "=" v "`n"
}
Return s
}
for k, v in Result
{
if (k = MatchKey)
{
s := v
break
}
}
if (s = "")
TempTooltip("No matching key: " . MatchKey, 4000)
Return s
}
}
#!/usr/bin/env python3.6
import os
import sys
import time
import tkinter
import tkinter.messagebox
import traceback
from datetime import datetime
from colorama import init, Fore
from pyfiglet import Figlet
from win10toast import ToastNotifier
from python_get_resolve import GetResolve
from get_project_dir import get_project_dir
# Get resolve variables
resolve = GetResolve()
projectManager = resolve.GetProjectManager()
project = projectManager.GetCurrentProject()
project_name = project.GetName()
# Global vars
init(autoreset = True)
debug = True
alternate_backup_path = os.path.join(
"R:\\",
"Resolve Project Backups",
project_name,
"Project",
"Backups",
)
toaster = ToastNotifier()
def exit_in_seconds(seconds=5, level=0):
''' Allow time to read console before exit '''
ansi_colour = Fore.CYAN
if level > 0: ansi_colour = Fore.RED
for i in range(seconds, -1, -1):
sys.stdout.write(f"{ansi_colour}\rExiting in " + str(i))
time.sleep(1)
erase_line = '\x1b[2K'
sys.stdout.write(f"\r{erase_line}")
print()
sys.exit(level)
def toast(message, toast=True, threaded=True):
if toast:
toaster.show_toast("Resolve Project Backup", message, threaded=threaded)
def get_backup_path(project_dir):
''' Check path is valid, exists or use alternate '''
if not project_dir:
message = f"Could not guess project directory. Using alternate: {alternate_backup_path}"
print(f"{Fore.YELLOW} message")
toast(message)
return alternate_backup_path
elif not os.path.exists(project_dir): # Passed path doesn't exist
toast(f"File path '{project_dir}' doesn't exist. Using alternate: {alternate_backup_path}", toast=True)
return alternate_backup_path
else: # Return the path that was sent
passed_path = os.path.normpath(os.path.join(project_dir, "Project", "Backups"))
if not os.path.exists(passed_path):
os.makedirs(passed_path)
return passed_path
def append_file_time(dir_path):
'''Append current time as a UUID to filename'''
time = datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")
file_name = f"{project_name}_{time}"
file_path = os.path.join(dir_path, file_name)
return file_path
if __name__ == "__main__":
try:
f = Figlet()
print(f.renderText("Backup Current Project"))
project_dir = get_project_dir(False)
message = f"Backing up '{project_name}'"
print(f"{Fore.CYAN}{message}")
toast(message, threaded = False)
dir_path = get_backup_path(project_dir)
assert dir_path is not alternate_backup_path
file_path = append_file_time(dir_path)
# Try backup to chosen path
if projectManager.ExportProject(project_name, file_path, withStillsandLuts=True):
message = f"Successfully backed up '{project_name}' to project directory."
print(f"{Fore.GREEN}{message}")
toast(message)
exit_in_seconds()
# Try the alternate path
message = f"Failed to back up '{project_name}' to project directory. Trying alternate directory."
print(f"{Fore.YELLOW}{message}")
toast(message)
if projectManager.ExportProject(project_name, append_file_time(alternate_backup_path), withStillsandLuts=True):
message = f"Successfully backed up '{project_name}' in alternate directory: {alternate_backup_path}."
print(f"{Fore.GREEN}{message}")
toast(message)
exit_in_seconds()
# Out of options.
else:
message = f"Failed to back up '{project_name}' to alternate directory: {alternate_backup_path}."
print(f"{Fore.RED}{message}")
toast(message)
exit_in_seconds(seconds = 10, level = 1)
except Exception as e:
tb = traceback.format_exc()
print(tb)
tkinter.messagebox.showinfo("ERROR", tb)
print(f"{Fore.RED}ERROR - {str(e)}")
exit_in_seconds(seconds = 10, level = 2)
#!/usr/bin/python3.6
import time
import os, sys, platform
import traceback
import tkinter
import tkinter.messagebox
import re
from colorama import init, Fore
from pyfiglet import Figlet
from python_get_resolve import GetResolve
from natsort import natsorted
# Get resolve variables
r = GetResolve()
pm = r.GetProjectManager()
p = pm.GetCurrentProject()
# Global vars
debug=False
init(autoreset = True)
def exit_in_seconds(timeout):
'''Allow time to read console before exit'''
for i in range(timeout, -1, -1):
time.sleep(1)
sys.stdout.write(f"{Fore.RED}\rExiting in " + str(i))
return
def get_next_revision():
'''Get the next version number for current timeline'''
revisions = []
for i in range(1, tc + 1):
x = p.GetTimelineByIndex(i)
xn = x.GetName()
if xn is not None:
if debug: print(f" [x] Found timeline: {xn}")
if re.search(rf"^({ctn})(\s)(V\d+)", xn, re.IGNORECASE):
if debug: print(f" [x] Found revision: {xn}")
revisions.append(xn)
# If none exist, start first
if len(revisions) == 0:
print(f"\n{Fore.YELLOW}No revisions exist.\nStarting first.")
return f"{ctn} V1"
# Get last revision of all
lr = natsorted(revisions).pop()
print(f"{Fore.GREEN}\nLast revision: {lr}")
# Increment revision
nr = re.sub(r"[0-9]+$",
lambda x: f"{str(int(x.group())+1).zfill(len(x.group()))}",
lr)
nr = str(nr)
print(f"{Fore.GREEN}New revision: {nr}")
return nr
if __name__ == "__main__":
f = Figlet()
print(f.renderText("New Timeline Revision"))
try:
print(f"{Fore.GREEN}Getting next timeline revision.")
print()
print(f"{Fore.CYAN}Getting current timeline.")
ct = p.GetCurrentTimeline()
ctn = ct.GetName()
print(f"{Fore.CYAN}Getting total timelines in project.")
tc = p.GetTimelineCount()
print(f"{Fore.CYAN}Comparing timeline revision numbers...")
nrn = get_next_revision()
print()
print(f"{Fore.YELLOW}Revising active timeline...")
ct.SetName(nrn) # Rename original
print(f"{Fore.YELLOW}Duplicating timeline...")
ct.DuplicateTimeline(ctn)
print(f"\n{Fore.GREEN}Done!")
except Exception as e:
tb = traceback.format_exc()
print(tb)
tkinter.messagebox.showinfo("ERROR", tb)
exit_in_seconds(10)
if debug:
input("Press any key to exit...")
else:
exit_in_seconds(3)
SummonCurve(CurveType)
{
yes := "yes"
;BlockInput, MouseMove
SwitchInspectorTab("Video")
Send, ^2 ; TIMELINE MUST BE ACTIVE OR ^!+r WILL NOT WORK
; MsgBox, % "Prior Hotkey: " . A_PriorHotkey . " This Hotkey: " . A_ThisHotkey
If yes = yes
; If !(A_Priorkey = A_ThisHotkey)
{
Send, ^!+r ; Guarantees curve will be open no matter what state it was in
MouseGetPos, A, B
Keyframe(CurveType, "ResetValue")
If ErrorLevel
{
TempTooltip("Keyframe function: ErrorLevel " . ErrorLevel, 2000)
RandomSound("\Negative\*")
Goto SummonEnd
}
Sleep, 50
MouseMove, %A%, %B%
Send, ^z
TempTooltip(CurveType)
}
Else
Send ^+s
SummonEnd:
BlockInput, MouseMoveOff
Return
}
SwitchInspectorTab2(TabType, Thorough:=False)
{
WinGet, hWnd, ID, ahk_exe Resolve.exe
; Imagesearch assumes files are .png for ease of swapping in different filenames
; Variables
TabsImagesDir = %A_WorkingDir%\ImageSearch\Inspector\Tabs
TabOffsetX = 7
TabOffsetY = 10
TabBarHeight = 200
MouseGetPos, LastMouseX, LastMouseY
Switch TabType
{
case "Video": TabPath = 1
case "Audio": TabPath = 2
case "Effects": TabPath = 3
case "Transition": TabPath = 4
case "Image": TabPath = 5
case "File": TabPath = 6
Default:
{
MsgBox, % "Unsupported parameter in SwitchInspector function: '" . %TabType% . "'"
ErrorLevel = 1
}
}
Send, ^9 ; Force Inspector panel open
AllTabsPath := "2.2.3.1.2.5.1.2."
vAccPath := AllTabsPath . TabPath
MsgBox % vAccPath
MsgBox % hWnd
oAcc := Acc_Get("Object", vAccPath, 0, "ahk_id " hWnd)
MsgBox % oAcc
; Tries = 0 ; Start tries at 0
; FindTab:
; ImageSearch, TabX, TabY, %InspectorXOffset%, 0, A_ScreenWidth, %TabBarHeight%, *20 %Path%.png
; If ErrorLevel = 1
; {
; If Thorough
; {
; If Tries < 3:
; {
; Tries +=1
; TempToolTip("Searching again: " . Tries " tries.")
; Goto FindTab
; }
; If Tries < 5:
; {
; Tries +=1
; TempToolTip("Searching active: " . Tries . " tries.")
; Path := Path . "Active"
; Goto FindTab
; }
; If Tries < 7:
; {
; Tries +=1
; TempToolTip("Searching inactive: " . Tries . " tries.")
; Path := Path . "Inactive"
; Goto FindTab
; }
; TempToolTip("Couldn't find '" . TabType . "' Inspector tab type on screen", 2000)
; SoundBeep, 200
; SoundBeep, 175
; Sleep, 2000
; Return ErrorLevel
; }
; Else
; {
; ; SoundBeep, 200
; ErrorLevel = 0
; Return ErrorLevel
; }
; }
; If ErrorLevel = 2
; {
; TempToolTip("SwitchInspector function couldn't find image file: '" . Path . "'")
; RandomSound("\Negative\*")
; Return ErrorLevel
; }
; If !(Instr(Path, "active"))
; {
; WhereX := TabX += TabOffsetX
; WhereY := TabY += TabOffsetY
; Click, %WhereX%, %WhereY%
; MouseMove, %LastMouseX%, %LastMouseY%
; Return ErrorLevel
; }
}
ToggleKeyframe(Xx,Yy)
{
Send, ^9
Sleep, 50
ImageSearch, TransformX, TransformY, %InspectorXOffset%, 0, A_ScreenWidth, A_ScreenHeight, %A_WorkingDir%\ImageSearch\Inspector\TransformSwitch.bmp
If ErrorLevel = 2
{
MsgBox, MISSING `n "/ImageSearch/Inspector/ZoomX.bmp"
Return
}
If ErrorLevel = 1
{
SoundBeep
Return
}
MouseMove, TransformX += Xx, TransformY += Yy
Sleep, 100
Click
Sleep, 100
Return
}
TransitionAlign(fAlignment)
{
If (fAlignment = "Left")
fOffsetAmount = 90
Else If (fAlignment = "Right")
fOffsetAmount = 240
Else If (fAlignment = "Center")
fOffsetAmount = 175
Else
{
MsgBox, Unsupported value in TransitionAlign function param '%fAlignment%'
Exit
}
MouseGetPos, fX, fY
fTabPath = %A_WorkingDir%\ImageSearch\Inspector\Tabs\
fVideoTabInactive = %fTabPath%\VideoTabInactive.png
fAlignment = %fTabPath%\TransitionTab\AlignmentText.png
; ImageSearch, fCheckX, fCheckY, %InspectorXOffset%, 0, A_ScreenWidth, 600, *20 %fVideoTabInactive%
; If ErrorLevel = 1
; Return False
ImageSearch, fAlignX, fAlignY, %InspectorXOffset%, 0, A_ScreenWidth, 600, *20 %fAlignment%
If ErrorLevel = 1
Return
If ErrorLevel = 2
MsgBox, Couldn't find %fAlignment% .png
fOffsetY = %fAlignY% + 5
fOffsetX := fAlignX += fOffsetAmount
Click, %fOffsetX%, %fOffsetY%
MouseMove, %fX%, %fY%
}
@DasGandlaf
Copy link

Ahahah, this is crazy! I didn't know you could run resolve scripts from ahk! Great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment