Last active
January 27, 2024 19:17
-
-
Save in03/0a6399311dbeea32c89dce49454b42be to your computer and use it in GitHub Desktop.
DaVinci Resolve AutoHotkey scripts - not exhaustive! Examples for others.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 | |
} | |
;--------------------------------------------------------------------------------------------------------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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)) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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*) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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 | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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% | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ahahah, this is crazy! I didn't know you could run resolve scripts from ahk! Great!