Spaces:
Sleeping
Sleeping
--[[ | |
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 | |