Extract caption metadata route assembly
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CaptionMetadataRouteRequest:
|
||||
row: dict[str, Any]
|
||||
detail_level: str
|
||||
keep_style: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CaptionMetadataRoute:
|
||||
prose: str
|
||||
method: str
|
||||
|
||||
def as_tuple(self) -> tuple[str, str]:
|
||||
return self.prose, self.method
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CaptionMetadataRouteDependencies:
|
||||
item_labels: tuple[str, ...]
|
||||
clean_text: Callable[[Any], str]
|
||||
row_value: Callable[[dict[str, Any], str, tuple[str, ...]], str]
|
||||
field_row_value: Callable[[dict[str, Any], str], str]
|
||||
clean_clothing: Callable[[str], str]
|
||||
normalize_composition: Callable[[str], str]
|
||||
expression_disabled: Callable[[dict[str, Any]], bool]
|
||||
detail_allows: Callable[..., bool]
|
||||
join_sentences: Callable[[list[str]], str]
|
||||
human_join: Callable[[list[str]], str]
|
||||
article: Callable[[str], str]
|
||||
cap_first: Callable[[str], str]
|
||||
body_phrase: Callable[[Any, Any], str]
|
||||
single_caption_front: Callable[[dict[str, Any]], dict[str, str]]
|
||||
pose_clause: Callable[[str], str]
|
||||
age_subject: Callable[[str, str], str]
|
||||
clean_age_phrase: Callable[[str], str]
|
||||
subject_phrase_from_counts: Callable[[dict[str, Any]], str]
|
||||
verb_for_row: Callable[[dict[str, Any]], str]
|
||||
metadata_action_label: Callable[[dict[str, Any]], str]
|
||||
natural_cast_descriptor_text: Callable[[str], str]
|
||||
cast_labels: Callable[[str], list[str]]
|
||||
natural_label_text: Callable[[Any, list[str]], str]
|
||||
metadata_to_prose: Callable[[dict[str, Any], str, bool], tuple[str, str]]
|
||||
|
||||
|
||||
def pronoun(subject: str) -> str:
|
||||
return "She" if subject == "woman" else "He"
|
||||
|
||||
|
||||
def possessive_pronoun(subject: str) -> str:
|
||||
return "Her" if subject == "woman" else "His"
|
||||
|
||||
|
||||
def couple_clothing_sentence(clothing: str, clean_text: Callable[[Any], str]) -> str:
|
||||
clothing = clean_text(clothing)
|
||||
lower = clothing.lower()
|
||||
partner_text = re.sub(r"\bPartner ([AB]) wears\b", r"Partner \1 wearing", clothing)
|
||||
partner_text = re.sub(r"\bPartner ([AB]) has\b", r"Partner \1 with", partner_text)
|
||||
if lower.startswith("partner a "):
|
||||
return f"The outfits show {partner_text}"
|
||||
if lower.startswith(("two ", "paired ", "coordinated ")):
|
||||
return f"The outfits are {partner_text}"
|
||||
return f"They wear {clothing}"
|
||||
|
||||
|
||||
def single_from_row_result(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> CaptionMetadataRoute | None:
|
||||
row = request.row
|
||||
detail_level = request.detail_level
|
||||
keep_style = request.keep_style
|
||||
subject = deps.clean_text(row.get("primary_subject") or row.get("subject") or "")
|
||||
if subject not in ("woman", "man"):
|
||||
return None
|
||||
|
||||
caption_front = deps.single_caption_front(row)
|
||||
age = deps.clean_text(row.get("age") or row.get("age_band") or caption_front.get("caption_age") or "")
|
||||
body_phrase = deps.field_row_value(row, "body_phrase") or caption_front.get("caption_body_phrase", "")
|
||||
if not body_phrase:
|
||||
body = deps.clean_text(row.get("body_type") or row.get("body") or "")
|
||||
figure = deps.clean_text(row.get("figure"))
|
||||
body_phrase = deps.body_phrase(body, figure)
|
||||
|
||||
skin = deps.field_row_value(row, "skin") or caption_front.get("caption_skin", "")
|
||||
hair = deps.field_row_value(row, "hair") or caption_front.get("caption_hair", "")
|
||||
eyes = deps.field_row_value(row, "eyes") or caption_front.get("caption_eyes", "")
|
||||
item = deps.row_value(row, "item", deps.item_labels)
|
||||
if item:
|
||||
item = deps.clean_clothing(item)
|
||||
if not item:
|
||||
item = deps.clean_clothing(deps.row_value(row, "clothing", ("Clothing", "Erotic outfit")))
|
||||
scene = deps.row_value(row, "scene_text", ("Scene", "Setting"))
|
||||
pose = deps.row_value(row, "pose", ("Pose",))
|
||||
expression = "" if deps.expression_disabled(row) else deps.row_value(row, "expression", ("Facial expression", "Facial expressions"))
|
||||
composition = deps.normalize_composition(deps.row_value(row, "composition", ("Composition",)))
|
||||
camera_scene = deps.clean_text(row.get("camera_scene_directive"))
|
||||
prop = deps.row_value(row, "prop", ("Prop/detail",))
|
||||
style = deps.field_row_value(row, "style") if keep_style else ""
|
||||
|
||||
parts = []
|
||||
opener = deps.age_subject(age, subject)
|
||||
appearance_details = [piece for piece in (skin, hair, eyes) if piece]
|
||||
if body_phrase:
|
||||
parts.append(f"{opener} has {deps.article(body_phrase)} {body_phrase}")
|
||||
elif appearance_details:
|
||||
parts.append(f"{opener} has {deps.human_join(appearance_details)}")
|
||||
else:
|
||||
parts.append(opener)
|
||||
if body_phrase and appearance_details:
|
||||
parts.append(f"{pronoun(subject)} has {deps.human_join(appearance_details)}")
|
||||
if item:
|
||||
verb = "wears" if subject == "woman" else "is dressed in"
|
||||
parts.append(f"{pronoun(subject)} {verb} {item}")
|
||||
if prop:
|
||||
parts.append(f"{pronoun(subject)} is {prop}")
|
||||
if pose:
|
||||
parts.append(f"{pronoun(subject)} is {deps.pose_clause(pose)}")
|
||||
if expression:
|
||||
parts.append(f"{possessive_pronoun(subject)} expression is {expression}")
|
||||
if scene:
|
||||
parts.append(f"The setting is {scene}")
|
||||
if deps.detail_allows(detail_level) and camera_scene:
|
||||
parts.append(camera_scene)
|
||||
if deps.detail_allows(detail_level) and composition:
|
||||
parts.append(f"The composition is {composition}")
|
||||
if keep_style and style:
|
||||
parts.append(f"The visual style is {style}")
|
||||
return CaptionMetadataRoute(deps.join_sentences(parts), "metadata(single)")
|
||||
|
||||
|
||||
def couple_from_row_result(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> CaptionMetadataRoute | None:
|
||||
row = request.row
|
||||
detail_level = request.detail_level
|
||||
keep_style = request.keep_style
|
||||
subject = deps.clean_text(row.get("subject_phrase") or row.get("primary_subject"))
|
||||
primary = deps.clean_text(row.get("primary_subject"))
|
||||
if "couple" not in primary and subject not in ("two women", "two men", "a woman and a man"):
|
||||
if not primary.startswith("two ") and " and " not in subject:
|
||||
return None
|
||||
if subject == "woman and man":
|
||||
subject = "a woman and a man"
|
||||
|
||||
ages = deps.row_value(row, "age", ("Ages",)) or deps.clean_text(row.get("age_band"))
|
||||
body = deps.row_value(row, "body", ("Body types",)) or deps.clean_text(row.get("body_type"))
|
||||
pose = deps.row_value(row, "pose", ("Pose",))
|
||||
pose = pose.replace(", affectionate and flirtatious but non-explicit", "")
|
||||
clothing = deps.clean_clothing(deps.row_value(row, "item", deps.item_labels) or deps.row_value(row, "clothing", ("Clothing",)))
|
||||
scene = deps.row_value(row, "scene_text", ("Scene", "Setting"))
|
||||
expression = ""
|
||||
if not deps.expression_disabled(row):
|
||||
expression = deps.row_value(row, "character_expression_text") or deps.row_value(
|
||||
row,
|
||||
"expression",
|
||||
("Facial expressions", "Facial expression"),
|
||||
)
|
||||
composition = deps.normalize_composition(deps.row_value(row, "composition", ("Composition",)))
|
||||
camera_scene = deps.clean_text(row.get("camera_scene_directive"))
|
||||
style = deps.field_row_value(row, "style") if keep_style else ""
|
||||
|
||||
parts = [f"{deps.cap_first(subject)} are adults"]
|
||||
if ages:
|
||||
parts.append(f"The age detail is {deps.clean_age_phrase(ages)}")
|
||||
if body:
|
||||
parts.append(f"Their body types are {body}")
|
||||
if clothing:
|
||||
parts.append(couple_clothing_sentence(clothing, deps.clean_text))
|
||||
if pose:
|
||||
parts.append(f"The pose is {pose}")
|
||||
if scene:
|
||||
parts.append(f"The setting is {scene}")
|
||||
if deps.detail_allows(detail_level) and camera_scene:
|
||||
parts.append(camera_scene)
|
||||
if expression:
|
||||
parts.append(f"Their expressions are {expression}")
|
||||
if deps.detail_allows(detail_level) and composition:
|
||||
parts.append(f"The composition is {composition}")
|
||||
if keep_style and style:
|
||||
parts.append(f"The visual style is {style}")
|
||||
return CaptionMetadataRoute(deps.join_sentences(parts), "metadata(couple)")
|
||||
|
||||
|
||||
def configured_cast_from_row_result(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> CaptionMetadataRoute | None:
|
||||
row = request.row
|
||||
detail_level = request.detail_level
|
||||
keep_style = request.keep_style
|
||||
if deps.clean_text(row.get("subject_type")) != "configured_cast":
|
||||
if "hardcore sexual poses" not in deps.clean_text(row.get("main_category")).lower():
|
||||
return None
|
||||
|
||||
subject = deps.subject_phrase_from_counts(row)
|
||||
verb = deps.verb_for_row(row)
|
||||
cast = deps.row_value(row, "cast_summary", ("Cast",))
|
||||
role_graph = deps.row_value(row, "role_graph", ("Role graph",))
|
||||
item = deps.row_value(row, "item", deps.item_labels)
|
||||
scene = deps.row_value(row, "scene_text", ("Setting", "Scene"))
|
||||
expression = ""
|
||||
if not deps.expression_disabled(row):
|
||||
expression = deps.row_value(row, "character_expression_text") or deps.row_value(
|
||||
row,
|
||||
"expression",
|
||||
("Facial expressions", "Facial expression"),
|
||||
)
|
||||
composition = deps.normalize_composition(deps.row_value(row, "composition", ("Composition",)))
|
||||
camera_scene = deps.clean_text(row.get("camera_scene_directive"))
|
||||
cast_descriptor_text = deps.row_value(row, "cast_descriptor_text", ("Characters", "Cast descriptors"))
|
||||
scene_kind = deps.field_row_value(row, "scene_kind") or "explicit adult sex scene"
|
||||
style = deps.field_row_value(row, "style") if keep_style else ""
|
||||
|
||||
parts = [f"{deps.cap_first(subject)} {verb} shown as a consensual {scene_kind}"]
|
||||
if cast_descriptor_text:
|
||||
parts.append(deps.natural_cast_descriptor_text(cast_descriptor_text))
|
||||
if cast and not cast_descriptor_text:
|
||||
parts.append(f"The cast is {cast}")
|
||||
if role_graph:
|
||||
parts.append(role_graph)
|
||||
if item:
|
||||
parts.append(f"The {deps.metadata_action_label(row)} is {item}")
|
||||
scene_bits = []
|
||||
if scene:
|
||||
scene_bits.append(f"set in {scene}")
|
||||
if expression:
|
||||
scene_bits.append(f"with {expression}")
|
||||
if composition:
|
||||
scene_bits.append(f"framed as {composition}")
|
||||
if scene_bits and deps.detail_allows(detail_level):
|
||||
parts.append(", ".join(scene_bits))
|
||||
if deps.detail_allows(detail_level) and camera_scene:
|
||||
parts.append(camera_scene)
|
||||
if keep_style and style:
|
||||
parts.append(f"The visual style is {style}")
|
||||
return CaptionMetadataRoute(deps.join_sentences(parts), "metadata(configured_cast)")
|
||||
|
||||
|
||||
def group_or_layout_from_row_result(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> CaptionMetadataRoute | None:
|
||||
row = request.row
|
||||
detail_level = request.detail_level
|
||||
keep_style = request.keep_style
|
||||
primary = deps.clean_text(row.get("primary_subject"))
|
||||
if "group" not in primary and primary != "layout scene":
|
||||
return None
|
||||
|
||||
subject = deps.field_row_value(row, "subject_phrase") or primary
|
||||
age = deps.row_value(row, "age", ("Ages",)) or deps.clean_text(row.get("age_band"))
|
||||
item = deps.clean_clothing(deps.row_value(row, "item", deps.item_labels) or deps.row_value(row, "clothing", ("Clothing",)))
|
||||
scene = deps.row_value(row, "scene_text", ("Scene", "Setting"))
|
||||
expression = ""
|
||||
if not deps.expression_disabled(row):
|
||||
expression = deps.row_value(row, "character_expression_text") or deps.row_value(
|
||||
row,
|
||||
"expression",
|
||||
("Facial expressions", "Facial expression"),
|
||||
)
|
||||
composition = deps.normalize_composition(deps.row_value(row, "composition", ("Composition",)))
|
||||
camera_scene = deps.clean_text(row.get("camera_scene_directive"))
|
||||
style = deps.field_row_value(row, "style") if keep_style else ""
|
||||
|
||||
if primary == "layout scene":
|
||||
parts = [f"{deps.cap_first(subject)} is arranged as an adults-only designed illustration layout"]
|
||||
if expression:
|
||||
parts.append(f"The featured expression is {expression}")
|
||||
else:
|
||||
parts = [f"{deps.cap_first(subject)} includes adults"]
|
||||
if age:
|
||||
parts[0] += f" ages {age}"
|
||||
if item:
|
||||
parts.append(f"They wear {item}")
|
||||
if expression:
|
||||
parts.append(f"They show {expression}")
|
||||
if scene:
|
||||
parts.append(f"The setting is {scene}")
|
||||
if deps.detail_allows(detail_level) and camera_scene:
|
||||
parts.append(camera_scene)
|
||||
if deps.detail_allows(detail_level) and composition:
|
||||
parts.append(f"The composition is {composition}")
|
||||
if keep_style and style:
|
||||
parts.append(f"The visual style is {style}")
|
||||
return CaptionMetadataRoute(deps.join_sentences(parts), "metadata(group_layout)")
|
||||
|
||||
|
||||
def insta_of_pair_from_row_result(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> CaptionMetadataRoute | None:
|
||||
row = request.row
|
||||
detail_level = request.detail_level
|
||||
keep_style = request.keep_style
|
||||
if deps.clean_text(row.get("mode")).lower() != "insta/of":
|
||||
return None
|
||||
soft_row = row.get("softcore_row")
|
||||
hard_row = row.get("hardcore_row")
|
||||
if not isinstance(soft_row, dict) or not isinstance(hard_row, dict):
|
||||
return None
|
||||
|
||||
hard_row_for_text = dict(hard_row)
|
||||
options = row.get("options")
|
||||
if isinstance(options, dict) and options.get("continuity") == "same_creator_same_room":
|
||||
if soft_row.get("scene_text"):
|
||||
hard_row_for_text["scene_text"] = soft_row["scene_text"]
|
||||
if soft_row.get("composition"):
|
||||
hard_row_for_text["composition"] = soft_row["composition"]
|
||||
|
||||
soft_text, _soft_method = deps.metadata_to_prose(soft_row, detail_level, keep_style)
|
||||
hard_text, _hard_method = deps.metadata_to_prose(hard_row_for_text, detail_level, keep_style)
|
||||
descriptor = deps.clean_text(row.get("shared_descriptor"))
|
||||
options = row.get("options") if isinstance(row.get("options"), dict) else {}
|
||||
cast_descriptors = row.get("shared_cast_descriptors")
|
||||
if isinstance(cast_descriptors, list):
|
||||
cast_descriptor_text = "; ".join(deps.clean_text(item) for item in cast_descriptors if deps.clean_text(item))
|
||||
else:
|
||||
cast_descriptor_text = deps.clean_text(cast_descriptors)
|
||||
labels = deps.cast_labels(cast_descriptor_text)
|
||||
|
||||
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
|
||||
|
||||
parts = []
|
||||
if cast_descriptor_text and same_soft_cast:
|
||||
parts.append(deps.natural_cast_descriptor_text(cast_descriptor_text))
|
||||
elif descriptor:
|
||||
parts.append(f"A {descriptor}")
|
||||
if cast_descriptor_text and not same_soft_cast:
|
||||
parts.append(deps.natural_cast_descriptor_text(cast_descriptor_text))
|
||||
if same_soft_cast:
|
||||
parts.append("The softcore version keeps the same adult cast present together in a non-explicit teaser setup")
|
||||
partner_styling = row.get("softcore_partner_styling")
|
||||
if isinstance(partner_styling, dict):
|
||||
outfits = partner_styling.get("outfits")
|
||||
if isinstance(outfits, list):
|
||||
outfit_text = deps.human_join([deps.clean_text(item) for item in outfits if deps.clean_text(item)])
|
||||
outfit_text = deps.natural_label_text(outfit_text, labels)
|
||||
if outfit_text:
|
||||
parts.append(f"Softcore partner styling: {outfit_text}")
|
||||
pose = deps.clean_text(partner_styling.get("pose"))
|
||||
if pose:
|
||||
parts.append(f"The shared softcore cast pose is {pose}")
|
||||
if soft_text:
|
||||
parts.append(f"Softcore version: {soft_text}")
|
||||
if hard_text:
|
||||
parts.append(f"Hardcore version: {hard_text}")
|
||||
if not parts:
|
||||
return None
|
||||
return CaptionMetadataRoute(deps.join_sentences(parts), "metadata(insta_of_pair)")
|
||||
|
||||
|
||||
def single_from_row(request: CaptionMetadataRouteRequest, deps: CaptionMetadataRouteDependencies) -> tuple[str, str] | None:
|
||||
result = single_from_row_result(request, deps)
|
||||
return result.as_tuple() if result else None
|
||||
|
||||
|
||||
def couple_from_row(request: CaptionMetadataRouteRequest, deps: CaptionMetadataRouteDependencies) -> tuple[str, str] | None:
|
||||
result = couple_from_row_result(request, deps)
|
||||
return result.as_tuple() if result else None
|
||||
|
||||
|
||||
def configured_cast_from_row(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> tuple[str, str] | None:
|
||||
result = configured_cast_from_row_result(request, deps)
|
||||
return result.as_tuple() if result else None
|
||||
|
||||
|
||||
def group_or_layout_from_row(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> tuple[str, str] | None:
|
||||
result = group_or_layout_from_row_result(request, deps)
|
||||
return result.as_tuple() if result else None
|
||||
|
||||
|
||||
def insta_of_pair_from_row(
|
||||
request: CaptionMetadataRouteRequest,
|
||||
deps: CaptionMetadataRouteDependencies,
|
||||
) -> tuple[str, str] | None:
|
||||
result = insta_of_pair_from_row_result(request, deps)
|
||||
return result.as_tuple() if result else None
|
||||
Reference in New Issue
Block a user