Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions backend/app/routers/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,20 @@ def video_spec(payload: VideoRenderRequest, request: Request, session: Session =
note_id=note.id,
)
return DataResponse(data=VideoSpecRead(
spec=imported.spec.model_dump(mode="json", by_alias=True, exclude_none=True),
warnings=imported.warnings,
spec=_utf8_safe(imported.spec.model_dump(mode="json", by_alias=True, exclude_none=True)),
warnings=_utf8_safe(imported.warnings),
))


def _utf8_safe(value):
"""Replace lone UTF-16 surrogates — what a half-deleted emoji leaves in a
note — which a Python string can hold but a UTF-8 response cannot, so one
stray character would otherwise fail the whole response. Rendering keeps
the text as it is; only this document's copy is touched."""
if isinstance(value, str):
return value.encode("utf-16", "surrogatepass").decode("utf-16", "replace")
if isinstance(value, list):
return [_utf8_safe(v) for v in value]
if isinstance(value, dict):
return {k: _utf8_safe(v) for k, v in value.items()}
return value
14 changes: 14 additions & 0 deletions backend/tests/test_video_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,17 @@ def test_the_spec_route_refuses_another_users_note_and_bad_options(notes):
_spec_route(notes, options={"aspect": "4:3"})
assert invalid.value.status_code == 400


def test_the_spec_route_survives_half_of_a_deleted_emoji(notes):
# JSON.stringify writes a lone surrogate as \ud83d; decoded, it is a valid
# Python string but not valid UTF-8, so the response could not be encoded.
note = notes.get(Note, "mine")
note.content = json.dumps([
{"id": "p", "type": "paragraph", "content": [{"type": "text", "text": "Hi \ud83d there \U0001f98e"}]},
])
notes.add(note)
notes.commit()
data = _spec_route(notes, options={"title_card": False})
encoded = json.dumps(data.model_dump(), ensure_ascii=False).encode("utf-8")
assert "Hi \ufffd there \U0001f98e." in encoded.decode("utf-8")

Loading