Metadata-Version: 2.4
Name: optic-fx
Version: 0.1.0
Summary: Optical effects library — lens flares, camera optics, atmospheric/diffusion, stylized optics
Project-URL: Homepage, https://github.com/tomastimelock/optic-fx
Project-URL: Source, https://github.com/tomastimelock/optic-fx
Project-URL: Issues, https://github.com/tomastimelock/optic-fx/issues
Author-email: Trollfabriken AITrix AB <dev@trollfabriken.se>
License: MIT
License-File: LICENSE
Keywords: anamorphic,atmospheric,cinematic,fog,lens-flare,optics,post-production,vfx,video
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Requires-Dist: ffmpeg-python>=0.2
Requires-Dist: numpy>=1.24
Requires-Dist: opencv-python-headless>=4.8
Requires-Dist: pillow>=10.0
Requires-Dist: pydantic>=2.5
Requires-Dist: typer>=0.9
Provides-Extra: advanced
Requires-Dist: scikit-image>=0.22; extra == 'advanced'
Provides-Extra: all
Requires-Dist: scikit-image>=0.22; extra == 'all'
Requires-Dist: web-overlay>=0.1; extra == 'all'
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest-cov>=4; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: overlay
Requires-Dist: web-overlay>=0.1; extra == 'overlay'
Description-Content-Type: text/markdown

# optic-fx

Optical effects for video — lens flares, camera optics, atmospheric layers, and stylized optics.
86 named effects. Frame-accurate. Compositable.

