Spaces:
Sleeping
Sleeping
--[[ | |
ReaSpeechUI.lua - ReaSpeech user interface | |
]]-- | |
ReaSpeechUI = Polo { | |
VERSION = "unknown (development)", | |
-- Set to show ImGui Metrics/Debugger window | |
METRICS = false, | |
TITLE = 'ReaSpeech', | |
WIDTH = 1000, | |
HEIGHT = 600, | |
ITEM_WIDTH = 125, | |
} | |
function ReaSpeechUI:init() | |
self.onerror = function (e) | |
self:log(e) | |
end | |
self.requests = {} | |
self.responses = {} | |
self.logs = {} | |
ReaSpeechAPI:init(Script.host, Script.protocol) | |
self.worker = ReaSpeechWorker.new({ | |
requests = self.requests, | |
responses = self.responses, | |
logs = self.logs, | |
}) | |
self.controls_ui = ReaSpeechControlsUI.new() | |
self.actions_ui = ReaSpeechActionsUI.new({ | |
worker = self.worker | |
}) | |
self.transcript = Transcript.new() | |
self.transcript_ui = TranscriptUI.new { transcript = self.transcript } | |
self.failure = AlertPopup.new { title = 'Transcription Failed' } | |
self.react_handlers = self:get_react_handlers() | |
end | |
ReaSpeechUI.config_flags = function () | |
return ImGui.ConfigFlags_DockingEnable() | |
end | |
ReaSpeechUI.log_time = function () | |
return os.date('%Y-%m-%d %H:%M:%S') | |
end | |
function ReaSpeechUI:log(msg) | |
table.insert(self.logs, {msg, false}) | |
end | |
function ReaSpeechUI:debug(msg) | |
table.insert(self.logs, {msg, true}) | |
end | |
function ReaSpeechUI:trap(f) | |
return xpcall(f, self.onerror) | |
end | |
function ReaSpeechUI:has_js_ReaScriptAPI() | |
if reaper.JS_Dialog_BrowseForSaveFile then | |
return true | |
end | |
return false | |
end | |
function ReaSpeechUI:show_file_dialog(options) | |
local title = options.title or 'Save file' | |
local folder = options.folder or '' | |
local file = options.file or '' | |
local ext = options.ext or '' | |
local save = options.save or false | |
local multi = options.multi or false | |
if self:has_js_ReaScriptAPI() then | |
if save then | |
return reaper.JS_Dialog_BrowseForSaveFile(title, folder, file, ext) | |
else | |
return reaper.JS_Dialog_BrowseForOpenFiles(title, folder, file, ext, multi) | |
end | |
else | |
return nil | |
end | |
end | |
function ReaSpeechUI:tooltip(text) | |
if not ImGui.IsItemHovered(ctx, ImGui.HoveredFlags_DelayNormal()) or | |
not ImGui.BeginTooltip(ctx) | |
then return end | |
self:trap(function() | |
ImGui.PushTextWrapPos(ctx, ImGui.GetFontSize(ctx) * 42) | |
self:trap(function() | |
ImGui.Text(ctx, text) | |
end) | |
ImGui.PopTextWrapPos(ctx) | |
end) | |
ImGui.EndTooltip(ctx) | |
end | |
function ReaSpeechUI:react() | |
for _, handler in pairs(self.react_handlers) do | |
self:trap(handler) | |
end | |
end | |
function ReaSpeechUI:get_react_handlers() | |
return { | |
function() self:react_to_worker_response() end, | |
function() self:react_to_logging() end, | |
function() self.worker:react() end, | |
function() self:render() end | |
} | |
end | |
function ReaSpeechUI:react_to_worker_response() | |
local response = table.remove(self.responses, 1) | |
if not response then | |
return | |
end | |
self:debug('Response: ' .. dump(response)) | |
if response.error then | |
self.failure:show(response.error) | |
self.worker:cancel() | |
return | |
end | |
if not response.segments then | |
return | |
end | |
for _, segment in pairs(response.segments) do | |
for _, s in pairs( | |
TranscriptSegment.from_whisper(segment, response._job.item, response._job.take) | |
) do | |
if s:get('text') then | |
self.transcript:add_segment(s) | |
end | |
end | |
end | |
self.transcript:update() | |
end | |
function ReaSpeechUI:react_to_logging() | |
for _, log in pairs(self.logs) do | |
local msg, dbg = table.unpack(log) | |
if dbg and self.controls_ui.log_enable:value() and self.controls_ui.log_debug:value() then | |
reaper.ShowConsoleMsg(self:log_time() .. ' [DBG] ' .. tostring(msg) .. '\n') | |
elseif not dbg and self.controls_ui.log_enable:value() then | |
reaper.ShowConsoleMsg(self:log_time() .. ' [LOG] ' .. tostring(msg) .. '\n') | |
end | |
end | |
self.logs = {} | |
end | |
function ReaSpeechUI:render() | |
ImGui.PushItemWidth(ctx, self.ITEM_WIDTH) | |
self:trap(function () | |
self.controls_ui:render() | |
self.actions_ui:render() | |
self.transcript_ui:render() | |
self.failure:render() | |
end) | |
ImGui.PopItemWidth(ctx) | |
end | |
function ReaSpeechUI:new_jobs(jobs) | |
local request = self.controls_ui:get_request_data() | |
request.jobs = jobs | |
self:debug('Request: ' .. dump(request)) | |
self.transcript:clear() | |
table.insert(self.requests, request) | |
end | |
function ReaSpeechUI.png_from_bytes(image_key) | |
if not IMAGES[image_key] or not IMAGES[image_key].bytes then | |
return | |
end | |
local image = IMAGES[image_key] | |
if not ImGui.ValidatePtr(image.imgui_image, 'ImGui_Image*') then | |
image.imgui_image = ImGui.CreateImageFromMem(image.bytes) | |
end | |
ImGui.Image(ctx, image.imgui_image, image.width, image.height) | |
end | |
function ReaSpeechUI.get_source_path(take) | |
local source = reaper.GetMediaItemTake_Source(take) | |
if source then | |
local source_path = reaper.GetMediaSourceFileName(source) | |
return source_path | |
end | |
return nil | |
end | |