Metadata-Version: 2.4
Name: openbrand
Version: 0.7.0
Summary: Extract brand assets (brand name, logos, colors) from any website. A dependency-light Python alternative to the openbrand npm package.
Project-URL: Homepage, https://github.com/cassidyhhaas/openbrand-py
Project-URL: Repository, https://github.com/cassidyhhaas/openbrand-py
Project-URL: Issues, https://github.com/cassidyhhaas/openbrand-py/issues
Project-URL: Changelog, https://github.com/cassidyhhaas/openbrand-py/releases
Author-email: Cass <cassidyhhaas@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: brand,color-extraction,favicon,logo,metadata,scraping,web
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: pillow>=10.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: tinycss2>=1.3.0
Provides-Extra: async
Requires-Dist: httpx>=0.27.0; extra == 'async'
Provides-Extra: dev
Requires-Dist: httpx>=0.27.0; extra == 'dev'
Requires-Dist: mypy>=1.13.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.7.0; extra == 'dev'
Description-Content-Type: text/markdown

# openbrand

Extract brand assets — **brand name, logos, and a color palette** — from any
website URL. A dependency-light Python port of the open-source
[`openbrand`](https://github.com/ethanjyx/OpenBrand) npm package, built to
remove any coupling to the npm registry.

**Three required dependencies:** [Pillow](https://python-pillow.org/) (image
decoding for color quantization), [Pydantic v2](https://docs.pydantic.dev/)
(typed result objects), and [tinycss2](https://doc.courtbouillon.org/tinycss2/)
(CSS parsing for color extraction). Everything else is the Python standard
library. `httpx` is an optional extra for the native-async backend.

All return objects are Pydantic v2 `BaseModel`s — `BrandAssets`, `LogoAsset`,
`ColorPalette`, `ColorAsset`, `Resolution`, `ExtractionError`,
`ExtractionResult` — so they
drop cleanly into FastAPI response models, expose
`.model_dump()` / `.model_dump_json()`, and ship `model_json_schema()` for
OpenAPI generation.

## Install

```bash
pip install openbrand
# or, with the native-async backend (httpx):
pip install "openbrand[async]"
```

Requires Python 3.12+.

## Library use

```python
from openbrand import extract_brand_assets

result = extract_brand_assets("https://stripe.com")
if result.ok:
    assets = result.data
    print(assets.brand_name)              # "Stripe"
    for logo in assets.logos:
        print(logo.type, logo.url)
    palette = assets.colors               # a ColorPalette grouped by role
    if palette.primary:
        print(palette.primary[0].hex)     # single best primary, e.g. "#635BFF"
    for accent in palette.accent:
        print(accent.hex, accent.source)  # e.g. "#00D4FF" "css_var"
else:
    print(result.error.code, result.error.message)
```

`assets.colors` is a `ColorPalette` with five role lists, each ordered
best-first and possibly empty (roles are best-effort, never synthesized):

| Field | Holds |
|-------|-------|
| `primary` | the dominant color and its tonal family; `primary[0]` is the single best primary |
| `secondary` | supporting colors that complement or smoothly contrast the primary |
| `accent` | high-pop colors for CTAs / key links |
| `neutral_darks` | dark neutrals for typography / dark surfaces |
| `neutral_lights` | light neutrals for backgrounds / light surfaces |

Each entry is a `ColorAsset` with `hex` (`"#RRGGBB"`), `role`
(`primary` / `secondary` / `accent` / `neutral_dark` / `neutral_light`, or
`None`), `saturation` (float in `[0, 1]`, exposed so you can re-rank), and
`source` (`meta` / `manifest` / `landmark_bg` / `css_var` / `logo`, or
`None`). A `palette.model_dump()` looks like:

```python
{
    "primary": [
        {"hex": "#635BFF", "role": "primary", "saturation": 0.64, "source": "css_var"},
    ],
    "secondary": [
        {"hex": "#0A2540", "role": "secondary", "saturation": 0.84, "source": "landmark_bg"},
    ],
    "accent": [
        {"hex": "#00D4FF", "role": "accent", "saturation": 1.0, "source": "css_var"},
    ],
    "neutral_darks": [
        {"hex": "#1A1A1A", "role": "neutral_dark", "saturation": 0.0, "source": "landmark_bg"},
    ],
    "neutral_lights": [
        {"hex": "#FFFFFF", "role": "neutral_light", "saturation": 0.0, "source": "landmark_bg"},
    ],
}
```

`extract_brand_assets` never raises on network/HTTP failures — they come back
via `result.ok == False` with a classified `result.error.code` (one of
`ACCESS_BLOCKED`, `NOT_FOUND`, `SERVER_ERROR`, `NETWORK_ERROR`,
`EMPTY_CONTENT`).

## Async use (FastAPI, asyncio, …)

```python
from openbrand import aextract_brand_assets

result = await aextract_brand_assets("https://stripe.com")
```

Drops cleanly into any event loop. Two backends, same return value:

- **Default** (no extras): runs the sync pipeline on a worker thread via
  `asyncio.to_thread`. Zero extra deps, never blocks the event loop, no
  in-extraction concurrency.
- **`pip install "openbrand[async]"`**: switches to a native async backend
  powered by `httpx` that fetches the page and pre-warms all candidate-image
  bytes **concurrently**, then runs the heuristics against cache. Measurably
  faster on sites with many logo candidates.

## CLI

```bash
openbrand https://stripe.com              # pretty JSON
openbrand https://stripe.com --indent 0   # compact, single line
python -m openbrand https://stripe.com    # module form
```

Exits non-zero when extraction fails.

## What it extracts

| Asset | Sources |
|-------|---------|
| **brand_name** | `og:site_name` → `application-name` → header/nav logo alt → `<title>` segment → domain |
| **logos** | favicons, apple-touch-icons, `<img>`, inline `<svg>` (serialized to a `data:` URI), ranked by a two-pass header/nav heuristic; dimensions probed per image |
| **colors** | declared (`theme-color`, `msapplication-TileColor`, `manifest.json`), brand-named CSS custom properties, landmark `background-color` + typography `color` from `<style>` blocks, and saturation-weighted quantization of the logo imagery; returned as a `ColorPalette` grouped into `primary` / `secondary` / `accent` / `neutral_darks` / `neutral_lights` |

## Links

- Source: [github.com/cassidyhhaas/openbrand-py](https://github.com/cassidyhhaas/openbrand-py)
- Issues: [github.com/cassidyhhaas/openbrand-py/issues](https://github.com/cassidyhhaas/openbrand-py/issues)
- Contributing & release process: [CONTRIBUTING.md](https://github.com/cassidyhhaas/openbrand-py/blob/main/CONTRIBUTING.md)

## License

MIT (matching the upstream `openbrand` package).
