Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"commandpost": {
"command": "node",
"args": ["/Users/briantate/Documents/GitHub/CommandPost/src/mcp-server/dist/index.js"]
}
}
}
97 changes: 97 additions & 0 deletions src/extensions/cp/apple/finalcutpro/import/MediaImport.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ local strings = require "cp.apple.finalcutpro.strings"
local axutils = require "cp.ui.axutils"
local Button = require "cp.ui.Button"
local Dialog = require "cp.ui.Dialog"
local GoToPrompt = require "cp.apple.finalcutpro.export.GoToPrompt"

local cache = axutils.cache
local childWith = axutils.childWith

local GO_TO_PROMPT_TIMEOUT = 5
local IMPORT_WINDOW_TIMEOUT = 5
local IMPORT_BUTTON_TIMEOUT = 5
local IMPORT_START_TIMEOUT = 15

local MediaImport = Dialog:subclass("cp.apple.finalcutpro.import.MediaImport")

-- _findWindowUI(windows) -> hs.axuielementObject | nil
Expand Down Expand Up @@ -121,4 +127,95 @@ function MediaImport:hide()
return self
end

--- cp.apple.finalcutpro.import.MediaImport:setPath(path) -> boolean, string
--- Method
--- Uses the "Go to Folder" prompt to select a file or folder path in the Media Import window.
---
--- Parameters:
--- * path - The file or folder path.
---
--- Returns:
--- * `true` if the path was selected.
--- * An error message when the path could not be selected.
function MediaImport:setPath(path)
if not self:isShowing() then
return false, "Media Import window is not showing"
end

local prompt = self.goToPrompt
prompt:show()
if not just.doUntil(function() return prompt:isShowing() end, GO_TO_PROMPT_TIMEOUT) then
return false, "Go to Folder prompt did not appear"
end

prompt:value(path)
if prompt:isShowing() then
local _, success = prompt.go:press()
if not success then
return false, "Unable to confirm Go to Folder prompt"
end
end

if not just.doUntil(function() return not prompt:isShowing() end, GO_TO_PROMPT_TIMEOUT) then
return false, "Go to Folder prompt did not close"
end

return true
end

--- cp.apple.finalcutpro.import.MediaImport:importPath(path) -> boolean, string
--- Method
--- Opens the Media Import window, selects the supplied path, and presses "Import All".
---
--- Parameters:
--- * path - The file or folder path to import.
---
--- Returns:
--- * `true` if the import started successfully.
--- * An error message when the import could not be started.
function MediaImport:importPath(path)
self:show()
if not just.doUntil(function() return self:isShowing() end, IMPORT_WINDOW_TIMEOUT) then
return false, "Unable to open Media Import window"
end

local pathSelected, message = self:setPath(path)
if not pathSelected then
return false, message
end

local importAll = self.importAll
local importAllUI = just.doUntil(function()
local ui = importAll:UI()
if ui and ui:attributeValue("AXEnabled") == true then
return ui
end
return false
end, IMPORT_BUTTON_TIMEOUT)
if not importAllUI then
return false, "Import All button is unavailable for the selected path"
end

local _, success = importAll:press()
if not success then
return false, "Unable to press Import All"
end

local importStarted = just.doUntil(function()
return not self:isShowing() or self.stopImport:UI() ~= nil
end, IMPORT_START_TIMEOUT)
if not importStarted then
return false, "Import did not appear to start"
end

return true
end

--- cp.apple.finalcutpro.import.MediaImport.goToPrompt <GoToPrompt>
--- Field
--- The Go To Prompt object for the Media Import window.
function MediaImport.lazy.value:goToPrompt()
return GoToPrompt(self)
end

return MediaImport
46 changes: 33 additions & 13 deletions src/extensions/cp/apple/finalcutpro/main/PrimaryToolbar.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,16 @@ end
--- The `CheckBox` that will open the `KeywordEditor` dialog when checked.
function PrimaryToolbar.lazy.value:keywordEditor()
return CheckBox(self, self.UI:mutate(function(original)
-- for some reason some of these buttons are individually wrapped in an AXGroup
local group = childFromLeft(original(), 1, Group.matches)
local ui = original()
-- Legacy (pre-v12): checkboxes wrapped in AXGroup
local group = childFromLeft(ui, 1, Group.matches)
if group then
return childMatching(group, CheckBox.matches)
end
-- FCP v12+: checkboxes are direct toolbar children, match by description
return childMatching(ui, function(e)
return CheckBox.matches(e) and (e:attributeValue("AXDescription") or ""):find("Keyword Editor")
end)
end))
end

