File size: 5,820 Bytes
4fca835 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | """Structured visual attributes for scene windows: shot type, setting, era.
These let footage searches match on *how* a scene is filmed and *when/where*
it is set — "aerial shots of a Kingdom Hall exterior", "modern-day disaster
relief", "Bible-times scenes set indoors" — which a free-text description alone
captures inconsistently.
Single source of truth for the controlled vocabularies, normalization of the
model's free text into those vocabularies, and the search phrase that gets
folded into a window's embedded text so the existing semantic search picks the
attributes up (no query-side changes needed). Both the vision pipeline (going
forward) and the text-backfill pass import from here so the two can't drift.
"""
from __future__ import annotations
# canonical value -> synonym-rich phrase woven into the embedded text. The
# phrase is what makes the attribute searchable: a query like "aerial drone
# shot" embeds near a window whose text contains "aerial drone overhead".
SHOT_TYPES: dict[str, str] = {
"aerial": "aerial drone overhead bird's-eye shot",
"establishing": "wide establishing landscape shot",
"medium": "medium shot",
"close-up": "close-up tight shot",
"pov": "point-of-view first-person shot",
"talking-head": "interview talking-head piece-to-camera shot",
"unclear": "",
}
SETTINGS: dict[str, str] = {
"interior": "indoor interior",
"exterior": "outdoor exterior",
"mixed": "both indoor and outdoor",
"unclear": "",
}
ERAS: dict[str, str] = {
"ancient": "Bible times ancient biblical era",
"medieval": "medieval middle ages",
"early-1900s": "early 1900s early twentieth century historical",
"mid-1900s": "mid 1900s mid twentieth century historical",
"late-1900s": "1970s 1980s 1990s late twentieth century",
"modern-day": "modern-day present-day contemporary",
"unclear": "",
}
# Free-text variants the model may emit -> canonical key. Checked as substrings
# (longest-first) so "close up" / "closeup" / "tight" all map to "close-up".
_SHOT_ALIASES: tuple[tuple[str, str], ...] = (
("drone", "aerial"), ("aerial", "aerial"), ("overhead", "aerial"),
("bird", "aerial"), ("tilt-shift", "aerial"),
("establishing", "establishing"), ("wide", "establishing"),
("landscape", "establishing"), ("vista", "establishing"),
("close-up", "close-up"), ("close up", "close-up"), ("closeup", "close-up"),
("tight", "close-up"), ("portrait", "close-up"), ("macro", "close-up"),
("point-of-view", "pov"), ("point of view", "pov"), ("pov", "pov"),
("first-person", "pov"),
("talking head", "talking-head"), ("talking-head", "talking-head"),
("piece to camera", "talking-head"), ("to camera", "talking-head"),
("interview", "talking-head"), ("presenter", "talking-head"),
("medium", "medium"),
)
_SETTING_ALIASES: tuple[tuple[str, str], ...] = (
("interior", "interior"), ("indoor", "interior"), ("inside", "interior"),
("exterior", "exterior"), ("outdoor", "exterior"), ("outside", "exterior"),
("mixed", "mixed"), ("both", "mixed"),
)
_ERA_ALIASES: tuple[tuple[str, str], ...] = (
("biblical", "ancient"), ("bible times", "ancient"), ("ancient", "ancient"),
("first century", "ancient"), ("antiquity", "ancient"),
("medieval", "medieval"), ("middle ages", "medieval"),
("early 1900", "early-1900s"), ("early-1900", "early-1900s"),
("early twentieth", "early-1900s"), ("1910", "early-1900s"),
("1920", "early-1900s"), ("1930", "early-1900s"),
("mid 1900", "mid-1900s"), ("mid-1900", "mid-1900s"),
("1940", "mid-1900s"), ("1950", "mid-1900s"), ("1960", "mid-1900s"),
("1970", "late-1900s"), ("1980", "late-1900s"), ("1990", "late-1900s"),
("late 1900", "late-1900s"), ("late-1900", "late-1900s"),
("late twentieth", "late-1900s"),
("modern", "modern-day"), ("present", "modern-day"),
("contemporary", "modern-day"), ("today", "modern-day"),
("2000", "modern-day"), ("2010", "modern-day"), ("2020", "modern-day"),
)
def _normalize(raw: object, aliases: tuple[tuple[str, str], ...], valid: dict[str, str]) -> str:
"""Map the model's free text to a canonical key; 'unclear' when no match."""
text = str(raw or "").strip().lower()
if not text:
return "unclear"
if text in valid: # already canonical
return text
for needle, canonical in aliases:
if needle in text:
return canonical
return "unclear"
def normalize_shot_type(raw: object) -> str:
return _normalize(raw, _SHOT_ALIASES, SHOT_TYPES)
def normalize_setting(raw: object) -> str:
return _normalize(raw, _SETTING_ALIASES, SETTINGS)
def normalize_era(raw: object) -> str:
return _normalize(raw, _ERA_ALIASES, ERAS)
def attribute_search_phrase(shot_type: str, setting: str, era: str) -> str:
"""Build the phrase appended to a window's description before embedding.
Empty parts ('unclear') are omitted. Returns '' when nothing is known, so a
window with no usable attributes embeds exactly as its description did.
"""
parts: list[str] = []
shot = SHOT_TYPES.get(shot_type or "unclear", "")
setting_phrase = SETTINGS.get(setting or "unclear", "")
era_phrase = ERAS.get(era or "unclear", "")
if shot:
parts.append(f"Camera: {shot}.")
if setting_phrase:
parts.append(f"Setting: {setting_phrase}.")
if era_phrase:
parts.append(f"Era: {era_phrase}.")
return " ".join(parts)
def embed_text(description: str, shot_type: str, setting: str, era: str) -> str:
"""The full text to embed for a window: description + attribute phrase."""
phrase = attribute_search_phrase(shot_type, setting, era)
description = (description or "").strip()
return f"{description} {phrase}".strip() if phrase else description
|