Skip to main content

looks

Named video effects that compile to a backend command, each carrying a licence tier — so you can demand commercial-safe-only and get a refusal rather than a surprise.

from looks.environment import needs_gpl

needs_gpl(["scale", "eq", "lut3d"])  # -> ('eq',)

eq is the obvious brightness/contrast/gamma filter. It exists only in a GPL build of ffmpeg. Nothing in the ffmpeg CLI will tell you, because the binary on your machine runs it fine — and the LGPL-clean way to do the same thing (curves, colorlevels, exposure) is one substitution away. That gap is what this package is for.

pip install lookszero dependencies. ffmpeg is shelled out to, never linked.

What it does today

from looks.geometry import Size, placement, ffmpeg_chain, social_size

# a vertical phone clip, filled into a 16:9 frame
p = placement(Size(480, 850), social_size("youtube"), mode="fill")
ffmpeg_chain(p)
# 'scale=1920:3400,crop=1920:1080:0:1160'
from looks.lut import Ramp, gradient_map, write_cube

# a look, as a colour ramp indexed by lightness
look = gradient_map(
    Ramp.from_hex(
        [
            (8.2, "#2E0C18"),  # the shadow floor — NOT black
            (46.8, "#D5254A"),
            (100.0, "#FEF0DC"),  # the highlight — NOT white
        ]
    )
)
write_cube(look, "look.cube")  # ffmpeg: -vf lut3d=look.cube
from looks.measure import measure, dispersion

# how far apart do three sources look, after the effect?
stats = [measure(clip, source_id=name, vf="lut3d=look.cube") for name, clip in sources]
dispersion(stats)  # 2.98 -> the spread you are trying to close
# a Look whose flattening scale must be MEASURED per clip, not guessed
look = looks.Look(
    name="que_calor",
    target=looks.Target.SET_RELATIVE,  # the target is the set's own spread
    steps=(looks.Effect(name="flatten", params={"scale": looks.Ref("flatten_scale")}),),
)
looks.resolve(look, {"flatten_scale": 0.5})  # -> refused: one clip cannot answer it
looks.resolve_across(look, probes)  # -> one resolved Look per clip

Why it exists

PyPI has a dozen ffmpeg wrappers. Surveyed, none of them carries a named-effect registry, and none carries any licence awareness at all — not a field, not a check, not a warning. Meanwhile the licence facts are genuinely surprising:

  • eq, boxblur, cropdetect, hqdn3d and 34 others exist only in a GPL ffmpeg. Only three of the 38 are colour operations, and each has an LGPL substitute in the same binary — so nothing you want is unreachable. But the gated ones are exactly the obvious first reach, and eq is the first thing anyone types.
  • libx264 and libx265 are FFmpeg's only software H.264/HEVC encoders, both GPL. So an LGPL-tier deliverable is AV1, VP9, ProRes/FFV1 or hardware. Never x264. The wall is in the encoders, not the filters.
  • geq is not GPL and has not been since FFmpeg 4.3, contrary to widespread belief.
  • av, imageio-ffmpeg and opencv-python's macOS wheels all declare permissive licences while shipping GPL binaries. For av, three layers disagree: the metadata says BSD-3, FFmpeg's own avutil_license() says LGPLv3, and otool -L shows GPL libx264 and libx265 actually linked. A licence check that trusts a declared field is not a check.

Design

Pure-data specs, separable from execution. A Look is inspectable, persistable, diffable and costable before anything runs — the shape falaw.Plan has, except the cost unit is CPU-seconds, not dollars.

Unknown is a refusal, never a warning. That applies to a licence tier, to an ffmpeg build whose -L output matches nothing known, and to a clip whose colour range is untagged.

Two things are deliberately out of scope, and one of them is enforced rather than documented:

Every ffmpeg process looks starts ends in -f null -.

That admits every measurement, probe and diagnostic; it excludes every render, encode, mux and concat, and therefore looks.render() — which cannot be written without violating it. A convenience render function will get used and will rebuild one whole-timeline -filter_complex, which is a measured 2.3 GB regression on the box this ecosystem deploys to. looks emits the chain; you run it. The other exclusion is cut/EDL decisions: an effect says where a look applies, never where a cut is.