Expand All @@ -122,11 +127,14 @@ end
--- The `CheckBox` that will open the `BackgroundTasksWindow` dialog
function PrimaryToolbar.lazy.value:backgroundTasksWindow()
return CheckBox(self, self.UI:mutate(function(original)
-- for some reason CheckBoxes are individually wrapped in an AXGroup
local group = childFromLeft(original(), 2, Group.matches)
local ui = original()
-- Legacy (pre-v12): checkboxes wrapped in AXGroup
local group = childFromLeft(ui, 2, Group.matches)
if group then
return childMatching(group, CheckBox.matches)
end
-- FCP v12+: second checkbox from left (index 4, no description)
return childFromLeft(ui, 2, CheckBox.matches)
end))
end

Expand All @@ -144,12 +152,16 @@ end
--- The `CheckBox` indicating if the `Browser` is showing
function PrimaryToolbar.lazy.value:browserShowing()
return CheckBox(self, self.UI:mutate(function(original)
-- for some reason CheckBoxes are individually wrapped in an AXGroup
local group = childFromRight(original(), 4)
local ui = original()
-- Legacy (pre-v12): CheckBoxes individually wrapped in an AXGroup
local group = childFromRight(ui, 4)
if Group.matches(group) then
return childMatching(group, CheckBox.matches)
end
return nil
-- FCP v12+: direct checkbox children, match by description
return childMatching(ui, function(e)
return CheckBox.matches(e) and (e:attributeValue("AXDescription") or ""):find("Browser")
end)
end))
end

Expand All @@ -158,12 +170,16 @@ end
--- The `CheckBox` indicating if the `Timeline` is showing
function PrimaryToolbar.lazy.value:timelineShowing()
return CheckBox(self, function()
-- for some reason CheckBoxes are individually wrapped in an AXGroup
local group = childFromRight(self:UI(), 3)
local ui = self:UI()
-- Legacy (pre-v12): CheckBoxes individually wrapped in an AXGroup
local group = childFromRight(ui, 3)
if Group.matches(group) then
return childMatching(group, CheckBox.matches)
end
return nil
-- FCP v12+: direct checkbox children, match by description
return childMatching(ui, function(e)
return CheckBox.matches(e) and (e:attributeValue("AXDescription") or ""):find("Timeline")
end)
end)
end

Expand All @@ -172,12 +188,16 @@ end
--- The `CheckBox` indicating if the Inspector is showing
function PrimaryToolbar.lazy.value:inspectorShowing()
return CheckBox(self, function()
-- for some reason CheckBoxes are individually wrapped in an AXGroup
local group = childFromRight(self:UI(), 2)
local ui = self:UI()
-- Legacy (pre-v12): CheckBoxes individually wrapped in an AXGroup
local group = childFromRight(ui, 2)
if Group.matches(group) then
return childMatching(group, CheckBox.matches)
end
return nil
-- FCP v12+: direct checkbox children, match by description
return childMatching(ui, function(e)
return CheckBox.matches(e) and (e:attributeValue("AXDescription") or ""):find("Inspector")
end)
end)
end

Expand Down
9 changes: 5 additions & 4 deletions src/extensions/cp/apple/finalcutpro/menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ menu:addMenuFinder(function(parentItem, path, childName, locale)
----------------------------------------------------------------------------------------
-- Perform Pattern Matching with Tokens:
----------------------------------------------------------------------------------------
local itemChild = item.child:gsub("%%@", ".*")
if isEqual(path, item.path) and childName == itemChild then
local keyWithPattern = strings:find(item.key, locale):gsub("%%@", ".*")
local itemChild = type(item.child) == "string" and item.child:gsub("%%@", ".*") or nil
local keyTitle = type(item.key) == "string" and strings:find(item.key, locale, true) or nil
if itemChild and keyTitle and isEqual(path, item.path) and childName == itemChild then
local keyWithPattern = keyTitle:gsub("%%@", ".*")
return childMatching(parentItem, function(child)
local title = child:attributeValue("AXTitle")
return title and string.match(title, keyWithPattern)
Expand All @@ -101,4 +102,4 @@ menu:addMenuFinder(function(parentItem, path, childName, locale)
end
end
end
end)
end)
27 changes: 22 additions & 5 deletions src/extensions/cp/apple/finalcutpro/timeline/Contents.lua
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ function Contents:clipsUI(expandGroups, filterFn)
end)
return self:_filterClips(clips, expandGroups, filterFn)
end
return nil
return {}
end

