# yt-media-storage smuggles files through a lossless video and gets them back out

> yt-media-storage encodes any file into a lossless FFV1 video inside an MKV container and decodes it back, or does the same over a live RTMP stream to Twitch or YouTube. Wirehair fountain codes add redundancy, libsodium encryption is optional, and the build ships a CLI, a Qt6 GUI and an embeddable shared library.

**PulseBeat02/yt-media-storage** — Encodes files into uploadable media

- Repository: https://github.com/PulseBeat02/yt-media-storage
- Website: https://youtu.be/Qmds7-mwCMg
- Stars: 906 · Forks: 113
- Language: C++
- License: GPL-3.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/pulsebeat02-yt-media-storage

## The idea: the file becomes the video, not the metadata

yt-media-storage stores files onto any video or streaming platform, YouTube, Twitch and the rest, by encoding them into lossless video and decoding them back to the original file. It supports both a command-line interface and a graphical user interface.

The mechanism is worth understanding because it explains every constraint the project has. A file is split into packets, and each packet is rendered as pixels in a losslessly encoded video. The local path uses FFV1 inside an MKV container. FFV1 is a lossless codec, so the pixels that come out of the decoder are bit-identical to the pixels that went into the encoder, which means the file that comes back out is byte-identical to the file that went in.

That word lossless is the whole trick. A normal video upload path transcodes, and transcoding destroys data. Here the data is not in the picture's meaning but in the picture's bytes, and the codec has to preserve them exactly.

The feature list spells out the rest of the scope. File encoding and decoding, to and from a lossless video, and live streaming to Twitch or YouTube via RTMP using H.264, decoding from the stream or from the VOD. Optional encryption with a password using libsodium and XChaCha20-Poly1305. A selectable checksum, CRC32 by default or xxHash32. Batch processing to queue multiple files in the GUI, and real-time progress bars and status updates in the GUI.

The fountain codes are the part that makes it work over a lossy channel. Wirehair fountain codes provide redundancy and repair, so a stream that drops packets can be reconstructed without asking for the missing ones again.

## Encoding a file locally, and decoding it back

The CLI has four verbs: encode, decode, stream-encode and stream-decode. The local file path is the first pair.

```
./media_storage encode --input <file> --output <video> [--encrypt --password <pwd>] [--hash <crc32|xxhash>]
./media_storage decode --input <video> --output <file> [--password <pwd>]
```

The flags are short and each one is documented in a table. --input and -i is the input file path and is required for encode. --output and -o is the output file path and is required for decode. --encrypt and -e enables encryption and applies to encode only. --password and -p is the password for encryption and decryption. --hash and -H picks the checksum algorithm, crc32 as the default or xxhash, and it is encode only.

That last flag has a detail that saves an argument later. The checksum algorithm is embedded in each packet's flags, so decode automatically detects which algorithm was used and there is no need to specify it again on the decode side. That is a small design decision that keeps the decode command from being wrong by omission, and it means a file encoded with xxHash32 can be decoded without the operator remembering which one they picked.

The streaming pair takes the same shape with a URL in place of a file.

```
./media_storage stream-encode --input <file> --url <rtmp://...> [--bitrate <kbps>] [--width <w> --height <h>] [--fps <n>] [--encrypt --password <pwd>] [--hash <crc32|xxhash>]
./media_storage stream-decode --url <stream_url> --output <file> [--password <pwd>]
```

The streaming defaults are given too: bitrate 8000 for 1080p, width 1920, height 1080, frames per second 30.

There is a sequencing constraint that catches people. Stream-decode supports a 30-second retry window, so you can start it before the encoder begins streaming. The README's worked example does exactly that, with the decoder started first in one terminal because it waits for the stream, and the encoder in a second terminal.

## The streaming example, and the yt-dlp dependency

The README gives a complete example of streaming to Twitch at 1080p, and it is worth reading because it exposes a requirement that is easy to miss.

```bash
# Terminal 1: start decoder first (waits for stream)
./media_storage stream-decode --url stream_playback_url --output decoded.bin
```

```bash
# Terminal 2: start encoder
# You must use yt-dlp to get the raw Twitch or YouTube Stream URL
./media_storage stream-encode --input myfile.bin --url rtmp://... --width 1920 --height 1080 --bitrate 8000
```

