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 | |