Synchronous dispatch() now converts office documents via OnlyOffice
(asyncio.run around OOConversionManager.convert), refusing to run inside
a live event loop and pointing async callers at
pool.generate_office_preview(). The office import stays lazy so the base
install works without the 'office' extra.
Unknown file types now name the offending extension (or its absence)
instead of a generic 'preview unsupported'.
The temporary HTTP server that OnlyOffice downloads the source document
from was bound to 0.0.0.0, so internet scanners could (and did) connect,
and socketserver dumped a full traceback to stderr for every dropped
connection. It also stayed up for the whole conversion attempt, leaving
the port exposed when conversions hang.
- Bind only to the callback host (oonet gateway by default) so the port
is unreachable from the internet.
- Override handle_error to log at debug level instead of printing
tracebacks.
- Watchdog shuts the server down ~2s after the file is fetched, or at
max_lifetime (request_timeout + 30s), and the socket is closed with
server_close() in the normal path.
The cista service account has no docker CLI access, so detecting the
gateway via docker network inspect silently fell back to docker0/legacy
behavior. With the pinned subnet the gateway is always 172.30.0.1;
ONLYOFFICE_CALLBACK_HOST remains as an env override.
The container always lives on the isolated oonet network at the fixed
IP; falling back to a published localhost port silently masked broken
setups with confusing 'not reachable at localhost:8988' diagnostics.
Docker silently discards published ports on internal networks, so the
isolated oonet setup left the container unreachable at localhost:8988.
Reach it at its fixed IP (172.30.0.2) on the bridge instead; the host
is the gateway, so this needs no port publishing at all. The oosetup
CLI drops its now-meaningless <port> argument.
Also: record whether OnlyOffice fetched the input file from the
temporary callback server and report it in the timeout error message
('input file never fetched' = network/callback failure, vs a stalled
conversion). Convert POST timeout is 7s (conversion runs inside the
request), result PNG download stays at 2s.
- Dockerfile: pin ONLYOFFICE server sources to a known-good commit
(build-arg ONLYOFFICE_SERVER_REF) instead of tracking master; master
gained a 'memory runtime' branch that forks no converter workers on
community edition, silently breaking all conversions. Also patch that
branch out as defense in depth.
- setup_docker: run the container on an internal-only network (oonet,
172.30.0.0/24) with no outbound internet; only host callback traffic
is possible.
- _get_callback_host: resolve the oonet gateway via docker network
inspect instead of assuming docker0; fall back to docker0.
- OOConversionManager: cancel the background conversion task when its
last waiter is cancelled (strict preview deadline), releasing the
semaphore slot and aborting the hung HTTP request; shield the shared
future so one waiter's cancellation does not affect others.
- Split timeouts: convert POST 7s (conversion happens inside the
request with async:false), result PNG download 2s.
Worker logs expected PreviewError failures as warnings; tracebacks are
reserved for unanticipated internal errors. pdf read errors report
backend "pdf" (vips not yet reached); the stage field is dropped as the
backend name now encodes the failing pipeline step. Backend label
"pyvips" renamed to "vips" everywhere.
PreviewError subclasses carry a concise short label for UI, a full log
message, backend name, and metadata fields (OnlyOffice code/status/url,
backend stage, timeout seconds, cancel reason). Exceptions travel across
the worker pool wire pickled in the binary response payload (ok=False)
and are re-raised with their original type on the caller side. Combined
pipelines such as pdf+pyvips tag the failing stage.
Add fixtures and tests covering image EXIF orientations, HDR AVIF, SDR video, 90/270 rotated video, HDR video with and without rotation, PDF, dispatch, and OnlyOffice (skipped unless configured).
Regenerate rotated/HDR video fixtures and sample.pdf with tests/files/generate_fixtures.py.
- Accept a keyframe with pts as well as dts, so all-intra or short HDR clips are not rejected as No frames found.
- Round resized video dimensions to multiples of 2 (width) and 4 (height) so planar 4:2:0 YUV rotation always works.
- Use abs(frame.rotation) when swapping display dimensions, so both +90 and -90 rotation matrices report the correct orientation.
- __main__ is now a self-contained docopt CLI going straight to
backends.dispatch (no blake3/tracerite needed); 'oosetup' builds and
starts the bundled OnlyOffice container and prints
ONLYOFFICE_JWT_SECRET=<token> as its only stdout line
- dispatch: log a plain one-line error for missing-extra ImportErrors
instead of a traceback
- worker/pool: friendly install hints when the 'worker' extra is missing
- office: fix import on Python 3.11-3.13 without the 'office' extra
(lazy annotations); setup_docker returns the JWT secret used and
redacts it from the logged docker command
- add 'mediapreview' console script and docopt-ng dependency