# Qrs: Streaming Files Through Animated QR Codes

> Qrs is a TypeScript project that turns file data into a sequence of QR codes and reconstructs it from selectively scanned frames. Its Luby Transform approach suits visual transfers where some frames are missed, but QR capacity and scanning conditions still impose strict practical limits.

**qifi-dev/qrs** — Stream data through multiple series of QR codes

- Repository: https://github.com/qifi-dev/qrs
- Website: https://qrss.netlify.app/
- Stars: 1,703 · Forks: 109
- Language: TypeScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/qifi-dev-qrs

## Qrs and the QR transfer problem

Qrs addresses file transfer through a sequence of QR codes rather than one static image. A single QR code has a limited payload and a scanner may fail to capture every code in an animation. Qrs therefore treats QR capture as a transmission problem: the sender repeatedly presents encoded blocks, and the receiver keeps useful blocks until enough information exists to reconstruct the original data.

The intended users are people moving a limited amount of data between nearby devices, or developers interested in fountain-code encoding and animated QR interfaces. The repository includes a QiFi CLI for streaming QR file transmission, a `luby-transform` package for encoding and decoding, and `@qifi/generate` for generating streamed QR codes. Its live web application provides a Send interface and an animated QR display.

This is not positioned as a replacement for normal file delivery over a LAN, cloud storage, or removable media. QR frames still have to fit their encoded payload, and both devices need a camera, screen, and scanner that can cooperate at the required cadence. The useful question is therefore not whether the project can divide a file across QR codes, which the README explains, but whether a particular display, camera, and payload will complete the transfer without unacceptable scanning failures.

## Luby Transform encoding across Qrs frames

The README compares Qrs to a Binary Erasure Channel, a model in which the receiver knows that some transmitted information is missing but not its original value. It uses Luby Transform, described as a Fountain Code, to address that condition.

The original data is divided into small blocks. The encoder can then produce additional encoded blocks rather than requiring a fixed transmission sequence. A receiver does not need every frame in order. It only needs enough encoded blocks to reconstruct the source data, and the README notes that this commonly requires somewhat more blocks than the number of original blocks. Missing frames therefore do not automatically invalidate the transfer.

The repository layout separates the mechanism from the application. `packages/luby-transform` owns Luby Transform encoding and decoding, `packages/generate` generates QR-code streams, and `packages/cli` exposes file transmission through a command-line package. The root application is built with Nuxt and includes dependencies for QR generation and scanning, including `uqr`, `jsqr-es6`, and `qr-scanner`. The README also mentions `js-base64` as a dependency. These entries support the broader split between preparing data, rendering frames, and recovering scanned content.

That architecture does not eliminate data overhead. Readers must encode blocks, collect enough coverage, and wait for later frames when the first part of the animation is missed. High frame counts can also increase scanning and rendering time.

## Installing and building the Qrs application

The documented setup expects Node.js and `pnpm`. The root `package.json` identifies `pnpm@10.7.1` as the package manager. If `pnpm` is not installed, the README gives the following global installation command:

```bash
npm install -g pnpm
```

After that, install the workspace dependencies from the repository root:

```bash
pnpm install
```

The repository is a workspace containing the application and its sub-packages. Its build script runs `nuxi build`; no separate package build is required for this basic application build:

```bash
pnpm run build
```

The README states that this creates output in the `.output` directory. To inspect the application through the development server instead, run:

```bash
pnpm run dev
```

For a production-style local preview, the root `package.json` and README both identify the generated Node server entry point:

```bash
node .output/server/index.mjs
```

The documentation does not give a terminal command for creating and sending the first file. It directs readers to the live demo, while the local interface provides the practical first use: select a file on the Send page, display the QR sequence, and scan it with a compatible receiver. The README also documents `pnpm run typecheck`, `pnpm run lint`, and `pnpm run test`, although it does not describe their expected output.

## Exporting a Qrs GIF with a recovery check

The Send page can export the QR sequence as an animated GIF. This is useful when a user needs a saved, repeatable visual transfer rather than only the live stream. After selecting a file, the documented workflow is to expand Save QR sequence as GIF, select either a multiple of the data block count or an exact frame count, choose playback speed and image size, and select Export GIF.

The README says the page uses 1.5 times the data block count by default. This is a default interface choice, not a universal setting for all cameras. Larger frame counts can take longer to encode and use more memory, although the interface does not cap frame counts. An export can be cancelled while it runs.

Qrs performs a meaningful verification step before download. The GIF export uses a separate fountain-code encoder, pauses the live QR stream during encoding, and then decodes the completed sequence and compares it with the input. If recovery fails, the README instructs the user to increase the multiplier or frame count. Camera scanning can still miss frames even when the generated sequence itself passes this comparison, so the software check proves data integrity within the exported sequence rather than guaranteed readability through an arbitrary camera.

## Static hosting constraints for a Qrs build

