Repository navigation
deck: deck.media, a clip's bytes for Quick Deck's player - #515
Merged
Merged
Conversation
The maintainer approved playback on 13 Oct 2026: Quick Deck plays a
deck's video and audio with the runtime's Video widget, which hands the
file to the operating system's own player. The service adds one method,
deck.media {doc, slide, id}, which writes the clip's bytes from the open
document's model, as they are, to .cache/media/, and never decodes a
clip; media.info, media.posterFrame, insert.audio and insert.video stay
held (#448). The section is appended verbatim as approved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deck.media {doc, slide, id} answers {path, kind, ext, bytes, autoplay,
loop, poster}, as SERVICE.md "Playback" says. The clip's bytes come from
the open document's model, the item the shape's MediaClip names, and are
written as they are to .cache/media/<SHA-256>.<ext>, staged and placed by
a link that refuses anything already there. No engine command runs and
no engine function is called, so nothing decodes a clip; media.info,
media.posterFrame, insert.audio and insert.video stay held (#448).
- ext: the deck's own extension for the clip when it is one of mp4, m4v,
mov, mp3, m4a, aac or wav, else the one its declared content type
names; neither is unsupported:, and so is a clip whose bytes the deck
doesn't hold (linked). Any shape but a clip is unknown_id:.
- The media cache holds 32 MiB and 8 clips per storage; the clip used
longest ago goes first, and a clip already there with the same bytes
(compared in full) is handed out again and counts as just used.
- A document's clips go when it closes or is released with its renders,
unless another document open in the same storage uses them; clips no
open document uses go when a document next opens.
- Before a clip would not fit the storage, the render cache and then the
media cache are cleared; one that still doesn't fit, or one larger than
the whole media cache, is storage_full: in the contract's words.
- deck.slide now reports a clip's media {id, video, autoplay, loop}: the
engine's slide.inspect gave it, but shape_json didn't copy it.
The deck paragraph in apps/AGENTS.md describes it. sha2 is the
workspace's own 0.10, already in Cargo.lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The coordinator accepted the six choices the Playback section left open, as #515 built them, and asked for them here with three facts the app needs: - deck.slide's media.id is the clip's media item id; deck.media takes the shape's own id. - A clip larger than the whole 32 MiB media cache is storage_full: at once, with nothing cleared; one the deck holds no bytes for (linked, missing or empty) is unsupported:. - Oldest first means used longest ago (a reused clip's modification time is set to now); a stale entry at a clip's name goes and the clip is written anew (a folder there is invalid:); clips no open document uses go when a document next opens; a clip inside a group is found too. - deckcraft at d0e57d7e reads no autoplay or loop from a .pptx, and its .pptx writer gives a clip without a poster a placeholder picture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
marked this pull request as ready for review
October 13, 2026 03:30
Contributor
Author
|
Local CI passed on d50cd42 ( GitHub would run for this PR: desktop.yml, phone.yml, apps.yml. Merged on this local pass (macOS runner queue); GitHub CI runs on the merge commit on main.
Skipped (these did NOT pass; (!) marks a skip that blocks tools/ci-local-merge.sh):
Notes:
Native GitHub acceptance passed on the same head: embedded-browser.yml. |
This was referenced Oct 13, 2026
RoooyHe
pushed a commit
to woshuoduijiushidui/OctoSense
that referenced
this pull request
Oct 13, 2026
OctoSense-org#515's review found one minor issue and four nits. - A link at a clip's own name, pointing outside storage, blocked that clip for good: the path went through crate::inside(), which follows a final link and refused before the stale-entry repair ran, and eviction, clear, forget and prune remove only regular files. The path is now the checked plain .cache/media folder joined with the service-built name (a hex SHA-256 and an extension from EXTS), so the repair removes whatever is there, a link itself and never what it points to, and writes the clip anew. - Eviction takes clips no open document uses first, and an open document's clips only when that is not enough, each the one used longest ago first; deck.media reads the set before the call takes the documents table. - Only what can fit is staged: after the render cache and then the media cache are cleared, a clip still bigger than the room left is storage_full: before any staging, and writing.rs::place (save, export) clears the render cache and refuses in the commit's own words before it stages. Both stage through crate::stage, counted in tests. - The 32 MiB cap is checked before the clip is hashed. New tests: a link at a clip's name (to a file outside storage, and to one holding the clip's very bytes) goes, the clip is written as a plain file and the file outside is untouched; eviction takes a newer clip no document uses before an open document's oldest. The storage_full tests check that nothing is staged. The deck paragraph in apps/AGENTS.md follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RoooyHe
pushed a commit
to woshuoduijiushidui/OctoSense
that referenced
this pull request
Oct 13, 2026
"Writes are staged and never replace a file the call didn't create" now says that a stale entry at a clip's own name (a file that doesn't hold the clip's bytes, or a link, never followed) is removed and the clip written there anew, as OctoSense-org#515's review asked. The as-built "oldest first" bullet adds that clips no open document uses go before any that one does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The maintainer approved playback on 13 Oct 2026 ("Add playback"). Quick Deck plays a deck's video and audio with the runtime's
Videowidget, which hands a file to the operating system's own player. So the app needs each clip as a file in its storage. This PR adds the one service method that writes it,deck.media. It never decodes, probes or transcodes anything. The app side is being built in parallel.What
deck.media {doc, slide, id}answers{path, kind, ext, bytes, autoplay, loop, poster}, as the contract says (src/media.rs).MediaClipnames (Presentation::media). They're written as they are to.cache/media/<SHA-256 of the bytes>.<ext>.kind,autoplay,loopandposterare the model's own fields.ext: the deck's own extension for the clip, if it's one ofmp4,m4v,mov,mp3,m4a,aacorwav. Otherwise it comes from the declared content type, with any parameters dropped and in any case. A clip with neither isunsupported:.unknown_id:, and so is a shape or slide the deck doesn't have.storage_full:("There isn't room in Quick Deck's storage to play this clip.")..cache/mediaare counted, reused or removed, and a link in the folder path is refused.OpenDoc::clips). A clip that another document open in the same storage still uses stays.deck.slidereportsmedia: {id, video, autoplay, loop}. It didn't before: the engine'sslide.inspectgives the key, butshape_jsondidn't copy it. It now does.media.idis the clip's media item id in the deck. Two shapes playing the same item share it.deck.mediatakes the shape's ownid.apps/quickdeck/design/SERVICE.md, in its own commit (SERVICE.md: playback (deck.media)). A second commit (SERVICE.md: playback as built (#515)) adds the settled choices below and the facts for the app, as bullets at the end of that section. The deck paragraph inapps/AGENTS.mdnow describesdeck.media.sha2 = "0.10", the workspace's own, as Mail's and the shell's. It's already inCargo.lock, so the lock gains one line.Example
The test deck's video, which has a poster:
{"path": ".cache/media/93caaec9284191ce54f9ac92482dd8a271ce1fd0fc292b98a1b31638aea45444.mp4", "kind": "video", "ext": "mp4", "bytes": 42, "autoplay": false, "loop": false, "poster": true}Audit: every call into the decoders, and what the service runs
This is #511's audit at deckcraft
d0e57d7e, with the new method's row. The decoders live indeckcraft-media. Onlyengineandui-eguidepend on it.deckcraft-mediaengine/src/cmd/media.rs:100,deckcraft_media::probemedia.infoonlyHELD_BACK, #448); no method builds itengine/src/cmd/media.rs:123,deckcraft_media::poster_pngmedia.posterFrame(media.rs:139) andinsert.video(insert.rs:477)engine/src/cmd/insert.rs:450,deckcraft_media::probeinsert.audioandinsert.video(insert.rs:511,514)engine/src/lib.rs:27,pub use deckcraft_media as mediadeck.mediaSlide::shapeandPresentation::mediafrom deckcraft-model, a SHA-256, then a staged write of the item's byteszipcrate's directory readdeckcraft_pptx::import(read/shapes.rs:259stores the clip's bytes as aMediaClip), thendeckcraft_format::repairdeckcraft_format::loaddeck.cover,deck.view,deck.layouts, the images exportrender/src/lib.rs:559-571: the poster, or a film or speaker glyph)deck.view {step},deck.showdeck.slide,deck.slides,deck.format_state,deck.hitslide.inspect(inspect.rs:99),document.inspect,format.state,Session::hit_test, model reads.pptx, PDF and outline exportsedit.undo,edit.redo(a snapshot swap)deck.runThe engine's
media.play,media.pause,media.seek,media.stopandmedia.toggleare classeddevice, so the door refuses them too, and no method builds them. Playback happens in the app, through the operating system's player.Choices the contract left open (settled)
The coordinator accepted all six as built, and they're now in SERVICE.md's Playback section ("As built in #515"):
storage_full:straight away, and nothing is cleared, since even an empty cache can't hold it. The guard allows up to 64 MiB an entry, so such a clip can open.unsupported:, with its own words. That's a clip linked to a file elsewhere (the service never follows a link), or one whose media item is missing or empty.invalid:.deck.mediatoo (Slide::shapesearches groups).deck.slidelists only top-level shapes, as before.For the app: what the model holds for a
.pptxAlso recorded in SERVICE.md's Playback section, with the note that
deck.slide'smedia.idis the clip's media item id whiledeck.mediatakes the shape's ownid..pptx. deckcraft's package reader and writer atd0e57d7enever read or write a clip's autoplay or loop: nothing incrates/pptxnames them. PowerPoint keeps them in the slide's timing tree. So a.pptxdeck's clips answerautoplay: falseandloop: false, and Present's autoplay won't start them. A native deck keeps both settings, and the test checks that..pptx. deckcraft's package writer gives every clip a picture, so after a.pptxround trip even a sound answersposter: true.posterreports what the model holds, as the contract says.Tests
On the Linux build host (x86_64), at
98358fe8on main3f700a2d. The head,d50cd423, adds only the SERVICE.md bullets, which no test or CI step reads.cargo test --locked -p octosense-deck-service -p octosense-engine-area:first_callandskill: 1 passed each;cargo clippy --locked -p octosense-deck-service -p octosense-engine-area --all-targets --no-deps: no findings in either crate.New, in
src/doc_tests.rs:deck_media_writes_the_clips_bytes_as_the_deck_stores_them: a video (.mp4, with a poster) and a sound (.m4a, autoplay and loop), from a native deck and a.pptx..cache/media/<SHA-256>.<ext>.deck.slidereports each clip'smedia.deck_media_hands_out_a_clip_already_written_again: the same answer, one file and the same inode, from the same document and from a second one on the same deck. A file cut short at the clip's name is written anew, whole.deck_media_answers_unknown_id_for_another_shape_and_unsupported_for_an_unknown_kind:.binwithapplication/octet-streamisunsupported:, and the words list the seven extensions.unsupported:.extcomes from the type (mov,mp3, andwavfromAudio/X-WAV; rate=44100) or from the name (Film.M4V, andaacwith no type). A.wmaname with anaudio/mpegtype givesmp3.unknown_id:.the_media_cache_holds_32_mib_and_8_clips_the_oldest_going_first:a_documents_clips_go_when_it_closes:a_closed_isolates_clips_go_with_its_renders: a real isolate opens the deck, gets a clip and draws a slide.services::cancel_heapremoves the clip and the renders.the_render_cache_then_the_media_cache_are_cleared_before_storage_full:storage_full:straight away, and the renders and the cached clip stay.storage_full:in the contract's exact words, with no staging folder left behind.a_hostile_clip_is_written_byte_for_byte_and_never_decoded: deck: decks with audio or video open #511's hostile MP4 (boxes claiming 4 GiB and 16 EiB, four billion samples) and hostile WAV, from a.pptxand a native deck.src/media.rs's code, comments aside, names none ofdeckcraft_engine,deckcraft_media,deckcraft_render,deckcraft_pptx,gate::,Sessionorexecute(.HELD_BACK.deck.mediais added to the unknown-handle test and the unknown-key test.a_deck_past_the_media_ceiling_is_too_large_before_the_engine_readspasses unchanged.deck.mediaadds nothing to a session.Not run: no shell, desktop or Quick Deck UI, and no player. The app side is a separate PR.
🤖 Generated with Claude Code