Files changed (1) hide show
  1. attribute_map.py +257 -0
attribute_map.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Attribute map utility
3
+ ---------------------
4
+
5
+ Expose a structured mapping of the voice design attributes described in
6
+ `prompt.txt` together with the inline emotion tags listed in
7
+ `emotions.txt`. The module returns data that can be consumed by tooling
8
+ to programmatically build descriptions or sweep attribute combinations.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Dict, Iterable, List, TypedDict
16
+
17
+
18
+ class AttributeMap(TypedDict, total=False):
19
+ metadata: Dict[str, str]
20
+ emotions: Dict[str, object]
21
+ shared_attributes: Dict[str, Iterable[str]]
22
+ realistic_only: Dict[str, object]
23
+ creative_only: Dict[str, object]
24
+ constraints: Dict[str, object]
25
+ defaults: Dict[str, str]
26
+
27
+
28
+ MODULE_DIR = Path(__file__).resolve().parent
29
+ EMOTIONS_FILE = MODULE_DIR / "emotions.txt"
30
+ PROMPT_FILE = MODULE_DIR / "prompt.txt"
31
+
32
+
33
+ def _load_emotion_tags(path: Path) -> List[str]:
34
+ if not path.exists():
35
+ raise FileNotFoundError(f"Emotion tag file not found: {path}")
36
+
37
+ tags: List[str] = []
38
+ with path.open("r", encoding="utf-8") as handle:
39
+ for raw_line in handle:
40
+ line = raw_line.strip()
41
+ if not line or line.startswith("#"):
42
+ continue
43
+ tags.append(line)
44
+ return tags
45
+
46
+
47
+ def build_attribute_map(
48
+ emotions_path: Path | None = None,
49
+ prompt_path: Path | None = None,
50
+ ) -> AttributeMap:
51
+ """
52
+ Return an attribute map that mirrors the guidance in `prompt.txt`.
53
+
54
+ Parameters
55
+ ----------
56
+ emotions_path:
57
+ Optional override for the emotion tag file. Defaults to
58
+ `maya1/emotions.txt`.
59
+ prompt_path:
60
+ Included for symmetry/future validation. Defaults to
61
+ `maya1/prompt.txt` and is currently used only for metadata.
62
+ """
63
+
64
+ emotions_file = emotions_path or EMOTIONS_FILE
65
+ prompt_file = prompt_path or PROMPT_FILE
66
+
67
+ emotion_tags = _load_emotion_tags(emotions_file)
68
+
69
+ attribute_map: AttributeMap = {
70
+ "metadata": {
71
+ "source_prompt": str(prompt_file),
72
+ "source_emotions": str(emotions_file),
73
+ "description": (
74
+ "Programmatic representation of Maya voice design attributes. "
75
+ "Derived from prompt.txt guidance."
76
+ ),
77
+ },
78
+ "emotions": {
79
+ "inline_tags": emotion_tags,
80
+ "persona_emotion_attribute": [
81
+ "neutral",
82
+ "energetic",
83
+ "excited",
84
+ "sad",
85
+ "sarcastic",
86
+ "dry",
87
+ ],
88
+ "intensity": ["low", "med", "high"],
89
+ },
90
+ "shared_attributes": {
91
+ "age": ["20s", "30s", "40s"],
92
+ "gender": ["male", "female"],
93
+ "accent": [
94
+ "american",
95
+ "indian",
96
+ "middle_eastern",
97
+ "asian_american",
98
+ "british",
99
+ ],
100
+ "pitch": ["low", "normal", "high"],
101
+ "timbre_realistic": [
102
+ "deep",
103
+ "warm",
104
+ "gravelly",
105
+ "smooth",
106
+ "raspy",
107
+ "nasally",
108
+ "throaty",
109
+ "harsh",
110
+ ],
111
+ "timbre_creative": [
112
+ "deep",
113
+ "warm",
114
+ "gravelly",
115
+ "smooth",
116
+ "raspy",
117
+ "nasally",
118
+ "throaty",
119
+ "harsh",
120
+ "robotic",
121
+ "ethereal",
122
+ ],
123
+ "pacing": [
124
+ "very_slow",
125
+ "slow",
126
+ "conversational",
127
+ "brisk",
128
+ "fast",
129
+ "very_fast",
130
+ ],
131
+ "emotion_intensity": ["low", "med", "high"],
132
+ },
133
+ "realistic_only": {
134
+ "domain": [
135
+ "social_content",
136
+ "podcast",
137
+ "commercial",
138
+ "education",
139
+ "support",
140
+ "entertainment",
141
+ "corporate",
142
+ "viral_content",
143
+ ],
144
+ "speaking_role": {
145
+ "social_content": [
146
+ "youtube_vlogger",
147
+ "social_media_creator",
148
+ "influencer_voice",
149
+ "streamer_companion",
150
+ ],
151
+ "podcast": ["podcast_host", "interviewer"],
152
+ "commercial": [
153
+ "ad_narrator",
154
+ "brand_spokesperson",
155
+ "product_demo_voice",
156
+ "sales_pitch_voice",
157
+ ],
158
+ "education": ["elearning_instructor", "kids_story_voice"],
159
+ "support": [
160
+ "customer_support_agent",
161
+ "virtual_receptionist",
162
+ "healthcare_assistant",
163
+ ],
164
+ "entertainment": [
165
+ "storyteller",
166
+ "social_media_reaction",
167
+ "meme_voice",
168
+ ],
169
+ "corporate": [
170
+ "explainer_video_voice",
171
+ "event_host",
172
+ "corporate_training_narrator",
173
+ ],
174
+ "viral_content": ["short_form_narrator", "meme_voice"],
175
+ },
176
+ "register": ["formal", "neutral", "casual"],
177
+ },
178
+ "creative_only": {
179
+ "character": [
180
+ "animated_cartoon",
181
+ "ai_machine_voice",
182
+ "alien_scifi",
183
+ "seductively",
184
+ "flirty",
185
+ "anime",
186
+ "cyborg",
187
+ "pirate",
188
+ "dark_villain",
189
+ "demon",
190
+ "gangster",
191
+ "mafia",
192
+ "dramatic_narrator",
193
+ "mythical_godlike_magical",
194
+ "spy",
195
+ "vampire",
196
+ "alpha",
197
+ ],
198
+ },
199
+ "constraints": {
200
+ "pitch": {
201
+ "note": (
202
+ "For age 40s, avoid high pitch (use sparingly, <= 15% of uses)."
203
+ )
204
+ },
205
+ "creative_timbre": {
206
+ "robotic": [
207
+ "ai_machine_voice",
208
+ "cyborg",
209
+ "alien_scifi",
210
+ "mythical_godlike_magical",
211
+ ],
212
+ "ethereal": [
213
+ "ai_machine_voice",
214
+ "cyborg",
215
+ "alien_scifi",
216
+ "mythical_godlike_magical",
217
+ ],
218
+ },
219
+ "character_pacing_overrides": {
220
+ "mafia": ["slow", "conversational"],
221
+ "flirty": ["slow", "conversational"],
222
+ "alpha": ["fast", "very_fast"],
223
+ "seductively": ["very_slow", "slow"],
224
+ },
225
+ "defaults": "Default persona emotion should be neutral unless specified.",
226
+ },
227
+ "defaults": {
228
+ "age": "20s",
229
+ "gender": "female",
230
+ "accent": "american",
231
+ "pitch": "high",
232
+ "timbre": "warm",
233
+ "pacing": "brisk",
234
+ "emotion": "excited",
235
+ "emotion_intensity": "med",
236
+ "domain": "social_content",
237
+ "speaking_role": "influencer_voice",
238
+ "register": "casual",
239
+ },
240
+ }
241
+
242
+ return attribute_map
243
+
244
+
245
+ def main() -> None:
246
+ """
247
+ CLI helper: print the attribute map as JSON for inspection.
248
+ """
249
+
250
+ attribute_map = build_attribute_map()
251
+ print(json.dumps(attribute_map, indent=2, sort_keys=True))
252
+
253
+
254
+ if __name__ == "__main__":
255
+ main()
256
+
257
+