Parameters resolve against the clip they apply to. This is the package's most expensive lesson, and it is not a nicety. Building the first real look, one global flattening scale made the softest of three sources softer still — 46 → 38, where the other two went 35 → 72 and 117 → 114 — so it became the softest thing on screen, and it was the one the viewer complained about. The rule that follows is counter-intuitive: normalise the OUTPUT across sources, not the input. Don't sharpen the soft one; measure post-effect and pick parameters that land the clips in family. Full resolution was available and sharper, and was deliberately not used, because it would have made the softest source the sharpest thing in the edit — a new mismatch rather than a fix.

Status

Building. Shipped so far:

module what it does
looks.spec Effect / Look / Ref / Step / LookPlan — what a stylization is, before anything runs
looks.environment probe an ffmpeg build's licence and capabilities; FFmpeg's own gate table
looks.licence four axes, a ladder that is an explicitly replaceable policy, a 33-row evidence ledger, and refusals that name the alternative
looks.geometry fit / fill / stretch, crop, pad, social presets — pure arithmetic, compiles to any backend
looks.lut gradient-map .cube generation, at zero dependencies
looks.measure clip statistics via ffprobe, with identity fields that refuse a wrong comparison
looks.frame_dependency "can this effect flicker?", decided by four ffmpeg probes
looks._run the single process chokepoint, and the invariant guard

548 tests. They compose — looks/tests/test_integration.py walks the whole stack through the public surface: probe the environment, check the chain against the default ceiling, place a vertical clip into 16:9, verify the look cannot flicker, measure at source and post-effect, and confirm the two are correctly incomparable.

Next: the registry, the compiler, and the effect catalogue — see looks#2.

The design of record is docs/decisions_and_rationale.md, backed by the research notes in docs/research/ — thirteen investigations, each adversarially reviewed by a second reader who re-ran every command rather than taking it on trust. That review found a false permission in the first version of the gate table (five filters are GPL-gated indirectly, through EXTERNAL_LIBRARY_GPL_LIST, with no gpl token on their own line), which is the kind of error this package exists to prevent and is therefore worth reading about.

A note on what this is not

looks is not legal advice, and a licence tier is a mechanical reading of published metadata and source, not an opinion about your situation. What it can do is refuse to let a chain reach a filter your declared ceiling forbids, and tell you which substitution gets you the same result. What it cannot do is know what you are shipping, to whom, under what agreement.

License

MIT

Download files

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

Source Distribution

looks-0.0.7.tar.gz (604.6 kB view details)

Uploaded Source

Built Distribution

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

looks-0.0.7-py3-none-any.whl (205.5 kB view details)

Uploaded Python 3

File details

Details for the file looks-0.0.7.tar.gz.

File metadata

  • Download URL: looks-0.0.7.tar.gz
  • Upload date:
  • Size: 604.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for looks-0.0.7.tar.gz
Algorithm Hash digest
SHA256 af5a74b0ed6e0c0aa6f6cd07c8fea8cad9470746f75a80c8a0eeee269f5b044a
MD5 34e9b2fbce7314d0ae7e92e3be8b9ded
BLAKE2b-256 23dfc382bb2a9b659d293f347c6e6f938d360fe301f86f7674916a7f74cc2f59

See more details on using hashes here.

File details

Details for the file looks-0.0.7-py3-none-any.whl.

File metadata

  • Download URL: looks-0.0.7-py3-none-any.whl
  • Upload date:
  • Size: 205.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for looks-0.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 417190d9c6f0c66e817b6d9683b635b319238e878931f07bda118ce4be97faa9
MD5 b8ec98067ca9fc55baab31595da3bc3d
BLAKE2b-256 e48413d625d31f65d56e8ecf1faf35f4154c040be0cb7df79509609551fe0393

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.18

2 files

0.0.17

2 files

0.0.16

2 files

0.0.15

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

This release

0.0.7 This release

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page