Metadata-Version: 2.4
Name: openbrand
Version: 0.10.0
Summary: Extract a brand's profile (name, description, logos, colors) from any website. A dependency-light, async-native Python library.
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: httpx>=0.27.0
Requires-Dist: pillow>=10.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: tinycss2>=1.3.0
Provides-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 a brand's profile — **brand name, description, logos, and a color
palette** — from any website URL. A dependency-light, async-native Python
library, built to feed services that render themed widgets or pages on a
brand's behalf.

**Four dependencies:** [Pillow](https://python-pillow.org/) (image decoding for
color quantization), [Pydantic v2](https://docs.pydantic.dev/) (typed result
objects), [tinycss2](https://doc.courtbouillon.org/tinycss2/) (CSS parsing for
color extraction), and [httpx](https://www.python-httpx.org/) (concurrent async
fetching). Everything else is the Python standard library.

All return objects are Pydantic v2 `BaseModel`s — `BrandProfile`, `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
```

Requires Python 3.12+.

## Usage

The library is async-native — every extraction runs its fetches concurrently
on top of `httpx`, and drops cleanly into FastAPI / asyncio:

```python
from openbrand import extract_brand_profile

result = await extract_brand_profile("https://stripe.com")
if result.ok:
    profile = result.data
    print(profile.brand_name)             # "Stripe"
    print(profile.description)            # "Financial infrastructure…" (or None)
    for logo in profile.logos:
        print(logo.type, logo.url)
    palette = profile.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)
```

From a synchronous script, wrap the call in `asyncio.run`:

```python
import asyncio
from openbrand import extract_brand_profile

result = asyncio.run(extract_brand_profile("https://stripe.com"))
```

`profile.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_profile` 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`).

Each call opens one `httpx.AsyncClient` and fans out its network work — logo
dimension probing, the web app manifest, and linked stylesheets — concurrently,
so a page with many logo candidates resolves in roughly one round-trip's time.

## 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 |
| **description** | `og:description` → `<meta name="description">` → `twitter:description`; `None` when absent (optional) |
| **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.