--- cp.apple.finalcutpro.timeline.Contents:rangeSelectionUI() -> axuielements
Expand Down Expand Up @@ -331,12 +331,14 @@ end
--- * If `expandsGroups` is `true` any `AXGroup` items will be expanded to the list of contained `AXLayoutItems`.
--- * If `filterFn` is provided it will be called with a single argument to check if the provided clip should be included in the final table.
function Contents:positionClipsUI(position, expandGroups, filterFn)
if type(position) ~= "number" then return {} end

local clips = self:clipsUI(expandGroups, function(clip)
local frame = clip.AXFrame
return frame and position >= frame.x and position <= (frame.x + frame.w)
and (filterFn == nil or filterFn(clip))
end)
if not clips then return nil end
if not clips then return {} end

table.sort(clips, function(a, b) return a.AXPosition.y < b.AXPosition.y end)
return clips
Expand All @@ -357,7 +359,9 @@ end
--- * If `expandsGroups` is true any AXGroup items will be expanded to the list of contained `AXLayoutItems`.
--- * If `filterFn` is provided it will be called with a single argument to check if the provided clip should be included in the final table.
function Contents:playheadClipsUI(expandGroups, filterFn)
return self:positionClipsUI(self.playhead:position(), expandGroups, filterFn)
local position = self.playhead and self.playhead:position()
if type(position) ~= "number" then return {} end
return self:positionClipsUI(position, expandGroups, filterFn)
end

--- cp.apple.finalcutpro.timeline.Contents:skimmingPlayheadClipsUI(expandedGroups, filterFn) -> table of axuielements
Expand All @@ -375,7 +379,9 @@ end
--- * If `expandsGroups` is true any AXGroup items will be expanded to the list of contained `AXLayoutItems`.
--- * If `filterFn` is provided it will be called with a single argument to check if the provided clip should be included in the final table.
function Contents:skimmingPlayheadClipsUI(expandGroups, filterFn)
return self:positionClipsUI(self.skimmingPlayhead:position(), expandGroups, filterFn)
local position = self.skimmingPlayhead and self.skimmingPlayhead:position()
if type(position) ~= "number" then return {} end
return self:positionClipsUI(position, expandGroups, filterFn)
end

-- cp.apple.finalcutpro.timeline.Contents:_filterClips(clips, expandGroups, filterFn) -> table of axuielements
Expand Down Expand Up @@ -416,14 +422,25 @@ end
-- Returns:
-- * The table of axuielements that match the conditions
function Contents:_expandClips(clips, filterFn)
if type(clips) ~= "table" then
return {}
end

return fnutils.mapCat(clips, function(child)
if not child then
return {}
end

local role = child:attributeValue("AXRole")
if role == "AXLayoutItem" then
if filterFn == nil or filterFn(child) then
return {child}
end
elseif role == "AXGroup" then
return self:_expandClips(child:attributeValue("AXChildren"), filterFn)
local children = child:attributeValue("AXChildren")
if type(children) == "table" then
return self:_expandClips(children, filterFn)
end
end
return {}
end)
Expand Down
5 changes: 1 addition & 4 deletions src/extensions/cp/apple/finalcutpro/timeline/Timeline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,7 @@ end
--- Field
--- Checks if the Timeline is showing on either the Primary or Secondary display.
function Timeline.lazy.prop:isShowing()
return self.UI:mutate(function(original)
local ui = original()
return ui ~= nil and #ui > 0
end)
return self.UI:ISNOT(nil):AND(self:app().isShowing)
end

--- cp.apple.finalcutpro.timeline.Timeline.mainUI <cp.prop: hs.axuielement; read-only>
Expand Down
3 changes: 3 additions & 0 deletions src/mcp-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
*.tsbuildinfo
Loading