feat: improve library and audiobook workflow
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""Real GPU/API smoke test, run on CMP. Uses and revokes a disposable device."""
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import time
|
||||
import zipfile
|
||||
import httpx
|
||||
from device_auth import DeviceStore
|
||||
|
||||
root = Path(os.environ.get('AUDIOBOOK_SERVICE_ROOT', '/home/sevenhill/apps/aletheia-audiobook'))
|
||||
base = 'https://argus.kusoft.xyz/aletheia-tts'
|
||||
with httpx.Client(timeout=60) as client:
|
||||
for attempt in range(30):
|
||||
try:
|
||||
health = client.get(base + '/health')
|
||||
if health.status_code == 200:
|
||||
break
|
||||
except httpx.TransportError:
|
||||
pass
|
||||
time.sleep(2)
|
||||
else:
|
||||
raise RuntimeError('Public receiver health endpoint is unavailable')
|
||||
store = DeviceStore(root / 'audiobook-jobs/jobs.db3')
|
||||
response = client.post(base + '/v1/devices/enroll', json={'code': store.create_code(), 'label': 'Deployment smoke test'})
|
||||
response.raise_for_status()
|
||||
device = response.json()
|
||||
headers = {'Authorization': 'Bearer ' + device['token']}
|
||||
job = None
|
||||
try:
|
||||
text = 'Это проверка изготовления аудиокниги. Приёмник получил книгу от приложения и читает её вслух. Готовая запись возвращается в библиотеку.'
|
||||
book_format = os.environ.get('AUDIOBOOK_SMOKE_FORMAT', 'epub')
|
||||
if book_format == 'epub':
|
||||
archive = io.BytesIO()
|
||||
with zipfile.ZipFile(archive, 'w') as epub:
|
||||
epub.writestr('mimetype', 'application/epub+zip')
|
||||
epub.writestr('META-INF/container.xml', '<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0"><rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles></container>')
|
||||
chapters = int(os.environ.get('AUDIOBOOK_SMOKE_CHAPTERS', '1'))
|
||||
manifest = ''.join(f'<item id="c{i}" href="chapter{i}.xhtml" media-type="application/xhtml+xml"/>' for i in range(chapters))
|
||||
spine = ''.join(f'<itemref idref="c{i}"/>' for i in range(chapters))
|
||||
epub.writestr('OEBPS/content.opf', '<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="bookid"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier id="bookid">cmp-smoke</dc:identifier><dc:title>Проверка CMP</dc:title><dc:language>ru</dc:language></metadata><manifest>' + manifest + '</manifest><spine>' + spine + '</spine></package>')
|
||||
for i in range(chapters):
|
||||
body = text if chapters == 1 else text + f' Раздел номер {i + 1}.'
|
||||
epub.writestr(f'OEBPS/chapter{i}.xhtml', '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Проверка</title></head><body><h1>Проверка ' + str(i + 1) + '</h1><p>' + body + '</p></body></html>')
|
||||
book = archive.getvalue()
|
||||
else:
|
||||
book = ('<?xml version="1.0" encoding="UTF-8"?><FictionBook><body><section><title><p>Проверка</p></title><p>' + text + '</p></section></body></FictionBook>').encode()
|
||||
response = client.post(base + '/v1/audiobooks', headers=headers, data={'title': 'Проверка CMP', 'author': 'Aletheia'}, files={'book': ('test.' + book_format, book, 'application/epub+zip' if book_format == 'epub' else 'application/xml')})
|
||||
response.raise_for_status()
|
||||
job = response.json()['id']
|
||||
path = base + '/v1/audiobooks/' + job
|
||||
assert client.get(path).status_code == 401
|
||||
deadline = time.monotonic() + 1200
|
||||
previous = None
|
||||
while time.monotonic() < deadline:
|
||||
response = client.get(path, headers=headers)
|
||||
response.raise_for_status()
|
||||
status = response.json()
|
||||
state = (status['status'], status['stage'], status['processedCharacters'], status['totalCharacters'])
|
||||
if state != previous:
|
||||
print(json.dumps({'state': state}, ensure_ascii=False), flush=True)
|
||||
previous = state
|
||||
if status['status'] == 'ready':
|
||||
response = client.get(path + '/file', headers=headers)
|
||||
response.raise_for_status()
|
||||
assert b'ftyp' in response.content[:32] and status['durationMs'] > 0
|
||||
output = root / 'smoke-audiobook.m4a'
|
||||
output.write_bytes(response.content)
|
||||
print(json.dumps({'result': 'PASS', 'bytes': len(response.content), 'durationMs': status['durationMs'], 'sha256': hashlib.sha256(response.content).hexdigest()}), flush=True)
|
||||
break
|
||||
if status['status'] in ('failed', 'cancelled'):
|
||||
raise RuntimeError(status)
|
||||
time.sleep(3)
|
||||
else:
|
||||
raise TimeoutError('Synthesis exceeded smoke test timeout')
|
||||
finally:
|
||||
if job:
|
||||
client.delete(base + '/v1/audiobooks/' + job, headers=headers)
|
||||
store.revoke(device['deviceId'])
|
||||
Reference in New Issue
Block a user