[![PyPI](https://img.shields.io/pypi/v/optic-fx)](https://pypi.org/project/optic-fx/)
[![CI](https://github.com/tomastimelock/optic-fx/actions/workflows/ci.yml/badge.svg)](https://github.com/tomastimelock/optic-fx/actions/workflows/ci.yml)

---

## Install

```bash
pip install optic-fx
```

Requires **ffmpeg >= 6.0** on PATH.

Extras:

| Extra | What it adds | Approx size |
|---|---|---|
| `pip install optic-fx` | base: flares, optics, atmospherics, stylized | ~80 MB |
| `pip install optic-fx[advanced]` | scikit-image — higher quality atmospheric noise | ~120 MB |
| `pip install optic-fx[overlay]` | web-overlay + Playwright Chromium — SVG-rendered flares | ~230 MB |
| `pip install optic-fx[all]` | overlay + advanced | ~270 MB |

The `[overlay]` extra installs Playwright Chromium. Most pipelines do not need it — the PIL fallback is used automatically when `web-overlay` is absent.

---

## 5-minute tour

Apply a named effect to a clip:

```python
from optic_fx import apply_optic

apply_optic(
    video="raw.mp4",
    effect="anamorphic_lens_flare_gold",
    output="flared.mp4",
)
```

Add a dense fog layer with animated drift:

```python
from optic_fx import render_atmosphere

render_atmosphere(
    video="raw.mp4",
    effect="fog_overlay_dense",
    output="foggy.mp4",
    density=0.8,
    movement_speed=0.3,
)
```

Render a flare as a standalone overlay for compositing in another tool:

```python
from optic_fx import render_flare

render_flare(
    effect="anamorphic_lens_flare_subtle",
    width=1920,
    height=1080,
    position=(0.7, 0.3),   # normalized (x, y) of the flare origin
    duration=2.0,
    output="flare_overlay.webm",
)
```

---

## The 86 effects

### lens_flares_and_glow (25)

| Slug | Display name |
|---|---|
| `anamorphic_lens_flare_subtle` | Anamorphic Lens Flare (Subtle) |
| `anamorphic_lens_flare_intense` | Anamorphic Lens Flare (Intense) |
| `anamorphic_lens_flare_gold` | Anamorphic Lens Flare (Gold) |
| `sci_fi_aggressive_flare` | Sci-Fi Aggressive Flare |
| `sun_glint_small` | Sun Glint (Small) |
| `sun_glint_ribbon` | Sun Glint (Ribbon) |
| `lens_orb_flare_single` | Lens Orb Flare (Single) |
| `lens_orb_flare_chain` | Lens Orb Flare (Chain) |
| `rainbow_prism_flare` | Rainbow Prism Flare |
| `glow_bloom_soft` | Glow Bloom (Soft) |
| `glow_bloom_intense` | Glow Bloom (Intense) |
| `soft_diffusion_pro_mist` | Soft Diffusion (Pro Mist) |
| `soft_diffusion_white_diffusion` | Soft Diffusion (White Diffusion) |
| `halation` | Halation |
| `halation_warm` | Halation (Warm) |
| `neon_glow_magenta` | Neon Glow (Magenta) |
| `neon_glow_cyan` | Neon Glow (Cyan) |
| `neon_glow_warm` | Neon Glow (Warm) |
| `starburst_filter_4_point` | Starburst Filter (4 Point) |
| `starburst_filter_8_point` | Starburst Filter (8 Point) |
| `dirty_lens` | Dirty Lens |
| `bokeh_bloom_circular` | Bokeh Bloom (Circular) |
| `bokeh_bloom_hexagonal` | Bokeh Bloom (Hexagonal) |
| `bokeh_bloom_anamorphic_oval` | Bokeh Bloom (Anamorphic Oval) |
| `lens_breathing` | Lens Breathing |

### camera_optics (18)

| Slug | Display name |
|---|---|
| `barrel_distortion_gentle` | Barrel Distortion (Gentle) |
| `barrel_distortion_strong` | Barrel Distortion (Strong) |
| `pincushion_distortion_gentle` | Pincushion Distortion (Gentle) |
| `pincushion_distortion_strong` | Pincushion Distortion (Strong) |
| `vignette_subtle` | Vignette (Subtle) |
| `vignette_heavy` | Vignette (Heavy) |
| `vignette_rectangular` | Vignette (Rectangular) |
| `lens_warp_organic` | Lens Warp (Organic) |
| `lens_warp_geometric` | Lens Warp (Geometric) |
| `soft_corners` | Soft Corners |
| `microscope_lens` | Microscope Lens |
| `telephoto_compression_moderate` | Telephoto Compression (Moderate) |
| `telephoto_compression_extreme` | Telephoto Compression (Extreme) |
| `underwater_lens_ripple` | Underwater Lens (Ripple) |
| `underwater_lens_deep` | Underwater Lens (Deep) |
| `prism_refraction_horizontal` | Prism Refraction (Horizontal) |
| `prism_refraction_radial` | Prism Refraction (Radial) |
| `prism_refraction_stacked` | Prism Refraction (Stacked) |

### atmospheric_and_diffusion (25)

| Slug | Display name |
|---|---|
| `cinematic_haze_morning` | Cinematic Haze (Morning) |
| `cinematic_haze_evening` | Cinematic Haze (Evening) |
| `fog_overlay_light` | Fog Overlay (Light) |
| `fog_overlay_dense` | Fog Overlay (Dense) |
| `fog_overlay_ground_only` | Fog Overlay (Ground Only) |
| `smoke_diffusion_stage` | Smoke Diffusion (Stage) |
| `smoke_diffusion_cinematic` | Smoke Diffusion (Cinematic) |
| `heat_haze_desert` | Heat Haze (Desert) |
| `heat_haze_urban` | Heat Haze (Urban) |
| `rain_streak_refraction_light` | Rain Streak Refraction (Light) |
| `rain_streak_refraction_heavy` | Rain Streak Refraction (Heavy) |
| `snow_glow` | Snow Glow |
| `dust_storm_dry` | Dust Storm (Dry) |
| `dust_storm_apocalyptic` | Dust Storm (Apocalyptic) |
| `dream_fog` | Dream Fog |
| `moonlight_mist` | Moonlight Mist |
| `fire_smoke_warm` | Fire Smoke (Warm) |
| `fire_smoke_drifting` | Fire Smoke (Drifting) |
| `volumetric_atmosphere_forest` | Volumetric Atmosphere (Forest) |
| `volumetric_atmosphere_cathedral` | Volumetric Atmosphere (Cathedral) |
| `stage_spotlight_haze_concert` | Stage Spotlight Haze (Concert) |
| `stage_spotlight_haze_theatrical` | Stage Spotlight Haze (Theatrical) |
| `window_diffusion` | Window Diffusion |
| `clouded_lens_light` | Clouded Lens (Light) |
| `clouded_lens_heavy` | Clouded Lens (Heavy) |

### stylized_optical (18)

| Slug | Display name |
|---|---|
| `kaleidoscope_prism_triangular` | Kaleidoscope Prism (Triangular) |
| `kaleidoscope_prism_hexagonal` | Kaleidoscope Prism (Hexagonal) |
| `glass_refraction` | Glass Refraction |
| `crystal_refraction` | Crystal Refraction |
| `mirror_hall` | Mirror Hall |
| `oil_smear_lens` | Oil Smear Lens |
| `dream_prism` | Dream Prism |
| `heatwave_blur` | Heatwave Blur |
| `tunnel_vision_subtle` | Tunnel Vision (Subtle) |
| `tunnel_vision_claustrophobic` | Tunnel Vision (Claustrophobic) |
| `psychedelic_refraction` | Psychedelic Refraction |
| `security_monitor_glow` | Security Monitor Glow |
| `holographic_reflections` | Holographic Reflections |
| `mirror_split_horizontal` | Mirror Split (Horizontal) |
| `mirror_split_vertical` | Mirror Split (Vertical) |
| `shattered_glass` | Shattered Glass |
| `retro_tv_curvature` | Retro TV Curvature |
| `acid_bloom` | Acid Bloom |

---

## Engines

Four base engines implement all 86 effects. Each engine handles a family of variants.

| Engine | Effects | What it does |
|---|---:|---|
| `flare_glow` | 25 | Anamorphic streaks, orb artifacts, glow bloom, halation, neon glow, starburst, bokeh |
| `optical_distortion` | 18 | Geometric remapping via OpenCV `cv2.remap` — barrel, pincushion, vignette, lens warp, prism |
| `atmospheric_layer` | 25 | Procedural noise layers — fog, smoke, haze, dust, rain streaks, volumetric light |
| `stylized_optical` | 18 | Kaleidoscope, mirror, glass/crystal refraction, CRT curvature, psychedelic transforms |

The resolver maps each slug to `(base_engine, variant, params)` from `data/effects_catalog.json`.
Engines are instantiated lazily. Optical distortion remap arrays are computed once and cached.
Atmospheric noise is procedural numpy — no extra dependency at base install; `[advanced]` adds
scikit-image for higher quality layering. Three flare effects use SVG templates rendered via
`[overlay]`; without it a PIL approximation runs with a `UserWarning`.

---

## OpticConfig

`OpticConfig` is a Pydantic model. All fields except `effect` have defaults.

```python
from optic_fx import OpticConfig

cfg = OpticConfig(
    effect="anamorphic_lens_flare_gold",
    intensity=0.9,
    position=(0.75, 0.25),
    blend_mode="screen",
)
```

| Field | Type | Default | Notes |
|---|---|---|---|
| `effect` | `str` | required | Slug from the catalog |
| `intensity` | `float` | `0.75` | Overall effect strength |
| `opacity` | `float` | `1.0` | Layer opacity before blend |
| `blend_mode` | `str` | `"screen"` | `screen`, `add`, `overlay`, `multiply`, `soft_light` |
| `seed` | `int \| None` | `None` | Fix random seed for reproducible noise |
| `start_time` | `float` | `0.0` | Seconds into the clip to start effect |
| `duration` | `float \| None` | `None` | Duration in seconds; `None` = full clip |
| `in_animation` | `str` | `"none"` | `fade_in` or `none` |
| `out_animation` | `str` | `"none"` | `fade_out` or `none` |
| `position` | `tuple[float, float] \| None` | `None` | Normalized (x, y) for flare origin |
| `flare_color` | `str \| None` | `None` | CSS color override for flare tint |
| `follow_motion` | `bool` | `False` | Subtle motion-based flare drift |
| `bloom_radius` | `float` | `12.0` | Bloom kernel radius in pixels |
| `density` | `float` | `0.5` | Atmospheric layer opacity/thickness |
| `movement_speed` | `float` | `0.5` | Animation speed for fog/smoke drift |
| `depth_aware` | `bool` | `False` | Depth-map-weighted atmospheric composite (advanced) |
| `color_tint` | `str \| None` | `None` | CSS color tint for atmospheric layer |
| `distortion_strength` | `float` | `0.5` | Geometric distortion amount |
| `chromatic_separation` | `float` | `0.0` | Extra RGB fringing on distortion edges |
| `video_codec` | `str` | `"libx264"` | Output video codec |
| `audio_codec` | `str` | `"copy"` | Output audio handling |

Extra fields are forbidden (`extra="forbid"`) — unknown keys raise immediately.

---

## CLI

Every subcommand accepts `--help`.

Apply an effect:

```bash
optic-fx apply --video raw.mp4 --effect anamorphic_lens_flare_gold --out flared.mp4
```

Render a standalone flare overlay (no input video):

```bash
optic-fx flare --effect anamorphic_lens_flare_subtle \
  --width 1920 --height 1080 --position 0.7,0.3 --duration 2 --out flare.webm
```

Pre-render an atmospheric layer:

```bash
optic-fx atmosphere --effect fog_overlay_dense --width 1920 --height 1080 --duration 10 --out fog.webm
```

Browse the catalog:

```bash
optic-fx list                                       # all 86, one slug per line
optic-fx list --category atmospheric_and_diffusion  # 25
optic-fx categories                                 # four category names
optic-fx info anamorphic_lens_flare_gold            # engine, variant, description
```

Preview an effect on a generated test frame:

```bash
optic-fx preview --effect kaleidoscope_prism_hexagonal --out preview.mp4
```

Print the OpticConfig JSON schema: `optic-fx schema --pretty`

---

## Composition with color-fx, video-fx

optic-fx is one of three siblings. Each handles a distinct layer of the finishing pass.

```python
# video-fx → optic-fx → color-fx pipeline
from optic_fx import apply_optic_chain
apply_optic_chain(
    video="dof.mp4",       # output from video-fx
    effects=[
        {"effect": "barrel_distortion_gentle"},
        {"effect": "anamorphic_lens_flare_subtle", "position": (0.7, 0.3)},
        {"effect": "halation_warm", "intensity": 0.5},
        {"effect": "vignette_subtle"},
    ],
    output="optic.mp4",    # feed into color-fx next
)
```

Pre-render a long atmospheric layer once, reuse across many shots:

```python
from optic_fx import pre_render_atmosphere, composite_atmosphere

atmo = pre_render_atmosphere(
    effect="volumetric_atmosphere_cathedral",
    width=1920, height=1080, duration=10.0,
    output="atmo_cathedral.webm",
)
for shot in shots:
    composite_atmosphere(shot, atmo, output=f"{shot.stem}_atmo.mp4")
```

The siblings share no runtime state and can run in any order. Each installs independently.

---

## What this is NOT

optic-fx is not a node-based compositor. No GUI, no real-time preview, no timeline. It processes
clips, not projects.

The 9 effects overlapping with video-fx — Fisheye Lens, Tilt Shift, Rack Focus, Depth of Field,
Chromatic Aberration, Rolling Shutter, Light Leak, God Rays, Underwater Caustics — are deliberately
absent. Use the video-fx versions for those.

Atmospheric layers are procedurally generated, not physically simulated. Fog does not respond to
scene geometry. The `depth_aware` field requires an external depth map — optic-fx does not produce
depth maps.

---

## Provenance

Built at Trollfabriken AITrix AB for the CineForge pipeline's anamorphic flare and atmospheric work,
AIMOS Insight municipal report visual polish, SocKartan civic transparency segments, and the
Timelock Film AB festival slate's optical finishing pass.

Source: https://github.com/tomastimelock/optic-fx — License: MIT
