Skip to main content

Encode still images and video into NBTV (Narrow-Bandwidth Television) WAV signals

Project description

nbtvencoder

CI

Encode still images and video into an NBTV (Narrow-Bandwidth Television) signal packaged as a WAV file — and emulate a televisor by decoding that signal back into a PNG or MP4.

NBTV is the modern continuation of John Logie Baird's 32-line mechanical television format. Playing the generated WAV into an NBTV televisor displays the picture. This encoder is known to work with the Mindsets televisor kit.

This is a Python/OpenCV (cv2) reimplementation of the original Perl proof of concept, which is preserved in legacy/.

Demo

NBTV alphabet demo

The alphabet A–Z, run through the encoder into an NBTV signal and then decoded back by the emulator — what a televisor would show. Reproduce it:

pip install "nbtvencoder[demo]"
python examples/alphabet_demo.py     # writes alphabet.wav, alphabet.mp4, alphabet.apng

The full pipeline is in examples/alphabet_demo.py (or the copy/paste shell version, examples/alphabet_demo.sh):

# 1. generate the alphabet as test frames, then encode them to one NBTV signal
nbtvencoder letters/*.png -o alphabet.wav --frames 3 --seed 1
# 2. emulate the televisor: render the signal back to video
nbtv-emulate alphabet.wav -o alphabet.mp4 --scale 6

Install

pip install nbtvencoder

This pulls in numpy and opencv-python-headless. If you already use the full opencv-python (with GUI support), that works too — the encoder only needs OpenCV's decoding and image-processing routines.

Command-line usage

# Encode a still image, held for 10 seconds (the default):
nbtvencoder photo.jpg -o nbtv.wav

# Encode a video — frames are resampled to the NBTV frame rate (~12.7 fps):
nbtvencoder clip.mp4 -o nbtv.wav

# Hold a still for 30 seconds:
nbtvencoder photo.jpg -d 30 -o nbtv.wav

# Combine several inputs into one signal (a slideshow / playlist):
nbtvencoder intro.png clip.mp4 outro.png -o show.wav

# Reproducible output (fixes the dithering noise):
nbtvencoder photo.jpg --seed 1 -o nbtv.wav

Image vs. video is detected from the file extension; pass --video to force every input through the video decoder. Run nbtvencoder --help for the full option list (geometry, sample rate, channels, frame count, …).

You can also run it as a module:

python -m nbtvencoder photo.jpg -o nbtv.wav

Emulator (signal → PNG / MP4)

nbtv-emulate decodes an NBTV WAV the way a televisor would and renders the picture to an image or a video. The output format is chosen from the extension:

# Render the first frame of a signal to a PNG still:
nbtv-emulate nbtv.wav -o frame.png

# Render the whole signal to an MP4 at the NBTV frame rate:
nbtv-emulate nbtv.wav -o out.mp4

# Pick a frame, and upscale the chunky 32-line picture more:
nbtv-emulate nbtv.wav -o frame.png --frame 10 --scale 12

The decoder separates the line/frame sync pulses, slices each scan line, maps sample levels back to brightness, and undoes the scan geometry — so encoding an image and emulating it back round-trips to a recognisable picture (at the format's native 32×102 resolution). Run nbtv-emulate --help for all options.

Python API

from nbtvencoder import NBTVEncoder, NBTVDecoder, encode_to_wav, emulate_wav

# One-liner: encode any mix of images/videos straight to a WAV file.
encode_to_wav(["photo.jpg", "clip.mp4"], "nbtv.wav", seed=1)

# Emulate a televisor: decode the WAV back to a PNG still or an MP4.
emulate_wav("nbtv.wav", "frame.png")
emulate_wav("nbtv.wav", "out.mp4")

# Or work with the samples directly (a NumPy int16 array).
encoder = NBTVEncoder(seed=1)
samples = encoder.encode_still(cv2_image, duration=10.0)   # still image
samples = encoder.encode_frames(frame_iterable)            # any frame source

grids = NBTVDecoder().decode_grids(samples)                # intensity frames back out

Key entry points:

Function / class Purpose
NBTVEncoder Core encoder: encode_image_array, encode_still, encode_frames.
NBTVDecoder Emulator: decode_grids, render_frame, decode_images.
NBTVConfig Frozen dataclass of all timing/level parameters.
encode_file / encode_files Encode file(s) to a sample array (auto-detects video).
encode_to_wav Encode input(s) and write a WAV in one call.
emulate_wav Decode a WAV and render a PNG still or a video.
read_wav / write_wav Read/write a 16-bit PCM WAV as an int16 array.
iter_video_frames Iterate a video's frames resampled to a target fps.

How it works

For each frame the encoder emits a 16-bit PCM stream:

[ frame sync ][ line 0 dots ][ line sync ] ... [ line 31 dots ][ line sync ]

The image is converted to grayscale, flipped, rotated, and resized to dots × lines (102 × 32 by default) — NBTV scans vertically, so each image row becomes one scan line. Pixel intensity is inverted and scaled into the sample range, with small dithering noise, exactly as the original encoder did. At 44.1 kHz this produces roughly 12.7 frames per second.

For video, the source is resampled to that frame rate so playback runs at real time; for a still image the single frame is repeated to fill the requested duration.

The emulator runs this in reverse: it detects the line-sync pulses (the strongly negative end-of-line samples), groups them into frames using the wider gap left by the frame sync, reads the dots samples before each sync as one scan line, maps the levels back to brightness, and applies the inverse of the scan geometry to produce an upright picture.

Development

git clone https://github.com/anarkiwi/nbtvencoder
cd nbtvencoder
pip install -e ".[dev]"

pytest                 # run the test suite
ruff check .           # lint
ruff format .          # format

CI runs the linter, the test suite across Python 3.9–3.13, and a package build on every push and pull request. Dependencies are kept current by Dependabot.

Releasing

Publishing a GitHub Release builds and uploads the package to PyPI via PyPI Trusted Publishing (OIDC — no stored tokens). See RELEASING.md for the one-time trusted publisher setup and the per-release steps.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nbtvencoder-0.1.0.tar.gz (23.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nbtvencoder-0.1.0-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

File details

Details for the file nbtvencoder-0.1.0.tar.gz.

File metadata

  • Download URL: nbtvencoder-0.1.0.tar.gz
  • Upload date:
  • Size: 23.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nbtvencoder-0.1.0.tar.gz
Algorithm Hash digest
SHA256 be2372db4c8475abf46599dca48aa80e79a2f1fddb1e47fb20af6f7e943f35a0
MD5 76fca0bcc150f77f5973b454e32f6d14
BLAKE2b-256 3707b1892a4dfe0319d510df34a1382898fffe16c3497fd2ddec85562980c458

See more details on using hashes here.

Provenance

The following attestation bundles were made for nbtvencoder-0.1.0.tar.gz:

Publisher: release.yml on anarkiwi/nbtvencoder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nbtvencoder-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: nbtvencoder-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nbtvencoder-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fc241e72f19500e2ec45b6e2a8eae019cefccb20770602a9c37a9fb347a40659
MD5 4c0f149bb978ff40f6db571b236ace3a
BLAKE2b-256 9251b2dc69f63b1834dac72f1a388a1cd9f9a40b87fe695df3ebaa50fe2eba19

See more details on using hashes here.

Provenance

The following attestation bundles were made for nbtvencoder-0.1.0-py3-none-any.whl:

Publisher: release.yml on anarkiwi/nbtvencoder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page