ReaSpeech-Cloud / reascripts /ReaSpeech /source /TranscriptExporter.lua
j
initial commit
402daee
--[[
TranscriptExporter.lua - Transcript export UI
]]--
TranscriptExporter = Polo {
TITLE = 'Export',
WIDTH = 650,
HEIGHT = 200,
BUTTON_WIDTH = 120,
INPUT_WIDTH = 120,
FILE_WIDTH = 500,
}
function TranscriptExporter:init()
assert(self.transcript, 'missing transcript')
self.is_open = false
self.export_formats = TranscriptExporterFormats.new {
TranscriptExportFormat.exporter_json(),
TranscriptExportFormat.exporter_srt(),
TranscriptExportFormat.exporter_csv(),
}
self.export_options = {}
self.file = ''
self.success = AlertPopup.new { title = 'Export Successful' }
self.failure = AlertPopup.new { title = 'Export Failed' }
end
function TranscriptExporter:show_success()
self.success.onclose = function ()
self.success.onclose = nil
self:close()
end
self.success:show('Exported ' .. self.export_formats:selected_key() .. ' to: ' .. self.file)
end
function TranscriptExporter:show_error(msg)
self.failure:show(msg)
end
function TranscriptExporter:render()
if not self.is_open then
return
end
local center = {ImGui.Viewport_GetCenter(ImGui.GetWindowViewport(ctx))}
ImGui.SetNextWindowPos(ctx, center[1], center[2], ImGui.Cond_Appearing(), 0.5, 0.5)
ImGui.SetNextWindowSize(ctx, self.WIDTH, self.HEIGHT, ImGui.Cond_FirstUseEver())
local flags = (
0
| ImGui.WindowFlags_AlwaysAutoResize()
| ImGui.WindowFlags_NoCollapse()
| ImGui.WindowFlags_NoDocking()
)
local visible, open = ImGui.Begin(ctx, self.TITLE, true, flags)
if visible then
app:trap(function ()
self:render_content()
self.success:render()
self.failure:render()
end)
ImGui.End(ctx)
end
if not open then
self:close()
end
end
function TranscriptExporter:render_content()
self.export_formats:render_combo(self.INPUT_WIDTH)
ImGui.Spacing(ctx)
self.export_formats:render_format_options(self.export_options)
ImGui.Spacing(ctx)
self:render_file_selector()
self:render_separator()
self:render_buttons()
end
-- Display a text input for the output filename, with a Browse button if
-- the js_ReaScriptAPI extension is available.
function TranscriptExporter:render_file_selector()
ImGui.Text(ctx, 'File')
if app:has_js_ReaScriptAPI() then
if ImGui.Button(ctx, 'Choose File', self.BUTTON_WIDTH, 0) then
local rv, file = app:show_file_dialog {
title = 'Save transcript',
file = self.file,
save = true,
ext = self.export_formats:file_selector_spec(),
}
if rv == 1 then
self.file = file
end
end
ImGui.SameLine(ctx)
end
ImGui.SetNextItemWidth(ctx, self.FILE_WIDTH)
local file_changed, file = ImGui.InputText(ctx, '##file', self.file, 256)
if file_changed then
self.file = file
end
if not app:has_js_ReaScriptAPI() then
ImGui.Text(ctx, "For a better experience, install js_ReaScriptAPI")
ImGui.Spacing(ctx)
end
end
function TranscriptExporter:render_buttons()
ReaUtil.disabler(ctx)(self.file == '', function()
if ImGui.Button(ctx, 'Export', self.BUTTON_WIDTH, 0) then
if self:handle_export() then
self:show_success()
end
end
end)
ImGui.SameLine(ctx)
if ImGui.Button(ctx, 'Cancel', self.BUTTON_WIDTH, 0) then
self:close()
end
end
function TranscriptExporter:render_separator()
ImGui.Dummy(ctx, 0, 0)
ImGui.Separator(ctx)
ImGui.Dummy(ctx, 0, 0)
end
function TranscriptExporter:handle_export()
if self.file == '' then
self:show_error('Please specify a file name.')
return false
end
local file = io.open(self.file, 'w')
if not file then
self:show_error('Could not open file: ' .. self.file)
return false
end
self.export_formats:write(self.transcript, file, self.export_options)
file:close()
return true
end
function TranscriptExporter:open()
self.is_open = true
end
function TranscriptExporter:close()
self.is_open = false
end
TranscriptExporterFormats = Polo {
new = function(formatters)
local format_map = {}
for i, formatter in ipairs(formatters) do
format_map[formatter.key] = i
end
return {
formatters = formatters,
format_map = format_map,
}
end,
}
function TranscriptExporterFormats:render_combo(width)
ImGui.Text(ctx, 'Format')
ImGui.SetNextItemWidth(ctx, width)
if ImGui.BeginCombo(ctx, "##format", self.selected_format_key) then
app:trap(function()
for _, format in pairs(self.formatters) do
local is_selected = self.selected_format_key == format.key
if ImGui.Selectable(ctx, format.key, is_selected) then
self.selected_format_key = format.key
end
if is_selected then
ImGui.SetItemDefaultFocus(ctx)
end
end
end)
ImGui.EndCombo(ctx)
end
end
function TranscriptExporterFormats:selected_key()
return self:selected_format().key
end
function TranscriptExporterFormats:file_selector_spec()
return self:selected_format():file_selector_spec()
end
function TranscriptExporterFormats:write(transcript, output_file, options)
return self:selected_format().writer(transcript, output_file, options)
end
function TranscriptExporterFormats:selected_format()
if not self.selected_format_key then
if not self.formatters or #self.formatters < 1 then
app:debug('no formats to set for default')
return
end
self.selected_format_key = self.formatters[1].key
end
local index = self.format_map[self.selected_format_key]
return self.formatters[index]
end
function TranscriptExporterFormats:render_format_options(options)
app:trap(function()
local format = self:selected_format()
if format then
format.option_renderer(options)
end
end)
end
TranscriptExportFormat = Polo {
OPTIONS_NOOP = function(_options) end,
new = function (key, extension, option_renderer, writer_f)
return {
key = key,
extension = extension,
option_renderer = option_renderer,
writer = writer_f,
}
end,
}
function TranscriptExportFormat:file_selector_spec()
local selector_spec = '%s files (*.%s)\0*.%s\0All files (*.*)\0*.*\0\0'
return selector_spec:format(self.key, self.extension, self.extension)
end
function TranscriptExportFormat.exporter_json()
return TranscriptExportFormat.new(
'JSON', 'json',
TranscriptExportFormat.options_json,
TranscriptExportFormat.writer_json
)
end
function TranscriptExportFormat.options_json(options)
local rv, value = ImGui.Checkbox(ctx, 'One Object per Transcript Segment', options.object_per_segment)
if rv then
options.object_per_segment = value
end
end
function TranscriptExportFormat.writer_json(transcript, output_file, options)
if options.object_per_segment then
for _, segment in pairs(transcript:get_segments()) do
output_file:write(segment:to_json())
output_file:write('\n')
end
else
output_file:write(transcript:to_json())
end
end
function TranscriptExportFormat.exporter_srt()
return TranscriptExportFormat.new(
'SRT', 'srt',
TranscriptExportFormat.options_srt,
TranscriptExportFormat.writer_srt
)
end
function TranscriptExportFormat.strip_non_numeric(value)
return value:gsub("[^0-9]", ""):gsub("^0+", "")
end
function TranscriptExportFormat.options_srt(options)
local rv, value
rv, value = ImGui.InputText(ctx, 'X1', options.coords_x1, ImGui.InputTextFlags_CharsDecimal())
if rv then
options.coords_x1 = TranscriptExportFormat.strip_non_numeric(value)
end
ImGui.SameLine(ctx)
rv, value = ImGui.InputText(ctx, 'Y1', options.coords_y1, ImGui.InputTextFlags_CharsDecimal())
if rv then
options.coords_y1 = TranscriptExportFormat.strip_non_numeric(value)
end
rv, value = ImGui.InputText(ctx, 'X2', options.coords_x2, ImGui.InputTextFlags_CharsDecimal())
if rv then
options.coords_x2 = TranscriptExportFormat.strip_non_numeric(value)
end
ImGui.SameLine(ctx)
rv, value = ImGui.InputText(ctx, 'Y2', options.coords_y2, ImGui.InputTextFlags_CharsDecimal())
if rv then
options.coords_y2 = TranscriptExportFormat.strip_non_numeric(value)
end
end
function TranscriptExportFormat.writer_srt(transcript, output_file, options)
local writer = SRTWriter.new { file = output_file, options = options }
writer:write(transcript)
end
function TranscriptExportFormat.exporter_csv()
return TranscriptExportFormat.new(
'CSV', 'csv',
TranscriptExportFormat.options_csv,
TranscriptExportFormat.writer_csv
)
end
function TranscriptExportFormat.options_csv(options)
local delimiters = CSVWriter.DELIMITERS
local selected_delimiter = delimiters[1]
for _, delimiter in ipairs(delimiters) do
if delimiter.char == options.delimiter then
selected_delimiter = delimiter
break
end
end
if ImGui.BeginCombo(ctx, 'Delimiter', selected_delimiter.name) then
app:trap(function()
for _, delimiter in ipairs(delimiters) do
local is_selected = options.delimiter == delimiter.char
if ImGui.Selectable(ctx, delimiter.name, is_selected) then
options.delimiter = delimiter.char
end
if is_selected then
ImGui.SetItemDefaultFocus(ctx)
end
end
end)
ImGui.EndCombo(ctx)
end
ImGui.Spacing(ctx)
local rv, value = ImGui.Checkbox(ctx, 'Include Header Row', options.include_header_row)
if rv then
options.include_header_row = value
end
end
function TranscriptExportFormat.writer_csv(transcript, output_file, options)
local writer = CSVWriter.new {
file = output_file,
delimiter = options.delimiter,
include_header_row = options.include_header_row
}
writer:write(transcript)
end