The comment on the encoder line is the point: you must use yt-dlp to get the raw Twitch or YouTube Stream URL. The public playback URL that a browser would use is not the same thing as the RTMP ingest URL that the encoder needs, and yt-dlp is what bridges the two.

That is a real operational dependency outside the tool itself, and it applies to the live path rather than the local file path. For the local path, the input is a video file on disk and nothing else is needed.

The design point about the 30-second retry window is also worth understanding. A live stream has to be running before the decoder can latch onto it, and stream-decode starts first and waits. So the practical order is decoder, then encoder, and the encoder must begin within that window or the decoder gives up.

For the local path, encode and decode are independent operations, so the order is irrelevant and you can even run them on different machines, which is the case the tool is really built for: one side uploads the video to the platform, the other side downloads it and decodes it back.

## Building it: CMake, Qt6, FFmpeg and a march=native default

The build requirements are CMake 3.22, a C++23 compiler, FFmpeg with libx264 for streaming, libsodium, OpenMP and Qt6 with the Core and Widgets modules.

On Ubuntu and Debian the dependency install is one apt invocation.

```bash
sudo apt update
sudo apt install cmake build-essential pkg-config qt6-base-dev \
  libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev \
  libsodium-dev libomp-dev ffmpeg
```

Fedora and CentOS, Arch and macOS each have their own one-liner, and macOS uses Homebrew with qt@6, ffmpeg, libsodium and libomp. Windows uses vcpkg.

```powershell
vcpkg install ffmpeg libsodium qt6-base gtest --triplet x64-windows
```

Then you configure with the vcpkg toolchain file.

```powershell
cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
```

There is a WSL note worth repeating because the error message is confusing. If CMake reports Could NOT find PkgConfig and the error mentions Strawberry, WSL is using the Windows pkg-config.exe from an inherited PATH, and the fix is to pass -DPKG_CONFIG_EXECUTABLE=/usr/bin/pkg-config to CMake.

The build itself is two commands.

```bash
cmake -B build
cmake --build build
```

And it produces two executables and one shared library: media_storage as the command-line interface, media_storage_gui as the graphical user interface, and libmedia_storage.so or media_storage.dll as an embeddable shared library.

Two build options matter in practice. The build type defaults to Release, and -DCMAKE_BUILD_TYPE=Debug gives a debug build. By default the core is compiled for the build machine's CPU with -march=native or /arch:AVX2, and -DMEDIA_STORAGE_NATIVE_ARCH=OFF produces binaries that run on any CPU of the target architecture.

That default is the one to think about. A native build is faster and will not run on an older machine than the one you built it on, so if you are producing binaries for someone else's hardware, turn it off.

## The GUI, batch encoding and the Qt6 dependency

The GUI is a separate binary, started with no arguments.

```
./media_storage_gui
```

The single-file operations are straightforward. To encode a file to video you click Browse next to Input File to select the file, click Browse next to Output File to choose where to save the video, and click Encode to Video. To decode, the same two Browse fields, then Decode from Video.

Batch operations add a queue. You click Add Files to add multiple files to the batch queue, select an output directory for all encoded videos, and click Batch Encode All to process all files in sequence.

The streaming section follows the same model as the CLI, with a platform selection and its own fields.

What is notable is the split of responsibilities. The GUI adds batch processing and progress tracking, which is why the feature list lists batch processing and progress tracking as GUI features specifically. The underlying encode and decode is the same code path, which is what the shared library is for.

That Qt6 requirement is the heaviest part of the dependency list for anyone who just wants the command line. CMake, a C++23 compiler, FFmpeg, libsodium and OpenMP are all reasonable for a C++ project. Qt6 with the Core and Widgets modules is a large framework to pull in for a single-file utility, and the README's own guidance reflects that: install Qt6 separately via the Qt Online Installer if you prefer, then point CMake at the install prefix.

```powershell
cmake -B build -DCMAKE_PREFIX_PATH="C:/Qt/XXX/msvcXXX_64"
```

The Windows build therefore has two paths, both documented, and the difference is whether you want Qt from vcpkg or from the official installer.

