Using Porter
porter-sender myfile.pdf --fountain --slideshow
Open the receiver, point it at the terminal. That is the common case — the rest of this page is what the flags do and when they matter.
Sequential or fountain
This is the one decision worth making deliberately.
| Sequential (default) | Fountain (--fountain) | |
|---|---|---|
| Frame | index|total|mode|id|payload | F|seq|K|fileSize|id|payload |
| A missed frame | Wait for the sender to loop back to that index | Costs nothing — catch a different one |
| Frames needed | Exactly the ones you're missing | ~1.33×–1.89× K, any of them |
| Good for | Small files, good light, a steady hand | Long transfers, poor light, handheld scanning |
Sequential is fine for a few hundred chunks. Past that, one frame your camera consistently misses — a glare spot, a blur at the same point in every loop — stalls the whole transfer, and fountain is worth the overhead.
--base64 has no effect in fountain mode; fountain payloads are always base64.Driving the slideshow
The sender is a full TUI: a QR grid, a sidebar showing chunk, progress, QR version, ETA, elapsed time and loop count, and a status line.
| Key | Action |
|---|---|
→ l k space | Next frame |
← h j | Previous frame |
Shift+←/→ | Scrub ~100 chunks |
J | Jump to a specific chunk |
G | Gap-fill mode — loop only the indices you name |
+ / - | Faster / slower |
S | Toggle slideshow |
I | Toggle the sidebar |
Q | Quit |
G is the sequential-mode rescue: when the receiver reports missing chunks
4,102 and 9,887, loop just those instead of the whole file again.
Speed
--speed is the delay between frames in seconds. The default works essentially
everywhere; going faster trades reliability for time.
| Value | Rate | Conditions |
|---|---|---|
0.5 | 2/sec | Default — works everywhere |
0.3 | 3.3/sec | Good lighting |
0.2 | 5/sec | Bright light, steady hand |
0.1 | 10/sec | Optimal only |
Dropped frames cost more than a slow rate does in sequential mode. In fountain mode they cost almost nothing, so push harder there.
--multi=N renders up to four QR codes per frame in a grid, multiplying
throughput if the camera can resolve them all. A larger terminal also means a
higher QR version and more bytes per code, so a maximised window is free
throughput.
K and
blockSize — forking the stream into one the receiver can't merge with what it
already has. Size the window before you start.Binary files and verification
Binary input is detected automatically and switches on base64; --base64 forces
it. --verify=<file> checks the input against an existing SHA-256 file before
sending.
Fountain transfers always end with a CHECKSUM frame, so the receiver verifies
by default. Sequential transfers only send one when you ask.
By default the sender writes nothing to disk. --resume opts into persisting
slideshow position in .porter_history; --reset ignores a saved position
without discarding the file.
Receiving
Scanning starts automatically — there is no button and no session to establish, because the sender does not know you exist. Each decoded payload is parsed, handed to a worker isolate, and written to disk as it resolves. On completion the app verifies SHA-256 and saves to Downloads.
K. Peeling needs materially more than
K distinct symbols — 1.33× to 1.89× across real transfers — so a bar scaled to
K sits at 99% with a third of the scanning left. The app scales to ~2×K.Chunks are written to disk as they decode, one file per recovered block. If the app is killed, it rehydrates from those files on the next launch — no re-scanning what you already caught. Hydration trusts the chunk filenames, not the metadata file, which is debounced and can lag what is actually on disk.
Without a camera
Two subcommands finish a transfer when the bytes are already somewhere useful.
serve — over a network
porter-sender serve --port=8080 --output-dir=received
An HTTP receiver on axum. POST /upload accepts raw bodies,
multipart/form-data, and QR-scan JSON — sequential, fountain and checksum
frames alike — and auto-joins completed transfers.
join — from parts on disk
porter-sender join <transfer-dir|file|id> [--output <path>] [--force] [--no-verify]
Reassembles the .partaa, .partab, … files a receiver wrote, verifying against
<base>.sha256 when one is present. The target can be a transfer directory, any
file inside one, or a bare transfer id. Output goes to <base>.joined unless
--output says otherwise; an existing destination is never overwritten without
--force.
.part prefix are skipped rather than
concatenated in — a deliberate difference from the TypeScript implementation this
was ported from.