The README describes two ways to serve the generated output. A target with Node.js can run `.output/server/index.mjs`. For a target without Node.js, the README says the static files can be hosted, but it also warns that a basic server may return JavaScript modules with the wrong MIME type.

The documented failure appears when the browser reports that it expected a JavaScript module but received `text/plain`. That matters because strict module checking can prevent the application from loading. The README responds with a small Python server that changes the MIME mapping for `.js` files to `application/javascript`:

```python
class CustomHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.extensions_map.update({
            ".js": "application/javascript",
        })
        super().end_headers()
```

The README presents this as `custom_http_server.py` and says to run the server from `.output/public`. This is a deployment constraint rather than a cosmetic hosting option. Merely copying files to an arbitrary static host may produce a build that builds successfully but fails in the browser.

The repository also contains `netlify.toml` and the documented live demo is hosted through Netlify. The README does not define the complete Netlify configuration, so it would be unsafe to infer all production hosting behavior from the demo alone.

## Where Qrs differs from a conventional file transfer

A conventional file transfer sends bytes over a transport that already provides addressing, delivery confirmation, and usually much higher throughput. Qrs instead carries those bytes through visual QR codes, one capacity-limited frame after another. That difference creates both its distinguishing use case and its main boundary.

Fountain coding is more tolerant than a fixed ordered sequence because the receiver can continue collecting other encoded blocks after missing one. It does not remove the QR format's payload limit, the need to display enough frames, or the need for a camera to recognize those frames accurately. Those constraints move reliability into the physical scanning process, where focus, lighting, screen size, playback speed, frame duration, and camera movement all matter.

Compared with a network utility, Qrs needs no conventional network connection once the application and receiver are arranged, but it offers far less control over transfer diagnostics and cannot be assumed to handle large payloads. Compared with storing a file in a single static QR code, it can continue across multiple frames and tolerate losses, but requires a stream rather than one capture. The QiFi CLI provides an alternative interface to the same package family, while the browser application adds GIF export and its built-in decode check.

## Maintenance, releases, and the MIT package boundary

The repository is not archived. Its last push was on 2026-09-17, which indicates recent repository activity, although the documentation should not be read as a compatibility promise for every dependency or browser. No GitHub releases are listed, so adopters cannot rely on a published release history as a migration guide from the available documentation.

The root project is private under its `package.json`, with version `0.2.0`, while the workspace contains separately identified sub-packages. That distinction matters when deciding how to consume Qrs. The documented commands operate on the repository application, while the package list identifies names that may have their own build and publishing behavior. The README does not provide CLI installation syntax, release tags, compatibility matrices, or upgrade instructions, so those details require direct verification before integrating the sub-packages into another project.

The repository identifies the project under the MIT license. The `LICENSE` file is the authoritative text to review for redistribution and notice obligations, and this review does not provide legal advice. Operationally, upgrades may require dependency and Nuxt compatibility checks because the project is built around pinned package-manager metadata and several separately evolving browser packages. No GitHub releases are available here to define a stable upgrade path.

## Conclusion

Qrs is appropriate for people who want a browser-based or CLI-oriented way to exchange small files through QR frames and can accept the scanning delay. It is not a substitute for a conventional network transfer when the file is large or the camera view cannot reliably retain frames. Before adoption, verify the exact file sizes your camera can decode, test a complete transfer under the intended lighting and display conditions, and read the repository's MIT LICENSE for the applicable terms.

## FAQ

### What is Qrs used for?

Qrs streams file data through multiple QR codes. It can display a live QR sequence or export the sequence as an animated GIF, and its decoder reconstructs the source from enough encoded blocks.

### How do I install and run Qrs?

Install Node.js and `pnpm`, run `pnpm install`, then use `pnpm run dev` for the development server. The documented production build command is `pnpm run build`, followed by `node .output/server/index.mjs` to preview the generated server.

### Can Qrs recover if I miss some QR frames?

The Luby Transform design is intended to tolerate missing encoded blocks, and the receiver needs enough blocks to reconstruct the original data. It may therefore need more than the original block count, but QR payload and scanning constraints still apply.

### Does Qrs verify an exported GIF before download?

The README says Qrs decodes the exported sequence and compares it with the input before download. It advises increasing the multiplier or frame count if recovery fails, while noting that a camera may still miss frames during scanning.

### Can I use Qrs on a static host?

The README says the static output can be hosted without Node.js, but warns that JavaScript modules must receive the correct MIME type. Its example uses a custom Python handler that maps `.js` files to `application/javascript`.

## Sources

- [Issues](https://github.com/qifi-dev/qrs/issues)
- [License: MIT](https://github.com/qifi-dev/qrs/blob/main/LICENSE)
- [Project website](https://qrss.netlify.app/)
- [qifi-dev/qrs on GitHub](https://github.com/qifi-dev/qrs)
- [README](https://github.com/qifi-dev/qrs/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qifi-dev-qrs
