Spaces:
Sleeping
Sleeping
j
commited on
Commit
·
170e15c
1
Parent(s):
4fa523b
update from base repository
Browse files- Dockerfile +10 -32
- app/faster_whisper/core.py +1 -0
- app/run.py +36 -6
- app/webservice.py +5 -1
- reascripts/ReaSpeech/source/ColumnLayout.lua +70 -0
- reascripts/ReaSpeech/source/ReaSpeechAPI.lua +10 -3
- reascripts/ReaSpeech/source/ReaSpeechActionsUI.lua +6 -2
- reascripts/ReaSpeech/source/ReaSpeechControlsUI.lua +252 -125
- reascripts/ReaSpeech/source/ReaSpeechWorker.lua +25 -9
- reascripts/ReaSpeech/source/Theme.lua +5 -2
- reascripts/ReaSpeech/tests/TestColumnLayout.lua +202 -0
- reascripts/ReaSpeech/tests/TestReaSpeechUI.lua +1 -0
Dockerfile
CHANGED
|
@@ -1,14 +1,8 @@
|
|
| 1 |
FROM swaggerapi/swagger-ui:v4.18.2 AS swagger-ui
|
| 2 |
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
|
| 3 |
|
| 4 |
-
ARG SERVICE_USER=service
|
| 5 |
-
ARG SERVICE_UID=1001
|
| 6 |
-
ARG SERVICE_GID=1001
|
| 7 |
-
|
| 8 |
ENV PYTHON_VERSION=3.10
|
| 9 |
ENV POETRY_VENV=/app/.venv
|
| 10 |
-
ENV HF_HOME="/app/.cache"
|
| 11 |
-
ENV ASR_MODEL_PATH="/app/.cache"
|
| 12 |
|
| 13 |
RUN export DEBIAN_FRONTEND=noninteractive \
|
| 14 |
&& apt-get -qq update \
|
|
@@ -29,44 +23,28 @@ RUN ln -s -f /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 && \
|
|
| 29 |
ln -s -f /usr/bin/python${PYTHON_VERSION} /usr/bin/python && \
|
| 30 |
ln -s -f /usr/bin/pip3 /usr/bin/pip
|
| 31 |
|
| 32 |
-
RUN
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
RUN getent group $SERVICE_USER
|
| 36 |
-
RUN getent passwd $SERVICE_USER
|
| 37 |
-
|
| 38 |
-
COPY --chown=$SERVICE_UID:$SERVICE_GID . /app
|
| 39 |
-
COPY --chown=$SERVICE_UID:$SERVICE_GID --from=swagger-ui /usr/share/nginx/html/swagger-ui.css /app/swagger-ui-assets/swagger-ui.css
|
| 40 |
-
COPY --chown=$SERVICE_UID:$SERVICE_GID --from=swagger-ui /usr/share/nginx/html/swagger-ui-bundle.js /app/swagger-ui-assets/swagger-ui-bundle.js
|
| 41 |
-
|
| 42 |
-
RUN chown -R $SERVICE_UID:$SERVICE_GID /app
|
| 43 |
|
| 44 |
-
|
| 45 |
-
RUN mkdir -p /app/.cache && chown -R $SERVICE_UID:$SERVICE_GID /app/.cache && ls -la /app/.cache
|
| 46 |
-
|
| 47 |
-
USER $SERVICE_USER
|
| 48 |
|
| 49 |
WORKDIR /app
|
| 50 |
|
| 51 |
-
|
| 52 |
-
$POETRY_VENV/bin/pip install poetry==1.6.1
|
| 53 |
-
|
| 54 |
-
ENV PATH="${PATH}:${POETRY_VENV}/bin"
|
| 55 |
-
|
| 56 |
-
COPY --chown=$SERVICE_UID:$SERVICE_GID poetry.lock pyproject.toml ./
|
| 57 |
|
| 58 |
RUN poetry config virtualenvs.in-project true
|
| 59 |
RUN poetry install --no-root
|
| 60 |
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
RUN $POETRY_VENV/bin/pip install --no-cache-dir torch==1.13.1+cu117 -f https://download.pytorch.org/whl/torch
|
| 63 |
|
| 64 |
WORKDIR /app/reascripts/ReaSpeech
|
| 65 |
-
|
| 66 |
-
RUN ls -la /app/reascripts/ReaSpeech
|
| 67 |
-
|
| 68 |
RUN make publish
|
| 69 |
-
|
| 70 |
WORKDIR /app
|
| 71 |
RUN rm -rf reascripts
|
| 72 |
|
|
|
|
| 1 |
FROM swaggerapi/swagger-ui:v4.18.2 AS swagger-ui
|
| 2 |
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
ENV PYTHON_VERSION=3.10
|
| 5 |
ENV POETRY_VENV=/app/.venv
|
|
|
|
|
|
|
| 6 |
|
| 7 |
RUN export DEBIAN_FRONTEND=noninteractive \
|
| 8 |
&& apt-get -qq update \
|
|
|
|
| 23 |
ln -s -f /usr/bin/python${PYTHON_VERSION} /usr/bin/python && \
|
| 24 |
ln -s -f /usr/bin/pip3 /usr/bin/pip
|
| 25 |
|
| 26 |
+
RUN python3 -m venv $POETRY_VENV \
|
| 27 |
+
&& $POETRY_VENV/bin/pip install -U pip setuptools \
|
| 28 |
+
&& $POETRY_VENV/bin/pip install poetry==1.6.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
ENV PATH="${PATH}:${POETRY_VENV}/bin"
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
WORKDIR /app
|
| 33 |
|
| 34 |
+
COPY poetry.lock pyproject.toml ./
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
RUN poetry config virtualenvs.in-project true
|
| 37 |
RUN poetry install --no-root
|
| 38 |
|
| 39 |
+
COPY . .
|
| 40 |
+
COPY --from=swagger-ui /usr/share/nginx/html/swagger-ui.css swagger-ui-assets/swagger-ui.css
|
| 41 |
+
COPY --from=swagger-ui /usr/share/nginx/html/swagger-ui-bundle.js swagger-ui-assets/swagger-ui-bundle.js
|
| 42 |
+
|
| 43 |
+
RUN poetry install && rm -rf /root/.cache/pypoetry
|
| 44 |
RUN $POETRY_VENV/bin/pip install --no-cache-dir torch==1.13.1+cu117 -f https://download.pytorch.org/whl/torch
|
| 45 |
|
| 46 |
WORKDIR /app/reascripts/ReaSpeech
|
|
|
|
|
|
|
|
|
|
| 47 |
RUN make publish
|
|
|
|
| 48 |
WORKDIR /app
|
| 49 |
RUN rm -rf reascripts
|
| 50 |
|
app/faster_whisper/core.py
CHANGED
|
@@ -13,6 +13,7 @@ from .utils import ResultWriter, WriteTXT, WriteSRT, WriteVTT, WriteTSV, WriteJS
|
|
| 13 |
ASR_ENGINE_OPTIONS = frozenset([
|
| 14 |
"task",
|
| 15 |
"language",
|
|
|
|
| 16 |
"initial_prompt",
|
| 17 |
"vad_filter",
|
| 18 |
"word_timestamps",
|
|
|
|
| 13 |
ASR_ENGINE_OPTIONS = frozenset([
|
| 14 |
"task",
|
| 15 |
"language",
|
| 16 |
+
"hotwords",
|
| 17 |
"initial_prompt",
|
| 18 |
"vad_filter",
|
| 19 |
"word_timestamps",
|
app/run.py
CHANGED
|
@@ -30,6 +30,9 @@ argmap = {
|
|
| 30 |
'--asr-model': {
|
| 31 |
'default': os.getenv('ASR_MODEL', 'small'),
|
| 32 |
'help': 'ASR model to use (default: %(default)s)' },
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
parser = argparse.ArgumentParser()
|
|
@@ -46,26 +49,53 @@ os.environ['FFMPEG_BIN'] = args.ffmpeg_bin
|
|
| 46 |
os.environ['ASR_ENGINE'] = args.asr_engine
|
| 47 |
os.environ['ASR_MODEL'] = args.asr_model
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
# Start Redis
|
| 50 |
print('Starting database...', file=sys.stderr)
|
| 51 |
-
subprocess.Popen([args.redis_bin], stdout=subprocess.DEVNULL)
|
| 52 |
|
| 53 |
# Start Celery
|
| 54 |
print('Starting worker...', file=sys.stderr)
|
| 55 |
-
subprocess.Popen(['celery', '-A', 'app.worker.celery', 'worker', '--pool=solo', '--loglevel=info'])
|
| 56 |
|
| 57 |
# Start Gunicorn
|
| 58 |
print('Starting application...', file=sys.stderr)
|
| 59 |
-
subprocess.Popen(['gunicorn', '--bind', '0.0.0.0:9000', '--workers', '1', '--timeout', '0', 'app.webservice:app', '-k', 'uvicorn.workers.UvicornWorker'])
|
| 60 |
|
| 61 |
# Wait for any process to exit
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
# Terminate any child processes
|
| 66 |
print('Terminating child processes...', file=sys.stderr)
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
# Exit with status of process that exited
|
|
|
|
| 70 |
print('Exiting with status', status, file=sys.stderr)
|
| 71 |
sys.exit(status)
|
|
|
|
| 30 |
'--asr-model': {
|
| 31 |
'default': os.getenv('ASR_MODEL', 'small'),
|
| 32 |
'help': 'ASR model to use (default: %(default)s)' },
|
| 33 |
+
'--build-reascripts': {
|
| 34 |
+
'action': 'store_true',
|
| 35 |
+
'help': 'Build ReaScripts before starting' },
|
| 36 |
}
|
| 37 |
|
| 38 |
parser = argparse.ArgumentParser()
|
|
|
|
| 49 |
os.environ['ASR_ENGINE'] = args.asr_engine
|
| 50 |
os.environ['ASR_MODEL'] = args.asr_model
|
| 51 |
|
| 52 |
+
if args.build_reascripts:
|
| 53 |
+
if os.system('cd reascripts/ReaSpeech && make') != 0:
|
| 54 |
+
print('ReaScript build failed', file=sys.stderr)
|
| 55 |
+
sys.exit(1)
|
| 56 |
+
|
| 57 |
+
processes = {}
|
| 58 |
+
|
| 59 |
# Start Redis
|
| 60 |
print('Starting database...', file=sys.stderr)
|
| 61 |
+
processes['redis'] = subprocess.Popen([args.redis_bin], stdout=subprocess.DEVNULL)
|
| 62 |
|
| 63 |
# Start Celery
|
| 64 |
print('Starting worker...', file=sys.stderr)
|
| 65 |
+
processes['celery'] = subprocess.Popen(['celery', '-A', 'app.worker.celery', 'worker', '--pool=solo', '--loglevel=info'])
|
| 66 |
|
| 67 |
# Start Gunicorn
|
| 68 |
print('Starting application...', file=sys.stderr)
|
| 69 |
+
processes['gunicorn'] = subprocess.Popen(['gunicorn', '--bind', '0.0.0.0:9000', '--workers', '1', '--timeout', '0', 'app.webservice:app', '-k', 'uvicorn.workers.UvicornWorker'])
|
| 70 |
|
| 71 |
# Wait for any process to exit
|
| 72 |
+
pid, waitstatus = os.wait()
|
| 73 |
+
exitcode = os.waitstatus_to_exitcode(waitstatus)
|
| 74 |
+
process_name = '<unknown>'
|
| 75 |
+
for name, p in processes.items():
|
| 76 |
+
if p.pid == pid:
|
| 77 |
+
process_name = name
|
| 78 |
+
break
|
| 79 |
+
if exitcode < 0:
|
| 80 |
+
print('Process', process_name, 'received signal', -exitcode, file=sys.stderr)
|
| 81 |
+
else:
|
| 82 |
+
print('Process', process_name, 'exited with status', exitcode, file=sys.stderr)
|
| 83 |
|
| 84 |
# Terminate any child processes
|
| 85 |
print('Terminating child processes...', file=sys.stderr)
|
| 86 |
+
for name, p in processes.items():
|
| 87 |
+
try:
|
| 88 |
+
print('Terminating', name, file=sys.stderr)
|
| 89 |
+
|
| 90 |
+
# kinda bass-ackwards, but poll() returns None if process is still running
|
| 91 |
+
if not p.poll():
|
| 92 |
+
p.terminate()
|
| 93 |
+
else:
|
| 94 |
+
print(name, "already exited", file=sys.stderr)
|
| 95 |
+
except Exception as e:
|
| 96 |
+
print(e, file=sys.stderr)
|
| 97 |
|
| 98 |
# Exit with status of process that exited
|
| 99 |
+
status = 1 if exitcode < 0 else exitcode
|
| 100 |
print('Exiting with status', status, file=sys.stderr)
|
| 101 |
sys.exit(status)
|
app/webservice.py
CHANGED
|
@@ -24,6 +24,7 @@ ASR_ENGINE = os.getenv("ASR_ENGINE", "faster_whisper")
|
|
| 24 |
ASR_OPTIONS = frozenset([
|
| 25 |
"task",
|
| 26 |
"language",
|
|
|
|
| 27 |
"initial_prompt",
|
| 28 |
"encode",
|
| 29 |
"output",
|
|
@@ -36,6 +37,8 @@ DEFAULT_MODEL_NAME = os.getenv("ASR_MODEL", "small")
|
|
| 36 |
|
| 37 |
LANGUAGE_CODES = sorted(list(tokenizer.LANGUAGES.keys()))
|
| 38 |
|
|
|
|
|
|
|
| 39 |
projectMetadata = importlib.metadata.metadata('reaspeech')
|
| 40 |
app = FastAPI(
|
| 41 |
# docs_url=None,
|
|
@@ -111,6 +114,7 @@ async def reascript(request: Request, name: str, host: str):
|
|
| 111 |
async def asr(
|
| 112 |
task: Union[str, None] = Query(default="transcribe", enum=["transcribe", "translate"]),
|
| 113 |
language: Union[str, None] = Query(default=None, enum=LANGUAGE_CODES),
|
|
|
|
| 114 |
initial_prompt: Union[str, None] = Query(default=None),
|
| 115 |
audio_file: UploadFile = File(...),
|
| 116 |
encode: bool = Query(default=True, description="Encode audio first through ffmpeg"),
|
|
@@ -137,7 +141,7 @@ async def asr(
|
|
| 137 |
transcriber = transcribe.si(temp_file_path, audio_file.filename, asr_options)
|
| 138 |
|
| 139 |
if use_async:
|
| 140 |
-
job = transcriber.apply_async()
|
| 141 |
return JSONResponse({"job_id": job.id})
|
| 142 |
|
| 143 |
else:
|
|
|
|
| 24 |
ASR_OPTIONS = frozenset([
|
| 25 |
"task",
|
| 26 |
"language",
|
| 27 |
+
"hotwords",
|
| 28 |
"initial_prompt",
|
| 29 |
"encode",
|
| 30 |
"output",
|
|
|
|
| 37 |
|
| 38 |
LANGUAGE_CODES = sorted(list(tokenizer.LANGUAGES.keys()))
|
| 39 |
|
| 40 |
+
TASK_EXPIRATION_SECONDS = 30
|
| 41 |
+
|
| 42 |
projectMetadata = importlib.metadata.metadata('reaspeech')
|
| 43 |
app = FastAPI(
|
| 44 |
# docs_url=None,
|
|
|
|
| 114 |
async def asr(
|
| 115 |
task: Union[str, None] = Query(default="transcribe", enum=["transcribe", "translate"]),
|
| 116 |
language: Union[str, None] = Query(default=None, enum=LANGUAGE_CODES),
|
| 117 |
+
hotwords: Union[str, None] = Query(default=None),
|
| 118 |
initial_prompt: Union[str, None] = Query(default=None),
|
| 119 |
audio_file: UploadFile = File(...),
|
| 120 |
encode: bool = Query(default=True, description="Encode audio first through ffmpeg"),
|
|
|
|
| 141 |
transcriber = transcribe.si(temp_file_path, audio_file.filename, asr_options)
|
| 142 |
|
| 143 |
if use_async:
|
| 144 |
+
job = transcriber.apply_async(expires=TASK_EXPIRATION_SECONDS)
|
| 145 |
return JSONResponse({"job_id": job.id})
|
| 146 |
|
| 147 |
else:
|
reascripts/ReaSpeech/source/ColumnLayout.lua
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--[[
|
| 2 |
+
|
| 3 |
+
ColumnLayout.lua - Fixed-width column layout helper
|
| 4 |
+
|
| 5 |
+
]]--
|
| 6 |
+
|
| 7 |
+
ColumnLayout = Polo {
|
| 8 |
+
DEFAULT_COLUMN_PADDING = 15,
|
| 9 |
+
DEFAULT_NUM_COLUMNS = 3,
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
function ColumnLayout:init()
|
| 13 |
+
assert(self.render_column, 'render_column function must be provided')
|
| 14 |
+
self.column_padding = self.column_padding or self.DEFAULT_COLUMN_PADDING
|
| 15 |
+
self.margin_bottom = self.margin_bottom or 0
|
| 16 |
+
self.margin_left = self.margin_left or 0
|
| 17 |
+
self.margin_right = self.margin_right or 0
|
| 18 |
+
self.margin_top = self.margin_top or 0
|
| 19 |
+
self.num_columns = self.num_columns or self.DEFAULT_NUM_COLUMNS
|
| 20 |
+
self.width = self.width or 0
|
| 21 |
+
end
|
| 22 |
+
|
| 23 |
+
function ColumnLayout:render()
|
| 24 |
+
local total_padding = (self.num_columns - 1) * self.column_padding
|
| 25 |
+
local total_width = self.width
|
| 26 |
+
if total_width == 0 then
|
| 27 |
+
total_width = self:_get_avail_width()
|
| 28 |
+
end
|
| 29 |
+
local content_width = total_width - self.margin_left - self.margin_right
|
| 30 |
+
local column_width = (content_width - total_padding) / self.num_columns
|
| 31 |
+
|
| 32 |
+
self:_horiz_margin(self.margin_left)
|
| 33 |
+
|
| 34 |
+
for i = 1, self.num_columns do
|
| 35 |
+
local column = {num = i, width = column_width}
|
| 36 |
+
|
| 37 |
+
self:_with_group(function ()
|
| 38 |
+
self:_vert_margin(self.margin_top, column_width)
|
| 39 |
+
self.render_column(column)
|
| 40 |
+
self:_vert_margin(self.margin_bottom, column_width)
|
| 41 |
+
end)
|
| 42 |
+
|
| 43 |
+
if i < self.num_columns then
|
| 44 |
+
self:_column_gap(self.column_padding)
|
| 45 |
+
end
|
| 46 |
+
end
|
| 47 |
+
end
|
| 48 |
+
|
| 49 |
+
function ColumnLayout:_column_gap(padding)
|
| 50 |
+
ImGui.SameLine(ctx, 0, padding)
|
| 51 |
+
end
|
| 52 |
+
|
| 53 |
+
function ColumnLayout:_get_avail_width()
|
| 54 |
+
local avail_width, _ = ImGui.GetContentRegionAvail(ctx)
|
| 55 |
+
return avail_width
|
| 56 |
+
end
|
| 57 |
+
|
| 58 |
+
function ColumnLayout:_horiz_margin(margin)
|
| 59 |
+
ImGui.SetCursorPosX(ctx, ImGui.GetCursorPosX(ctx) + margin)
|
| 60 |
+
end
|
| 61 |
+
|
| 62 |
+
function ColumnLayout:_vert_margin(margin, width)
|
| 63 |
+
ImGui.Dummy(ctx, width, margin)
|
| 64 |
+
end
|
| 65 |
+
|
| 66 |
+
function ColumnLayout:_with_group(f)
|
| 67 |
+
ImGui.BeginGroup(ctx)
|
| 68 |
+
app:trap(f)
|
| 69 |
+
ImGui.EndGroup(ctx)
|
| 70 |
+
end
|
reascripts/ReaSpeech/source/ReaSpeechAPI.lua
CHANGED
|
@@ -30,8 +30,10 @@ end
|
|
| 30 |
|
| 31 |
-- Fetch simple JSON responses. Will block until result or curl timeout.
|
| 32 |
-- For large amounts of data, use fetch_large instead.
|
| 33 |
-
function ReaSpeechAPI:fetch_json(url_path, http_method, error_handler)
|
| 34 |
http_method = http_method or 'GET'
|
|
|
|
|
|
|
| 35 |
|
| 36 |
local curl = self:get_curl_cmd()
|
| 37 |
local api_url = self:get_api_url(url_path)
|
|
@@ -63,8 +65,13 @@ function ReaSpeechAPI:fetch_json(url_path, http_method, error_handler)
|
|
| 63 |
end
|
| 64 |
|
| 65 |
local status, output = exec_result:match("(%d+)\n(.*)")
|
|
|
|
| 66 |
|
| 67 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
local msg = "Curl failed with status " .. status
|
| 69 |
app:debug(msg)
|
| 70 |
error_handler(msg)
|
|
@@ -190,7 +197,7 @@ function ReaSpeechAPI.http_status_and_body(response)
|
|
| 190 |
|
| 191 |
local status = last_status_line:match("^HTTP/%d%.%d%s+(%d+)")
|
| 192 |
if not status then
|
| 193 |
-
return -1, '
|
| 194 |
end
|
| 195 |
|
| 196 |
local body = {}
|
|
|
|
| 30 |
|
| 31 |
-- Fetch simple JSON responses. Will block until result or curl timeout.
|
| 32 |
-- For large amounts of data, use fetch_large instead.
|
| 33 |
+
function ReaSpeechAPI:fetch_json(url_path, http_method, error_handler, timeout_handler)
|
| 34 |
http_method = http_method or 'GET'
|
| 35 |
+
error_handler = error_handler or function(_msg) end
|
| 36 |
+
timeout_handler = timeout_handler or function() end
|
| 37 |
|
| 38 |
local curl = self:get_curl_cmd()
|
| 39 |
local api_url = self:get_api_url(url_path)
|
|
|
|
| 65 |
end
|
| 66 |
|
| 67 |
local status, output = exec_result:match("(%d+)\n(.*)")
|
| 68 |
+
status = tonumber(status)
|
| 69 |
|
| 70 |
+
if status == 28 then
|
| 71 |
+
app:debug("Curl timeout reached")
|
| 72 |
+
timeout_handler()
|
| 73 |
+
return nil
|
| 74 |
+
elseif status ~= 0 then
|
| 75 |
local msg = "Curl failed with status " .. status
|
| 76 |
app:debug(msg)
|
| 77 |
error_handler(msg)
|
|
|
|
| 197 |
|
| 198 |
local status = last_status_line:match("^HTTP/%d%.%d%s+(%d+)")
|
| 199 |
if not status then
|
| 200 |
+
return -1, 'Status not found in headers'
|
| 201 |
end
|
| 202 |
|
| 203 |
local body = {}
|
reascripts/ReaSpeech/source/ReaSpeechActionsUI.lua
CHANGED
|
@@ -60,9 +60,13 @@ function ReaSpeechActionsUI:render()
|
|
| 60 |
end
|
| 61 |
|
| 62 |
ImGui.SameLine(ctx)
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
end
|
| 65 |
-
ImGui.Dummy(ctx,0, 5)
|
| 66 |
end
|
| 67 |
|
| 68 |
function ReaSpeechActionsUI.make_job(media_item, take)
|
|
|
|
| 60 |
end
|
| 61 |
|
| 62 |
ImGui.SameLine(ctx)
|
| 63 |
+
local overlay = string.format("%.0f%%", progress * 100)
|
| 64 |
+
local status = self.worker:status()
|
| 65 |
+
if status then
|
| 66 |
+
overlay = overlay .. ' - ' .. status
|
| 67 |
+
end
|
| 68 |
+
ImGui.ProgressBar(ctx, progress, nil, nil, overlay)
|
| 69 |
end
|
|
|
|
| 70 |
end
|
| 71 |
|
| 72 |
function ReaSpeechActionsUI.make_job(media_item, take)
|
reascripts/ReaSpeech/source/ReaSpeechControlsUI.lua
CHANGED
|
@@ -7,178 +7,305 @@ ReaSpeechControlsUI.lua - UI elements for configuring ASR services
|
|
| 7 |
ReaSpeechControlsUI = Polo {
|
| 8 |
-- Copied from whisper.tokenizer.LANGUAGES
|
| 9 |
LANGUAGES = {
|
| 10 |
-
en = '
|
| 11 |
-
es = '
|
| 12 |
-
fr = '
|
| 13 |
-
tr = '
|
| 14 |
-
nl = '
|
| 15 |
-
it = '
|
| 16 |
-
fi = '
|
| 17 |
-
uk = '
|
| 18 |
-
cs = '
|
| 19 |
-
hu = '
|
| 20 |
-
th = '
|
| 21 |
-
bg = '
|
| 22 |
-
mi = '
|
| 23 |
-
sk = '
|
| 24 |
-
lv = '
|
| 25 |
-
az = '
|
| 26 |
-
et = '
|
| 27 |
-
eu = '
|
| 28 |
-
ne = '
|
| 29 |
-
kk = '
|
| 30 |
-
gl = '
|
| 31 |
-
si = '
|
| 32 |
-
yo = '
|
| 33 |
-
oc = '
|
| 34 |
-
tg = '
|
| 35 |
-
am = '
|
| 36 |
-
uz = '
|
| 37 |
-
ps = '
|
| 38 |
-
mt = '
|
| 39 |
-
my = '
|
| 40 |
-
mg = '
|
| 41 |
-
haw = '
|
| 42 |
-
ba = '
|
| 43 |
},
|
|
|
|
| 44 |
LANGUAGE_CODES = {},
|
| 45 |
-
DEFAULT_LANGUAGE = 'en',
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
function ReaSpeechControlsUI:init()
|
|
|
|
|
|
|
| 52 |
self.log_enable = false
|
| 53 |
self.log_debug = false
|
| 54 |
|
| 55 |
self.language = self.DEFAULT_LANGUAGE
|
| 56 |
self.translate = false
|
|
|
|
| 57 |
self.initial_prompt = ''
|
| 58 |
-
self.model_name =
|
|
|
|
|
|
|
|
|
|
| 59 |
end
|
| 60 |
|
| 61 |
function ReaSpeechControlsUI:get_request_data()
|
| 62 |
return {
|
| 63 |
language = self.language,
|
| 64 |
translate = self.translate,
|
|
|
|
| 65 |
initial_prompt = self.initial_prompt,
|
| 66 |
model_name = self.model_name,
|
|
|
|
| 67 |
}
|
| 68 |
end
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
end
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
end
|
| 82 |
|
| 83 |
-
ReaSpeechControlsUI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
function ReaSpeechControlsUI:render()
|
| 86 |
-
|
| 87 |
-
if
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
ImGui.TableSetupColumn(ctx, 'Inputs', ImGui.TableColumnFlags_WidthFixed())
|
| 92 |
-
-- first column
|
| 93 |
-
ImGui.TableNextColumn(ctx)
|
| 94 |
-
ImGui.SameLine(ctx, -10)
|
| 95 |
-
app.png_from_bytes('reaspeech-logo-small')
|
| 96 |
-
-- second column
|
| 97 |
-
ImGui.TableNextColumn(ctx)
|
| 98 |
-
-- start language selection
|
| 99 |
-
self:render_language_controls()
|
| 100 |
-
ImGui.Dummy(ctx,0, 10)
|
| 101 |
-
self:render_advanced_controls()
|
| 102 |
-
end)
|
| 103 |
-
ImGui.EndTable(ctx)
|
| 104 |
end
|
| 105 |
-
|
| 106 |
-
ImGui.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
app.png_from_bytes('heading-logo-tech-audio')
|
|
|
|
|
|
|
| 108 |
end
|
| 109 |
|
| 110 |
-
function ReaSpeechControlsUI:
|
| 111 |
-
if ImGui.
|
| 112 |
-
app:trap(function()
|
| 113 |
-
ImGui.
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
app:trap(function()
|
| 117 |
-
local combo_items = self.LANGUAGE_CODES
|
| 118 |
-
for _, combo_item in pairs(combo_items) do
|
| 119 |
-
local is_selected = (combo_item == self.language)
|
| 120 |
-
if ImGui.Selectable(ctx, self.LANGUAGES[combo_item], is_selected) then
|
| 121 |
-
self.language = combo_item
|
| 122 |
-
end
|
| 123 |
-
end
|
| 124 |
end)
|
| 125 |
-
ImGui.
|
| 126 |
end
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
end
|
| 133 |
end)
|
| 134 |
-
|
| 135 |
-
ImGui.TreePop(ctx)
|
| 136 |
end
|
| 137 |
end
|
| 138 |
|
| 139 |
-
function ReaSpeechControlsUI:
|
| 140 |
-
|
|
|
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
ImGui.PushItemWidth(ctx, self.LARGE_ITEM_WIDTH)
|
| 148 |
-
app:trap(function ()
|
| 149 |
-
rv, value = ImGui.InputText(ctx, 'initial prompt', self.initial_prompt)
|
| 150 |
-
if rv then
|
| 151 |
-
self.initial_prompt = value
|
| 152 |
-
end
|
| 153 |
-
end)
|
| 154 |
-
ImGui.PopItemWidth(ctx)
|
| 155 |
-
|
| 156 |
-
ImGui.SameLine(ctx)
|
| 157 |
-
ImGui.PushItemWidth(ctx, 100)
|
| 158 |
-
app:trap(function ()
|
| 159 |
-
rv, value = ImGui.InputTextWithHint(ctx, 'model name', self.model_name or "<default>")
|
| 160 |
-
if rv then
|
| 161 |
-
self.model_name = value
|
| 162 |
-
end
|
| 163 |
-
end)
|
| 164 |
-
ImGui.PopItemWidth(ctx)
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
| 177 |
end
|
| 178 |
end
|
| 179 |
end)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
-
|
| 182 |
-
|
|
|
|
| 183 |
end
|
| 184 |
end
|
|
|
|
| 7 |
ReaSpeechControlsUI = Polo {
|
| 8 |
-- Copied from whisper.tokenizer.LANGUAGES
|
| 9 |
LANGUAGES = {
|
| 10 |
+
en = 'English', zh = 'Chinese', de = 'German',
|
| 11 |
+
es = 'Spanish', ru = 'Russian', ko = 'Korean',
|
| 12 |
+
fr = 'French', ja = 'Japanese', pt = 'Portuguese',
|
| 13 |
+
tr = 'Turkish', pl = 'Polish', ca = 'Catalan',
|
| 14 |
+
nl = 'Dutch', ar = 'Arabic', sv = 'Swedish',
|
| 15 |
+
it = 'Italian', id = 'Indonesian', hi = 'Hindi',
|
| 16 |
+
fi = 'Finnish', vi = 'Vietnamese', he = 'Hebrew',
|
| 17 |
+
uk = 'Ukrainian', el = 'Greek', ms = 'Malay',
|
| 18 |
+
cs = 'Czech', ro = 'Romanian', da = 'Danish',
|
| 19 |
+
hu = 'Hungarian', ta = 'Tamil', no = 'Norwegian',
|
| 20 |
+
th = 'Thai', ur = 'Urdu', hr = 'Croatian',
|
| 21 |
+
bg = 'Bulgarian', lt = 'Lithuanian', la = 'Latin',
|
| 22 |
+
mi = 'Maori', ml = 'Malayalam', cy = 'Welsh',
|
| 23 |
+
sk = 'Slovak', te = 'Telugu', fa = 'Persian',
|
| 24 |
+
lv = 'Latvian', bn = 'Bengali', sr = 'Serbian',
|
| 25 |
+
az = 'Azerbaijani', sl = 'Slovenian', kn = 'Kannada',
|
| 26 |
+
et = 'Estonian', mk = 'Macedonian', br = 'Breton',
|
| 27 |
+
eu = 'Basque', is = 'Icelandic', hy = 'Armenian',
|
| 28 |
+
ne = 'Nepali', mn = 'Mongolian', bs = 'Bosnian',
|
| 29 |
+
kk = 'Kazakh', sq = 'Albanian', sw = 'Swahili',
|
| 30 |
+
gl = 'Galician', mr = 'Marathi', pa = 'Punjabi',
|
| 31 |
+
si = 'Sinhala', km = 'Khmer', sn = 'Shona',
|
| 32 |
+
yo = 'Yoruba', so = 'Somali', af = 'Afrikaans',
|
| 33 |
+
oc = 'Occitan', ka = 'Georgian', be = 'Belarusian',
|
| 34 |
+
tg = 'Tajik', sd = 'Sindhi', gu = 'Gujarati',
|
| 35 |
+
am = 'Amharic', yi = 'Yiddish', lo = 'Lao',
|
| 36 |
+
uz = 'Uzbek', fo = 'Faroese', ht = 'Haitian Creole',
|
| 37 |
+
ps = 'Pashto', tk = 'Turkmen', nn = 'Nynorsk',
|
| 38 |
+
mt = 'Maltese', sa = 'Sanskrit', lb = 'Luxembourgish',
|
| 39 |
+
my = 'Myanmar', bo = 'Tibetan', tl = 'Tagalog',
|
| 40 |
+
mg = 'Malagasy', as = 'Assamese', tt = 'Tatar',
|
| 41 |
+
haw = 'Hawaiian', ln = 'Lingala', ha = 'Hausa',
|
| 42 |
+
ba = 'Bashkir', jw = 'Javanese', su = 'Sundanese'
|
| 43 |
},
|
| 44 |
+
|
| 45 |
LANGUAGE_CODES = {},
|
|
|
|
| 46 |
|
| 47 |
+
DEFAULT_LANGUAGE = '',
|
| 48 |
+
DEFAULT_MODEL_NAME = 'small',
|
| 49 |
+
|
| 50 |
+
SIMPLE_MODEL_SIZES = {
|
| 51 |
+
{'Small', 'small'},
|
| 52 |
+
{'Medium', 'medium'},
|
| 53 |
+
{'Large', 'distil-large-v3'},
|
| 54 |
+
},
|
| 55 |
+
|
| 56 |
+
COLUMN_PADDING = 15,
|
| 57 |
+
MARGIN_BOTTOM = 5,
|
| 58 |
+
MARGIN_LEFT = 115,
|
| 59 |
+
MARGIN_RIGHT = 0,
|
| 60 |
+
NARROW_COLUMN_WIDTH = 150,
|
| 61 |
}
|
| 62 |
|
| 63 |
+
ReaSpeechControlsUI._init_languages = function ()
|
| 64 |
+
for code, _ in pairs(ReaSpeechControlsUI.LANGUAGES) do
|
| 65 |
+
table.insert(ReaSpeechControlsUI.LANGUAGE_CODES, code)
|
| 66 |
+
end
|
| 67 |
+
|
| 68 |
+
table.sort(ReaSpeechControlsUI.LANGUAGE_CODES, function (a, b)
|
| 69 |
+
return ReaSpeechControlsUI.LANGUAGES[a] < ReaSpeechControlsUI.LANGUAGES[b]
|
| 70 |
+
end)
|
| 71 |
+
|
| 72 |
+
table.insert(ReaSpeechControlsUI.LANGUAGE_CODES, 1, '')
|
| 73 |
+
ReaSpeechControlsUI.LANGUAGES[''] = 'Detect'
|
| 74 |
+
end
|
| 75 |
+
|
| 76 |
+
ReaSpeechControlsUI._init_languages()
|
| 77 |
+
|
| 78 |
function ReaSpeechControlsUI:init()
|
| 79 |
+
self.tab = 'simple'
|
| 80 |
+
|
| 81 |
self.log_enable = false
|
| 82 |
self.log_debug = false
|
| 83 |
|
| 84 |
self.language = self.DEFAULT_LANGUAGE
|
| 85 |
self.translate = false
|
| 86 |
+
self.hotwords = ''
|
| 87 |
self.initial_prompt = ''
|
| 88 |
+
self.model_name = self.DEFAULT_MODEL_NAME
|
| 89 |
+
self.vad_filter = true
|
| 90 |
+
|
| 91 |
+
self:init_layouts()
|
| 92 |
end
|
| 93 |
|
| 94 |
function ReaSpeechControlsUI:get_request_data()
|
| 95 |
return {
|
| 96 |
language = self.language,
|
| 97 |
translate = self.translate,
|
| 98 |
+
hotwords = self.hotwords,
|
| 99 |
initial_prompt = self.initial_prompt,
|
| 100 |
model_name = self.model_name,
|
| 101 |
+
vad_filter = self.vad_filter,
|
| 102 |
}
|
| 103 |
end
|
| 104 |
|
| 105 |
+
function ReaSpeechControlsUI:init_layouts()
|
| 106 |
+
self:init_simple_layouts()
|
| 107 |
+
self:init_advanced_layouts()
|
| 108 |
+
end
|
| 109 |
+
|
| 110 |
+
function ReaSpeechControlsUI:init_simple_layouts()
|
| 111 |
+
local with_button_color = function (selected, f)
|
| 112 |
+
if selected then
|
| 113 |
+
ImGui.PushStyleColor(ctx, ImGui.Col_Button(), Theme.colors.dark_gray_translucent)
|
| 114 |
+
app:trap(f)
|
| 115 |
+
ImGui.PopStyleColor(ctx)
|
| 116 |
+
else
|
| 117 |
+
f()
|
| 118 |
+
end
|
| 119 |
end
|
| 120 |
|
| 121 |
+
self.model_sizes_layout = ColumnLayout.new {
|
| 122 |
+
column_padding = self.COLUMN_PADDING,
|
| 123 |
+
margin_bottom = self.MARGIN_BOTTOM,
|
| 124 |
+
margin_left = self.MARGIN_LEFT,
|
| 125 |
+
margin_right = self.MARGIN_RIGHT,
|
| 126 |
+
num_columns = #self.SIMPLE_MODEL_SIZES,
|
| 127 |
|
| 128 |
+
render_column = function (column)
|
| 129 |
+
self:render_input_label(column.num == 1 and 'Model Size' or '')
|
| 130 |
+
local label, model_name = table.unpack(self.SIMPLE_MODEL_SIZES[column.num])
|
| 131 |
+
with_button_color(self.model_name == model_name, function ()
|
| 132 |
+
if ImGui.Button(ctx, label, column.width) then
|
| 133 |
+
self.model_name = model_name
|
| 134 |
+
end
|
| 135 |
+
end)
|
| 136 |
+
end
|
| 137 |
+
}
|
| 138 |
end
|
| 139 |
|
| 140 |
+
function ReaSpeechControlsUI:init_advanced_layouts()
|
| 141 |
+
local renderers = {
|
| 142 |
+
{self.render_model_name, self.render_hotwords, self.render_language},
|
| 143 |
+
{self.render_options, self.render_initial_prompt, self.render_logging},
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
self.advanced_layouts = {}
|
| 147 |
+
|
| 148 |
+
for row = 1, #renderers do
|
| 149 |
+
self.advanced_layouts[row] = ColumnLayout.new {
|
| 150 |
+
column_padding = self.COLUMN_PADDING,
|
| 151 |
+
margin_bottom = self.MARGIN_BOTTOM,
|
| 152 |
+
margin_left = self.MARGIN_LEFT,
|
| 153 |
+
margin_right = self.MARGIN_RIGHT,
|
| 154 |
+
num_columns = #renderers[row],
|
| 155 |
+
|
| 156 |
+
render_column = function (column)
|
| 157 |
+
ImGui.PushItemWidth(ctx, column.width)
|
| 158 |
+
app:trap(function () renderers[row][column.num](self, column) end)
|
| 159 |
+
ImGui.PopItemWidth(ctx)
|
| 160 |
+
end
|
| 161 |
+
}
|
| 162 |
+
end
|
| 163 |
+
end
|
| 164 |
|
| 165 |
function ReaSpeechControlsUI:render()
|
| 166 |
+
self:render_heading()
|
| 167 |
+
if self.tab == 'advanced' then
|
| 168 |
+
self:render_advanced()
|
| 169 |
+
else
|
| 170 |
+
self:render_simple()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
end
|
| 172 |
+
ImGui.Separator(ctx)
|
| 173 |
+
ImGui.Dummy(ctx, 0, 5)
|
| 174 |
+
end
|
| 175 |
+
|
| 176 |
+
function ReaSpeechControlsUI:render_heading()
|
| 177 |
+
local init_x, init_y = ImGui.GetCursorPos(ctx)
|
| 178 |
+
|
| 179 |
+
ImGui.SetCursorPosX(ctx, init_x - 20)
|
| 180 |
+
app.png_from_bytes('reaspeech-logo-small')
|
| 181 |
+
|
| 182 |
+
ImGui.SetCursorPos(ctx, init_x + self.MARGIN_LEFT + 2, init_y)
|
| 183 |
+
self:render_tabs()
|
| 184 |
+
|
| 185 |
+
ImGui.SetCursorPos(ctx, ImGui.GetWindowWidth(ctx) - 55, init_y)
|
| 186 |
app.png_from_bytes('heading-logo-tech-audio')
|
| 187 |
+
|
| 188 |
+
ImGui.SetCursorPos(ctx, init_x, init_y + 40)
|
| 189 |
end
|
| 190 |
|
| 191 |
+
function ReaSpeechControlsUI:render_tabs()
|
| 192 |
+
if ImGui.BeginTabBar(ctx, '##tabs', ImGui.TabBarFlags_None()) then
|
| 193 |
+
app:trap(function ()
|
| 194 |
+
if ImGui.BeginTabItem(ctx, 'Simple') then
|
| 195 |
+
app:trap(function ()
|
| 196 |
+
self.tab = 'simple'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
end)
|
| 198 |
+
ImGui.EndTabItem(ctx)
|
| 199 |
end
|
| 200 |
+
if ImGui.BeginTabItem(ctx, 'Advanced') then
|
| 201 |
+
app:trap(function ()
|
| 202 |
+
self.tab = 'advanced'
|
| 203 |
+
end)
|
| 204 |
+
ImGui.EndTabItem(ctx)
|
| 205 |
end
|
| 206 |
end)
|
| 207 |
+
ImGui.EndTabBar(ctx)
|
|
|
|
| 208 |
end
|
| 209 |
end
|
| 210 |
|
| 211 |
+
function ReaSpeechControlsUI:render_simple()
|
| 212 |
+
self:render_model_sizes()
|
| 213 |
+
end
|
| 214 |
|
| 215 |
+
function ReaSpeechControlsUI:render_advanced()
|
| 216 |
+
for row = 1, #self.advanced_layouts do
|
| 217 |
+
self.advanced_layouts[row]:render()
|
| 218 |
+
end
|
| 219 |
+
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
+
function ReaSpeechControlsUI:render_input_label(text)
|
| 222 |
+
ImGui.Text(ctx, text)
|
| 223 |
+
ImGui.Dummy(ctx, 0, 0)
|
| 224 |
+
end
|
| 225 |
+
|
| 226 |
+
function ReaSpeechControlsUI:render_language(column)
|
| 227 |
+
self:render_input_label('Language')
|
| 228 |
|
| 229 |
+
if ImGui.BeginCombo(ctx, "##language", self.LANGUAGES[self.language]) then
|
| 230 |
+
app:trap(function()
|
| 231 |
+
local combo_items = self.LANGUAGE_CODES
|
| 232 |
+
for _, combo_item in pairs(combo_items) do
|
| 233 |
+
local is_selected = (combo_item == self.language)
|
| 234 |
+
if ImGui.Selectable(ctx, self.LANGUAGES[combo_item], is_selected) then
|
| 235 |
+
self.language = combo_item
|
| 236 |
end
|
| 237 |
end
|
| 238 |
end)
|
| 239 |
+
ImGui.EndCombo(ctx)
|
| 240 |
+
end
|
| 241 |
+
|
| 242 |
+
local translate_label = "Translate to English"
|
| 243 |
+
if column.width < self.NARROW_COLUMN_WIDTH then
|
| 244 |
+
translate_label = "Translate"
|
| 245 |
+
end
|
| 246 |
+
local rv, value = ImGui.Checkbox(ctx, translate_label, self.translate)
|
| 247 |
+
if rv then
|
| 248 |
+
self.translate = value
|
| 249 |
+
end
|
| 250 |
+
end
|
| 251 |
+
|
| 252 |
+
function ReaSpeechControlsUI:render_model_name()
|
| 253 |
+
self:render_input_label('Model Name')
|
| 254 |
+
|
| 255 |
+
local rv, value = ImGui.InputTextWithHint(ctx, '##model_name', self.model_name or "<default>")
|
| 256 |
+
if rv then
|
| 257 |
+
self.model_name = value
|
| 258 |
+
end
|
| 259 |
+
end
|
| 260 |
+
|
| 261 |
+
function ReaSpeechControlsUI:render_model_sizes()
|
| 262 |
+
self.model_sizes_layout:render()
|
| 263 |
+
end
|
| 264 |
+
|
| 265 |
+
function ReaSpeechControlsUI:render_hotwords()
|
| 266 |
+
self:render_input_label('Hot Words')
|
| 267 |
+
|
| 268 |
+
local rv, value = ImGui.InputText(ctx, '##hotwords', self.hotwords)
|
| 269 |
+
if rv then
|
| 270 |
+
self.hotwords = value
|
| 271 |
+
end
|
| 272 |
+
end
|
| 273 |
+
|
| 274 |
+
function ReaSpeechControlsUI:render_options(column)
|
| 275 |
+
self:render_input_label('Options')
|
| 276 |
+
|
| 277 |
+
local vad_label = "Voice Activity Detection"
|
| 278 |
+
if column.width < self.NARROW_COLUMN_WIDTH then
|
| 279 |
+
vad_label = "VAD"
|
| 280 |
+
end
|
| 281 |
+
local rv, value = ImGui.Checkbox(ctx, vad_label, self.vad_filter)
|
| 282 |
+
if rv then
|
| 283 |
+
self.vad_filter = value
|
| 284 |
+
end
|
| 285 |
+
end
|
| 286 |
+
|
| 287 |
+
function ReaSpeechControlsUI:render_logging()
|
| 288 |
+
self:render_input_label('Logging')
|
| 289 |
+
|
| 290 |
+
local rv, value = ImGui.Checkbox(ctx, "Enable", self.log_enable)
|
| 291 |
+
if rv then
|
| 292 |
+
self.log_enable = value
|
| 293 |
+
end
|
| 294 |
+
|
| 295 |
+
if self.log_enable then
|
| 296 |
+
ImGui.SameLine(ctx)
|
| 297 |
+
rv, value = ImGui.Checkbox(ctx, "Debug", self.log_debug)
|
| 298 |
+
if rv then
|
| 299 |
+
self.log_debug = value
|
| 300 |
+
end
|
| 301 |
+
end
|
| 302 |
+
end
|
| 303 |
+
|
| 304 |
+
function ReaSpeechControlsUI:render_initial_prompt()
|
| 305 |
+
self:render_input_label('Initial Prompt')
|
| 306 |
|
| 307 |
+
rv, value = ImGui.InputText(ctx, '##initial_prompt', self.initial_prompt)
|
| 308 |
+
if rv then
|
| 309 |
+
self.initial_prompt = value
|
| 310 |
end
|
| 311 |
end
|
reascripts/ReaSpeech/source/ReaSpeechWorker.lua
CHANGED
|
@@ -89,6 +89,12 @@ function ReaSpeechWorker:progress()
|
|
| 89 |
return completed_job_count / job_count
|
| 90 |
end
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
function ReaSpeechWorker:cancel()
|
| 93 |
if self.active_job then
|
| 94 |
if self.active_job.job and self.active_job.job.job_id then
|
|
@@ -123,7 +129,7 @@ function ReaSpeechWorker:handle_request(request)
|
|
| 123 |
task = request.translate and 'translate' or 'transcribe',
|
| 124 |
output = 'json',
|
| 125 |
use_async = 'true',
|
| 126 |
-
vad_filter = 'true',
|
| 127 |
word_timestamps = 'true',
|
| 128 |
model_name = request.model_name,
|
| 129 |
}
|
|
@@ -132,6 +138,10 @@ function ReaSpeechWorker:handle_request(request)
|
|
| 132 |
data.language = request.language
|
| 133 |
end
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
if request.initial_prompt and request.initial_prompt ~= '' then
|
| 136 |
data.initial_prompt = request.initial_prompt
|
| 137 |
end
|
|
@@ -156,6 +166,7 @@ function ReaSpeechWorker:handle_job_status(active_job, response)
|
|
| 156 |
end
|
| 157 |
|
| 158 |
active_job.job.job_id = response.job_id
|
|
|
|
| 159 |
|
| 160 |
if not response.job_status then
|
| 161 |
return false
|
|
@@ -252,21 +263,23 @@ function ReaSpeechWorker:handle_response_json(output_file, sentinel_file, succes
|
|
| 252 |
|
| 253 |
local f = io.open(output_file, 'r')
|
| 254 |
if not f then
|
| 255 |
-
fail_f("Couldn't open
|
|
|
|
| 256 |
return
|
| 257 |
end
|
| 258 |
|
| 259 |
local http_status, body = ReaSpeechAPI.http_status_and_body(f)
|
| 260 |
f:close()
|
| 261 |
|
| 262 |
-
if
|
| 263 |
-
|
| 264 |
return
|
| 265 |
end
|
| 266 |
|
|
|
|
|
|
|
|
|
|
| 267 |
if http_status ~= 200 then
|
| 268 |
-
Tempfile:remove(sentinel_file)
|
| 269 |
-
Tempfile:remove(output_file)
|
| 270 |
local msg = "Server responded with status " .. http_status
|
| 271 |
fail_f(msg)
|
| 272 |
app:log(msg)
|
|
@@ -274,15 +287,18 @@ function ReaSpeechWorker:handle_response_json(output_file, sentinel_file, succes
|
|
| 274 |
return
|
| 275 |
end
|
| 276 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
local response = nil
|
| 278 |
if app:trap(function ()
|
| 279 |
response = json.decode(body)
|
| 280 |
end) then
|
| 281 |
-
Tempfile:remove(sentinel_file)
|
| 282 |
-
Tempfile:remove(output_file)
|
| 283 |
success_f(response)
|
| 284 |
else
|
| 285 |
-
|
| 286 |
end
|
| 287 |
end
|
| 288 |
|
|
|
|
| 89 |
return completed_job_count / job_count
|
| 90 |
end
|
| 91 |
|
| 92 |
+
function ReaSpeechWorker:status()
|
| 93 |
+
if self.active_job and self.active_job.job then
|
| 94 |
+
return self.active_job.job.job_status
|
| 95 |
+
end
|
| 96 |
+
end
|
| 97 |
+
|
| 98 |
function ReaSpeechWorker:cancel()
|
| 99 |
if self.active_job then
|
| 100 |
if self.active_job.job and self.active_job.job.job_id then
|
|
|
|
| 129 |
task = request.translate and 'translate' or 'transcribe',
|
| 130 |
output = 'json',
|
| 131 |
use_async = 'true',
|
| 132 |
+
vad_filter = request.vad_filter and 'true' or 'false',
|
| 133 |
word_timestamps = 'true',
|
| 134 |
model_name = request.model_name,
|
| 135 |
}
|
|
|
|
| 138 |
data.language = request.language
|
| 139 |
end
|
| 140 |
|
| 141 |
+
if request.hotwords and request.hotwords ~= '' then
|
| 142 |
+
data.hotwords = request.hotwords
|
| 143 |
+
end
|
| 144 |
+
|
| 145 |
if request.initial_prompt and request.initial_prompt ~= '' then
|
| 146 |
data.initial_prompt = request.initial_prompt
|
| 147 |
end
|
|
|
|
| 166 |
end
|
| 167 |
|
| 168 |
active_job.job.job_id = response.job_id
|
| 169 |
+
active_job.job.job_status = response.job_status
|
| 170 |
|
| 171 |
if not response.job_status then
|
| 172 |
return false
|
|
|
|
| 263 |
|
| 264 |
local f = io.open(output_file, 'r')
|
| 265 |
if not f then
|
| 266 |
+
fail_f("Couldn't open output file: " .. tostring(output_file))
|
| 267 |
+
Tempfile:remove(sentinel_file)
|
| 268 |
return
|
| 269 |
end
|
| 270 |
|
| 271 |
local http_status, body = ReaSpeechAPI.http_status_and_body(f)
|
| 272 |
f:close()
|
| 273 |
|
| 274 |
+
if http_status == -1 then
|
| 275 |
+
app:debug(body .. ", trying again later")
|
| 276 |
return
|
| 277 |
end
|
| 278 |
|
| 279 |
+
Tempfile:remove(output_file)
|
| 280 |
+
Tempfile:remove(sentinel_file)
|
| 281 |
+
|
| 282 |
if http_status ~= 200 then
|
|
|
|
|
|
|
| 283 |
local msg = "Server responded with status " .. http_status
|
| 284 |
fail_f(msg)
|
| 285 |
app:log(msg)
|
|
|
|
| 287 |
return
|
| 288 |
end
|
| 289 |
|
| 290 |
+
if #body < 1 then
|
| 291 |
+
fail_f("Empty response from server")
|
| 292 |
+
return
|
| 293 |
+
end
|
| 294 |
+
|
| 295 |
local response = nil
|
| 296 |
if app:trap(function ()
|
| 297 |
response = json.decode(body)
|
| 298 |
end) then
|
|
|
|
|
|
|
| 299 |
success_f(response)
|
| 300 |
else
|
| 301 |
+
fail_f("Error parsing response JSON")
|
| 302 |
end
|
| 303 |
end
|
| 304 |
|
reascripts/ReaSpeech/source/Theme.lua
CHANGED
|
@@ -33,7 +33,10 @@ function Theme.init()
|
|
| 33 |
{ ImGui.Col_CheckMark(), Theme.colors.pink_opaque },
|
| 34 |
{ ImGui.Col_HeaderHovered(), Theme.colors.dark_gray_semi_opaque },
|
| 35 |
{ ImGui.Col_HeaderActive(), Theme.colors.dark_gray_semi_transparent },
|
| 36 |
-
{ ImGui.Col_Header(), Theme.colors.dark_gray_semi_opaque }
|
|
|
|
|
|
|
|
|
|
| 37 |
},
|
| 38 |
|
| 39 |
styles = {
|
|
@@ -47,4 +50,4 @@ function Theme.init()
|
|
| 47 |
})
|
| 48 |
|
| 49 |
return Theme.theme
|
| 50 |
-
end
|
|
|
|
| 33 |
{ ImGui.Col_CheckMark(), Theme.colors.pink_opaque },
|
| 34 |
{ ImGui.Col_HeaderHovered(), Theme.colors.dark_gray_semi_opaque },
|
| 35 |
{ ImGui.Col_HeaderActive(), Theme.colors.dark_gray_semi_transparent },
|
| 36 |
+
{ ImGui.Col_Header(), Theme.colors.dark_gray_semi_opaque },
|
| 37 |
+
{ ImGui.Col_Tab(), Theme.colors.dark_gray_opaque },
|
| 38 |
+
{ ImGui.Col_TabActive(), Theme.colors.medium_gray_opaque },
|
| 39 |
+
{ ImGui.Col_TabHovered(), Theme.colors.dark_gray_translucent },
|
| 40 |
},
|
| 41 |
|
| 42 |
styles = {
|
|
|
|
| 50 |
})
|
| 51 |
|
| 52 |
return Theme.theme
|
| 53 |
+
end
|
reascripts/ReaSpeech/tests/TestColumnLayout.lua
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package.path = '../common/libs/?.lua;../common/vendor/?.lua;' .. package.path
|
| 2 |
+
|
| 3 |
+
local lu = require('luaunit')
|
| 4 |
+
|
| 5 |
+
require('Polo')
|
| 6 |
+
|
| 7 |
+
require('source/ColumnLayout')
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
TestColumnLayout = {
|
| 12 |
+
AVAIL_WIDTH = 100
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
function TestColumnLayout:stub(layout)
|
| 16 |
+
layout._column_gap_calls = {}
|
| 17 |
+
layout._column_gap = function (self, padding)
|
| 18 |
+
table.insert(self._column_gap_calls, {padding})
|
| 19 |
+
end
|
| 20 |
+
|
| 21 |
+
layout._get_avail_width_calls = 0
|
| 22 |
+
layout._get_avail_width = function (self)
|
| 23 |
+
self._get_avail_width_calls = self._get_avail_width_calls + 1
|
| 24 |
+
return TestColumnLayout.AVAIL_WIDTH
|
| 25 |
+
end
|
| 26 |
+
|
| 27 |
+
layout._horiz_margin_calls = {}
|
| 28 |
+
layout._horiz_margin = function (self, margin)
|
| 29 |
+
table.insert(self._horiz_margin_calls, {margin})
|
| 30 |
+
end
|
| 31 |
+
|
| 32 |
+
layout._vert_margin_calls = {}
|
| 33 |
+
layout._vert_margin = function (self, margin, width)
|
| 34 |
+
table.insert(self._vert_margin_calls, {margin, width})
|
| 35 |
+
end
|
| 36 |
+
|
| 37 |
+
layout._with_group_calls = 0
|
| 38 |
+
layout._with_group = function (self, f)
|
| 39 |
+
self._with_group_calls = self._with_group_calls + 1
|
| 40 |
+
f()
|
| 41 |
+
end
|
| 42 |
+
end
|
| 43 |
+
|
| 44 |
+
function TestColumnLayout:testInitDefault()
|
| 45 |
+
local layout = ColumnLayout.new {
|
| 46 |
+
render_column = function () end
|
| 47 |
+
}
|
| 48 |
+
lu.assertEquals(layout.column_padding, ColumnLayout.DEFAULT_COLUMN_PADDING)
|
| 49 |
+
lu.assertEquals(layout.margin_bottom, 0)
|
| 50 |
+
lu.assertEquals(layout.margin_left, 0)
|
| 51 |
+
lu.assertEquals(layout.margin_right, 0)
|
| 52 |
+
lu.assertEquals(layout.margin_top, 0)
|
| 53 |
+
lu.assertEquals(layout.num_columns, ColumnLayout.DEFAULT_NUM_COLUMNS)
|
| 54 |
+
lu.assertEquals(layout.width, 0)
|
| 55 |
+
end
|
| 56 |
+
|
| 57 |
+
function TestColumnLayout:testRender()
|
| 58 |
+
local column_padding = 20
|
| 59 |
+
local margin_bottom = 15
|
| 60 |
+
local margin_left = 10
|
| 61 |
+
local margin_right = 30
|
| 62 |
+
local margin_top = 5
|
| 63 |
+
local num_columns = 2
|
| 64 |
+
|
| 65 |
+
local expected_column_width = (
|
| 66 |
+
self.AVAIL_WIDTH
|
| 67 |
+
- margin_left
|
| 68 |
+
- margin_right
|
| 69 |
+
- column_padding
|
| 70 |
+
) / num_columns
|
| 71 |
+
|
| 72 |
+
local render_column_calls = {}
|
| 73 |
+
|
| 74 |
+
local layout = ColumnLayout.new {
|
| 75 |
+
column_padding = column_padding,
|
| 76 |
+
margin_bottom = margin_bottom,
|
| 77 |
+
margin_left = margin_left,
|
| 78 |
+
margin_right = margin_right,
|
| 79 |
+
margin_top = margin_top,
|
| 80 |
+
num_columns = num_columns,
|
| 81 |
+
render_column = function (column)
|
| 82 |
+
table.insert(render_column_calls, {column})
|
| 83 |
+
end
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
self:stub(layout)
|
| 87 |
+
layout:render()
|
| 88 |
+
|
| 89 |
+
lu.assertEquals(layout._column_gap_calls, {{column_padding}})
|
| 90 |
+
lu.assertEquals(layout._get_avail_width_calls, 1)
|
| 91 |
+
lu.assertEquals(layout._horiz_margin_calls, {{margin_left}})
|
| 92 |
+
lu.assertEquals(layout._vert_margin_calls, {
|
| 93 |
+
{margin_top, expected_column_width},
|
| 94 |
+
{margin_bottom, expected_column_width},
|
| 95 |
+
{margin_top, expected_column_width},
|
| 96 |
+
{margin_bottom, expected_column_width},
|
| 97 |
+
})
|
| 98 |
+
lu.assertEquals(layout._with_group_calls, num_columns)
|
| 99 |
+
|
| 100 |
+
lu.assertEquals(render_column_calls, {
|
| 101 |
+
{{num = 1, width = expected_column_width}},
|
| 102 |
+
{{num = 2, width = expected_column_width}},
|
| 103 |
+
})
|
| 104 |
+
end
|
| 105 |
+
|
| 106 |
+
function TestColumnLayout:testRenderWithWidth()
|
| 107 |
+
local column_padding = 15
|
| 108 |
+
local margin_bottom = 10
|
| 109 |
+
local margin_left = 5
|
| 110 |
+
local margin_right = 20
|
| 111 |
+
local margin_top = 25
|
| 112 |
+
local num_columns = 3
|
| 113 |
+
local width = 205
|
| 114 |
+
|
| 115 |
+
local expected_column_width = 50
|
| 116 |
+
|
| 117 |
+
local render_column_calls = {}
|
| 118 |
+
|
| 119 |
+
local layout = ColumnLayout.new {
|
| 120 |
+
column_padding = column_padding,
|
| 121 |
+
margin_bottom = margin_bottom,
|
| 122 |
+
margin_left = margin_left,
|
| 123 |
+
margin_right = margin_right,
|
| 124 |
+
margin_top = margin_top,
|
| 125 |
+
num_columns = num_columns,
|
| 126 |
+
width = width,
|
| 127 |
+
render_column = function (column)
|
| 128 |
+
table.insert(render_column_calls, {column})
|
| 129 |
+
end
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
self:stub(layout)
|
| 133 |
+
layout:render()
|
| 134 |
+
|
| 135 |
+
lu.assertEquals(layout._column_gap_calls, {{column_padding}, {column_padding}})
|
| 136 |
+
lu.assertEquals(layout._get_avail_width_calls, 0)
|
| 137 |
+
lu.assertEquals(layout._horiz_margin_calls, {{margin_left}})
|
| 138 |
+
lu.assertEquals(layout._vert_margin_calls, {
|
| 139 |
+
{margin_top, expected_column_width},
|
| 140 |
+
{margin_bottom, expected_column_width},
|
| 141 |
+
{margin_top, expected_column_width},
|
| 142 |
+
{margin_bottom, expected_column_width},
|
| 143 |
+
{margin_top, expected_column_width},
|
| 144 |
+
{margin_bottom, expected_column_width},
|
| 145 |
+
})
|
| 146 |
+
lu.assertEquals(layout._with_group_calls, num_columns)
|
| 147 |
+
|
| 148 |
+
lu.assertEquals(render_column_calls, {
|
| 149 |
+
{{num = 1, width = expected_column_width}},
|
| 150 |
+
{{num = 2, width = expected_column_width}},
|
| 151 |
+
{{num = 3, width = expected_column_width}},
|
| 152 |
+
})
|
| 153 |
+
end
|
| 154 |
+
|
| 155 |
+
function TestColumnLayout:testRenderNested()
|
| 156 |
+
local inner_renders = {}
|
| 157 |
+
local outer_renders = {}
|
| 158 |
+
|
| 159 |
+
local outer_layout = ColumnLayout.new {
|
| 160 |
+
column_padding = 0,
|
| 161 |
+
num_columns = 2,
|
| 162 |
+
width = 60,
|
| 163 |
+
|
| 164 |
+
render_column = function (outer_column)
|
| 165 |
+
table.insert(outer_renders, outer_column)
|
| 166 |
+
|
| 167 |
+
local inner_layout = ColumnLayout.new {
|
| 168 |
+
column_padding = 0,
|
| 169 |
+
num_columns = 3,
|
| 170 |
+
width = outer_column.width,
|
| 171 |
+
|
| 172 |
+
render_column = function (inner_column)
|
| 173 |
+
table.insert(inner_renders, inner_column)
|
| 174 |
+
end
|
| 175 |
+
}
|
| 176 |
+
self:stub(inner_layout)
|
| 177 |
+
|
| 178 |
+
inner_layout:render()
|
| 179 |
+
end
|
| 180 |
+
}
|
| 181 |
+
self:stub(outer_layout)
|
| 182 |
+
|
| 183 |
+
outer_layout:render()
|
| 184 |
+
|
| 185 |
+
lu.assertEquals(inner_renders, {
|
| 186 |
+
{num=1, width=10},
|
| 187 |
+
{num=2, width=10},
|
| 188 |
+
{num=3, width=10},
|
| 189 |
+
{num=1, width=10},
|
| 190 |
+
{num=2, width=10},
|
| 191 |
+
{num=3, width=10},
|
| 192 |
+
})
|
| 193 |
+
|
| 194 |
+
lu.assertEquals(outer_renders, {
|
| 195 |
+
{num=1, width=30},
|
| 196 |
+
{num=2, width=30},
|
| 197 |
+
})
|
| 198 |
+
end
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
os.exit(lu.LuaUnit.run())
|
reascripts/ReaSpeech/tests/TestReaSpeechUI.lua
CHANGED
|
@@ -8,6 +8,7 @@ require('ReaUtil')
|
|
| 8 |
require('mock_reaper')
|
| 9 |
|
| 10 |
require('source/AlertPopup')
|
|
|
|
| 11 |
require('source/ReaSpeechActionsUI')
|
| 12 |
require('source/ReaSpeechAPI')
|
| 13 |
require('source/ReaSpeechProductActivation')
|
|
|
|
| 8 |
require('mock_reaper')
|
| 9 |
|
| 10 |
require('source/AlertPopup')
|
| 11 |
+
require('source/ColumnLayout')
|
| 12 |
require('source/ReaSpeechActionsUI')
|
| 13 |
require('source/ReaSpeechAPI')
|
| 14 |
require('source/ReaSpeechProductActivation')
|