Files
SocialPub/tools/pasture/town/core/media.py
T

67 lines
2.3 KiB
Python
Raw Normal View History

"""Media made from a seed, so two runs upload the same bytes: identicon PNGs, a sine-tone WAV, and the committed
media/tiny.mp4 and media/tiny.ogg (made once with ffmpeg; the command is in media/SOURCES.md)."""
import hashlib
import math
import os
import struct
import zlib
HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def png(seed, size=96, cells=6):
"""A symmetric identicon: a colour and a mirrored grid from the SHA-256 of the seed."""
h = hashlib.sha256(str(seed).encode()).digest()
fg = (h[0], h[1], h[2])
bg = (240, 240, 236) if sum(fg) < 380 else (34, 34, 40)
half = (cells + 1) // 2
bits = int.from_bytes(h[3:11], "big")
grid = [[False] * cells for _ in range(cells)]
for y in range(cells):
for x in range(half):
on = bool(bits & 1)
bits >>= 1
grid[y][x] = grid[y][cells - 1 - x] = on
cell = size // cells
rows = []
for py in range(cell * cells):
row = bytearray(b"\x00")
for px in range(cell * cells):
row += bytes(fg if grid[py // cell][px // cell] else bg)
rows.append(bytes(row))
w = hgt = cell * cells
return _png_bytes(w, hgt, b"".join(rows))
def _png_bytes(w, h, raw):
def chunk(t, c):
return struct.pack(">I", len(c)) + t + c + struct.pack(">I", zlib.crc32(t + c) & 0xffffffff)
return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
def wav(seed, seconds=1.0, rate=8000):
"""A mono 8-bit tone whose pitch comes from the seed."""
h = hashlib.sha256(str(seed).encode()).digest()
freq = 220 + h[0] * 2
n = int(seconds * rate)
samples = bytes(int(128 + 90 * math.sin(2 * math.pi * freq * i / rate)) for i in range(n))
header = b"RIFF" + struct.pack("<I", 36 + n) + b"WAVEfmt " + struct.pack("<IHHIIHH", 16, 1, 1, rate, rate, 1, 8) \
+ b"data" + struct.pack("<I", n)
return header + samples
def file(name):
with open(os.path.join(HERE, "media", name), "rb") as f:
return f.read()
def upload(kind, seed):
"""(filename, bytes, content type) for an upload of kind image, audio or video."""
if kind == "image":
return (f"town-{seed}.png", png(seed), "image/png")
if kind == "audio":
return (f"town-{seed}.ogg", file("tiny.ogg"), "audio/ogg")
if kind == "video":
return (f"town-{seed}.mp4", file("tiny.mp4"), "video/mp4")
raise ValueError(kind)