The repository layout matches: include/ for the public headers, src/ for the implementation, tests/ for the Google Test suite, scripts/, a CMakeLists.txt at the root, and a vcpkg.json for the Windows dependency manifest.

## Where yt-media-storage is the wrong tool

Four cases rule it out, and the first one is fatal to the whole idea.

The first is any platform that transcodes your upload. The method depends on the pixels surviving the round trip untouched, and FFV1 inside MKV is what makes that possible locally. If the platform re-encodes the video to H.264 for delivery, the pixel values change and the file does not decode back. The streaming path faces the same constraint from the other direction, which is why it uses H.264 deliberately and relies on the fountain codes to repair the loss.

The second is needing authenticated delivery. The encryption is a password with XChaCha20-Poly1305, and the password is what protects the file. There is no public key, no per-recipient key and no server-side access control. Anyone with the password and the video has the file, so this is confidentiality for a shared secret, not an access-controlled channel.

The third is the operational shape. This is a desktop and command-line tool with a shared library. There is no service, no queue and no web interface in the repository listing, so the workflow of uploading, distributing and downloading is entirely manual. The CLI's live streaming needs yt-dlp to obtain the raw RTMP URL, which means an extra tool and an extra failure mode.

The fourth is the storage cost. A file becomes a video, and video is a poor storage format for arbitrary bytes. The encoding overhead is not documented in the README, so before committing you would want to measure the ratio on your own files. There is also a limit worth thinking about: the fountain code stream has to fit through a live platform's bitrate, and a large file over RTMP is a long transfer.

On the licence, it is GPL-3.0 with a LICENSE.txt file at the repository root, which matters if you intend to embed the shared library in a closed product.

The alternative for moving files through a video platform is simply not to. A file host, an object store with a presigned URL, or a peer-to-peer transfer tool does the same job without encoding, without a platform that has to be trusted not to transcode, and without the fountain code overhead. The reason to choose this project is a constraint that rules the others out: the only storage available is a video hosting service, and the file has to come back out byte-identical.

## Conclusion

Adopt yt-media-storage if you need to move a file through a video platform that will host it but will not serve arbitrary uploads, and if you control both ends so you can run the decoder. Do not adopt it for ordinary file transfer, because a video platform's own transcoding will destroy the pixels the method depends on, and do not adopt it if you need an authenticated channel rather than a shared password. Before you try it, confirm the target platform serves the uploaded file without re-encoding, and read the stream-decode retry window note, because the decoder has to start before the encoder for a live stream.

## FAQ

### How do I encode a file into a video with yt-media-storage?

Run ./media_storage encode with --input and --output, optionally adding --encrypt with --password for encryption and --hash with crc32 or xxhash for the checksum. To recover the file, run ./media_storage decode with the video as --input and the target path as --output.

### What codec does yt-media-storage use for local files?

The feature list states that any file is encoded into a lossless video using FFV1 inside an MKV container, which is what allows the decoded output to be byte-identical to the original.

### How do I run the tests for yt-media-storage?

Configure with -DBUILD_TESTS=ON, build, then run ctest --test-dir build. The tests use Google Test, and the test binary can also be run directly for verbose output at ./build/tests/media_storage_tests.

### What does the --hash flag do in yt-media-storage?

It selects the checksum algorithm, crc32 by default or xxhash, and applies to encoding only. The algorithm is embedded in each packet's flags, so decode detects it automatically and you do not need to specify it again.

### Do I need anything special to stream to Twitch or YouTube?

Yes. The README says you must use yt-dlp to get the raw Twitch or YouTube Stream URL for the encoder. Stream-decode also has a 30-second retry window so you can start it before the encoder begins streaming.

### What licence is yt-media-storage released under?

GPL-3.0, with the LICENSE.txt file at the root of the repository.

## Sources

- [License: GPL-3.0](https://github.com/PulseBeat02/yt-media-storage/blob/master/LICENSE)
- [Project website](https://youtu.be/Qmds7-mwCMg)
- [PulseBeat02/yt-media-storage on GitHub](https://github.com/PulseBeat02/yt-media-storage)
- [README](https://github.com/PulseBeat02/yt-media-storage/blob/master/README.md)
- [Releases](https://github.com/PulseBeat02/yt-media-storage/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pulsebeat02-yt-media-storage
