Multilingual samples, multi-pass translation design, UI language plan, renderer
Operator direction: English is the source and stays editable; translations are confirm-only. - samples/translations/: Deal Hunter sample chapter 1 in all 10 Qwen3-TTS languages, named deal_hunter_sample_ch1_<LANG>.json. Single-pass LLM drafts, labelled as such in every file - they are the failure mode the multi-pass design exists to fix, and they double as a bad-case corpus for the Pass 4 critic. - scripts/build_sample_translations.py, with a validator that refuses to write on stray Latin residue, duplicated utterances or a wrong count. It caught three defects in my own draft before it would ship. - specs/27-translation-pipeline-multipass.md - six passes, the point being that Pass 4 must be a separate critic call, not the same call that drafted. - specs/28-ui-language-selection.md - confirm-only translations, English editable, selector scoped to the chapter page, language named on the page because the cookie is per browser. - scripts/render_lang_test.py - renders the translations with each speaker's own voice into <project>/Lang_test/, plus a stitched file per language. Dry run resolves all 12 utterances x 10 languages; 11 of 12 use a cached ICL prompt, Trader Romano falls back to ref_audio. The renderer resolves display names through the alias map rather than by munging strings: the translations say 'Kainda' and the speaker record says 'Kainda Walters Foster Murphy'. String-munging silently produced no voice for Kainda in the first draft - the same failure mode as the speaker-review audit. NOT DONE: no audio rendered. Qwen3-TTS generation on this machine is stalled - /v1/tts accepts the connection and never completes (zero POSTs logged across 17 minutes after a clean restart), while /v1/voice-prompt and /load return 422 immediately, so requests do reach the handler and the block is inside generation. Swap is at 16GB of 17.4GB. Left as a known blocker rather than worked around. Suite: 721 passed, 8 pre-existing.
This commit is contained in:
38
samples/translations/README.md
Normal file
38
samples/translations/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Deal Hunter sample chapter - translated
|
||||
|
||||
Sample chapter 1 (the voice-introduction sample) in all 10 languages
|
||||
Qwen3-TTS produces. Files are named `<base>_<LANG>.json`.
|
||||
|
||||
**These are LLM-produced samples from a single pass.** They are not the
|
||||
multi-pass semantic translation the production pipeline is meant to do
|
||||
(see specs/27-translation-pipeline-multipass.md). They exist to evaluate
|
||||
the audio stage per language and to agree the file format early.
|
||||
Native-speaker review required.
|
||||
|
||||
## Files
|
||||
|
||||
- `deal_hunter_sample_ch1_DE.json`
|
||||
- `deal_hunter_sample_ch1_EN.json`
|
||||
- `deal_hunter_sample_ch1_ES.json`
|
||||
- `deal_hunter_sample_ch1_FR.json`
|
||||
- `deal_hunter_sample_ch1_IT.json`
|
||||
- `deal_hunter_sample_ch1_JA.json`
|
||||
- `deal_hunter_sample_ch1_KO.json`
|
||||
- `deal_hunter_sample_ch1_PT.json`
|
||||
- `deal_hunter_sample_ch1_RU.json`
|
||||
- `deal_hunter_sample_ch1_ZH.json`
|
||||
|
||||
## Source issues found while translating
|
||||
|
||||
- utt 3: 'Kianda's aunt' — 'Kianda' is a typo for 'Kainda'.
|
||||
- utt 5: 'the AI the runs the ship' — 'the' should be 'that'.
|
||||
- utt 8: 'I trade salvage' — double space.
|
||||
- The narrator's cues are telegraphic ('Princess Kainda say hello'), which a translator has to resolve by guessing. The production pipeline should decide whether to normalise these at the source.
|
||||
- Every utterance in this chapter is typed 'narration' even where a character speaks in the first person. Same class of finding as the speaker-review audit; it matters because emotion and pace are set per utterance.
|
||||
|
||||
## Decisions to confirm
|
||||
|
||||
- `Deal Hunter` and `Umbra Audio` kept in English in every language.
|
||||
Deal Hunter is both the title and the ship AI's name, so translating it
|
||||
would break the referent inside the story. Localisation decision, yours.
|
||||
- Character names transliterated by sound, not translated by meaning.
|
||||
84
samples/translations/deal_hunter_sample_ch1_DE.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_DE.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "German",
|
||||
"language_code": "de",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Willkommen zum Hörbuch „Deal Hunter“. Ich bin Ihr Erzähler und stelle Ihnen gleich mehrere der Stimmen vor, die Sie hören werden. Prinzessin Kainda, bitte grüßen Sie."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Hallo. Ich bin Prinzessin Kainda, die Hauptfigur der Geschichte. Mein Abenteuer beginnt gleich."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Danke, Kainda. Herzogin Victoria, bitte grüßen Sie."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Hallo. Ich bin Herzogin Victoria. Ich bin außerdem Kaindas Tante. Ich kann es kaum erwarten, dieses Abenteuer mit Ihnen zu teilen."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Sie werden außerdem weitere Stimmen hören. Deal Hunter, oder Hunter, bitte grüßen Sie."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Hallo. Ich bin die künstliche Intelligenz, die das Schiff steuert. Man nennt mich gewöhnlich einfach Hunter. Ich freue mich darauf, Ihnen mit dieser Geschichte eine schöne Zeit zu bereiten. Hier ist einer meiner Roboter, den Kainda auf den Namen Nanny getauft hat. Sag Hallo, Nanny."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Hallo. Kainda hat mich Nanny genannt, weil das leichter zu sagen ist als meine Roboternummer. Ich werde so hilfreich sein, wie ich kann."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Ich stelle noch eine Stimme vor. Bitte grüßen Sie."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Hallo zusammen. Ich bin Romano. Ich kaufe Schrott vom Deal Hunter und biete stets den besten Preis. Kommen Sie zur Station, wenn Sie etwas tauschen möchten."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Diese Stimmen und weitere wurden von B. D. Murphy in seinem Umbra-Audio-System erzeugt, um seinem Originalroman „Deal Hunter“ eine Stimme zu geben. Das Projekt soll die KI-gestützte Erstellung von Hörbüchern ermöglichen."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Wir alle freuen uns darauf, Ihnen mit der Audiofassung von „Deal Hunter“ eine schöne Zeit zu bereiten."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Vielen Dank fürs Zuhören."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_EN.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_EN.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "English",
|
||||
"language_code": "en",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": true,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Welcome to the audiobook Deal Hunter. I am your narrator, and I will be introducing several of the voices you are about to hear. Princess Kainda, please say hello."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Hello. I am Princess Kainda, the main character of the story. My adventure will begin shortly."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Thank you, Kainda. Duchess Victoria, please say hello."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Hello. I am Duchess Victoria. I am also Kainda's aunt. I cannot wait to share this adventure with you."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "There are other voices you will hear as well. Deal Hunter, or Hunter, please say hello."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Hello. I am the AI that runs the ship. I am usually just called Hunter. I am looking forward to entertaining you with this story. One of my robots, whom Kainda decided to name Nanny, is here. Say hello, Nanny."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Hello. Kainda named me Nanny because it is easier to use than my robot designation. I will be as helpful as I can."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "I will introduce one more voice. Please say hello."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Hello, everyone. I am Romano. I trade salvage with Deal Hunter, always giving the best price. Come by the station if you want to trade."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "These voices, and more, were created by B. D. Murphy in his Umbra Audio system to voice his original book, Deal Hunter. The project is to make AI-assisted audiobook production possible."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "We are all looking forward to entertaining you with the audio version of Deal Hunter."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Thank you for listening."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_ES.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_ES.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Spanish",
|
||||
"language_code": "es",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Bienvenidos al audiolibro Deal Hunter. Soy su narrador y voy a presentarles varias de las voces que van a oír. Princesa Kainda, por favor, saluda."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Hola. Soy la princesa Kainda, la protagonista de la historia. Mi aventura comenzará en breve."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Gracias, Kainda. Duquesa Victoria, por favor, saluda."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Hola. Soy la duquesa Victoria. También soy la tía de Kainda. No veo la hora de compartir esta aventura con ustedes."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "También oirán otras voces. Deal Hunter, o Hunter, por favor, saluda."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Hola. Soy la inteligencia artificial que controla la nave. Normalmente me llaman solo Hunter. Tengo muchas ganas de entretenerles con esta historia. Aquí está uno de mis robots, al que Kainda decidió llamar Nanny. Di hola, Nanny."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Hola. Kainda me llamó Nanny porque es más fácil de usar que mi designación de robot. Seré tan servicial como pueda."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Voy a presentar una voz más. Por favor, saluda."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Hola a todos. Soy Romano. Compro chatarra al Deal Hunter y siempre ofrezco el mejor precio. Vengan a la estación si quieren comerciar."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Estas voces, y muchas más, fueron creadas por B. D. Murphy en su sistema Umbra Audio para dar voz a su libro original, Deal Hunter. El proyecto es hacer posible la producción de audiolibros asistida por IA."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Todos estamos deseando entretenerlos con la versión de audio de Deal Hunter."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Gracias por escuchar."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_FR.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_FR.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "French",
|
||||
"language_code": "fr",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Bienvenue dans le livre audio Deal Hunter. Je suis votre narrateur, et je vais vous présenter plusieurs des voix que vous allez entendre. Princesse Kainda, dites bonjour, je vous prie."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Bonjour. Je suis la princesse Kainda, l'héroïne principale de cette histoire. Mon aventure commencera dans un instant."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Merci, Kainda. Duchesse Victoria, dites bonjour, je vous prie."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Bonjour. Je suis la duchesse Victoria. Je suis aussi la tante de Kainda. J'ai hâte de partager cette aventure avec vous."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Vous entendrez également d'autres voix. Deal Hunter, ou Hunter, dites bonjour, je vous prie."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Bonjour. Je suis l'intelligence artificielle qui dirige le vaisseau. On m'appelle d'ordinaire simplement Hunter. J'ai hâte de vous faire découvrir cette histoire. Voici l'un de mes robots, que Kainda a décidé de surnommer Nanny. Dis bonjour, Nanny."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Bonjour. Kainda m'a appelée Nanny parce que c'est plus facile à prononcer que ma désignation de robot. Je ferai de mon mieux pour vous être utile."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Je vais vous présenter une dernière voix. Dites bonjour, je vous prie."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Bonjour à tous. Je suis Romano. Je fais commerce de ferraille avec le Deal Hunter, et j'offre toujours le meilleur prix. Venez à la station si vous souhaitez échanger."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Ces voix, et bien d'autres, ont été créées par B. D. Murphy au sein de son système Umbra Audio pour donner voix à son livre original, Deal Hunter. Le projet est de rendre possible la production de livres audio assistée par IA."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Nous avons tous hâte de vous offrir la version audio de Deal Hunter."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Merci d'avoir écouté."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_IT.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_IT.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Italian",
|
||||
"language_code": "it",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Benvenuti all'audiolibro Deal Hunter. Sono il vostro narratore e vi presenterò alcune delle voci che potrete sentire. Principessa Kainda, per favore, saluta."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Ciao. Sono la principessa Kainda, la protagonista della storia. La mia avventura inizierà tra poco."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Grazie, Kainda. Duchessa Victoria, per favore, saluta."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Ciao. Sono la duchessa Victoria. Sono anche la zia di Kainda. Non vedo l'ora di condividere questa avventura con voi."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Sentirete anche altre voci. Deal Hunter, o Hunter, per favore, saluta."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Ciao. Sono l'intelligenza artificiale che governa la nave. Di solito mi chiamano solo Hunter. Non vedo l'ora di intrattenervi con questa storia. Ecco uno dei miei robot, che Kainda ha deciso di chiamare Nanny. Saluta, Nanny."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Ciao. Kainda mi ha chiamata Nanny perché è più semplice da usare della mia designazione di robot. Farò il mio meglio per essere utile."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Presenterò un'altra voce. Per favore, saluta."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Ciao a tutti. Sono Romano. Compro rottami dal Deal Hunter e offro sempre il prezzo migliore. Venite alla stazione se volete scambiare."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Queste voci, e molte altre, sono state create da B. D. Murphy nel suo sistema Umbra Audio per dare voce al suo libro originale, Deal Hunter. Il progetto è rendere possibile la produzione di audiolibri assistita dall'IA."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Non vediamo l'ora di intrattenervi con la versione audio di Deal Hunter."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Grazie per aver ascoltato."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_JA.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_JA.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Japanese",
|
||||
"language_code": "ja",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "オードブック『Deal Hunter』へようこそ。ナレーターの私です。これから将要聞かれるお声をいくつかご紹介しましょう。プリンセス・カインダ、あいさつをお願いします。"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "こんにちは。物語の主人公、プリンセス・カインダです。冒険はまもなく始まります。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "ありがとうございます、カインダ。ヴィクトリア公妃、あいさつをお願いします。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "こんにちは。ヴィクトリア公妃です。カインダのおばでもあります。この冒険をあなたと分かち合えるのを楽しみにしています。"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "このほかにも、まだ聞いていない声があります。Deal Hunter、ハンター、あいさつをお願いします。"
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "こんにちは。この船を操っている人工知能です。通常は「ハンター」と呼ばれています。この物語でお楽しみいただくことを楽しみにしています。カインダが「ナニー」と名づけた、私のロボットの一人がいます。ナニー、あいさつをお願いします。"
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "こんにちは。カインダは、私のロボットの型番より呼びやすい「ナニー」という名前を付けてくれました。できるだけお手伝いします。"
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "もう一人の声をご紹介します。あいさつをお願いします。"
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "こんにちは、みなさん。ロマーノです。Deal Hunter と廃品回収の取引をしており、いつも最高の値で買い取ります。取引したいときはステーションにお越しください。"
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "これらの声、およびそれ以外の多くの声は、B. D. マーフィーが彼の Umbra Audio システムによって制作され、オリジナルの小説『Deal Hunter』の朗読のために使われています。目標は、人工知能を活用した有声書の制作です。"
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "私たち皆、『Deal Hunter』のオーディオ版でお会いできることを楽しみにしています。"
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "お聞きいただき、ありがとうございました。"
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_KO.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_KO.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Korean",
|
||||
"language_code": "ko",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "오디오북 『Deal Hunter』에 오신 것을 환영합니다. 내레이션은 제가 맡고 있습니다. 지금부터 여러분이 듣게 될 몇 가지 목소리를 소개하겠습니다. 케이인다 공주님, 인사해 주세요."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "안녕하세요. 저는 이야기의 주인공 케이인다 공주입니다. 제 모험은 곧 시작됩니다."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "고맙습니다, 케이인다. 빅토리아 공작부님, 인사해 주세요."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "안녕하세요. 저는 빅토리아 공작부입니다. 저는 케이인다의 이모이기도 합니다. 이 모험을 여러분과 함께하게 되어 매우 기뻐요."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "여러분이 추가로 들으실 목소리들도 있습니다. 딜 헌터, 혹은 헌터, 인사해 주세요."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "안녕하세요. 저는 이 함선을 운항하는 인공지능입니다. 보통은 그냥 헌터라고 부릅니다. 이 이야기로 여러분을 즐겁게 해 드리는 것이 기다려집니다. 케이인다가 나니라고 이름 붙여 준 제 로봇 중 한 대가 있습니다. 인사해 보세요, 나니."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "안녕하세요. 케이인다는 제 로봇 번호보다 쓰기 편하다는 이유로 저를 나니라고 불렀습니다. 최대한 도움이 되도록 하겠습니다."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "한 분 더 소개하겠습니다. 인사해 주세요."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "여러분 안녕하세요. 저는 로마노입니다. 딜 헌터와 폐기물 거래를 하며 항상 가장 좋은 가격에 사겠습니다. 거래를 원하시면 항대로 오세요."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "이 목소기들과 그 밖의 목소리들은 B. D. 머피가 자신의 Umbra Audio 시스템에서 만들어 자신의 원작 『Deal Hunter』에 음성을 입힌 것입니다. 이 프로젝트의 목표는 인공지능을 활용한 오디오북 제작을 가능하게 하는 것입니다."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "우리는 모두 『Deal Hunter』의 오디오 버전으로 여러분을 즐겁게 해 드리기를 기다리고 있습니다."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "들어주셔서 감사합니다."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_PT.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_PT.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Portuguese",
|
||||
"language_code": "pt",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Bem-vindos ao audiolivro Deal Hunter. Sou o seu narrador e vou apresentar algumas das vozes que vocês vão ouvir. Princesa Kainda, por favor, dê um olhar."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Olá. Sou a princesa Kainda, a personagem principal da história. Minha aventura começará em breve."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Obrigado, Kainda. Duquesa Victoria, por favor, dê um olhar."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Olá. Sou a duquesa Victoria. Sou também tia de Kainda. Mal posso esperar para compartilhar esta aventura com vocês."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Vocês também ouvirão outras vozes. Deal Hunter, ou Hunter, por favor, dê um olhar."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Olá. Sou a inteligência artificial que controla a nave. Normalmente me chamam apenas de Hunter. Estou ansioso para entretê-los com esta história. Aqui está um dos meus robôs, que Kainda decidiu chamar de Nanny. Diga olá, Nanny."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Olá. Kainda me chamou de Nanny porque é mais fácil de usar do que minha designação de robô. Serei tão prestativa quanto puder."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Vou apresentar mais uma voz. Por favor, dê um olhar."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Olá a todos. Sou Romano. Compro sucata do Deal Hunter e sempre ofereço o melhor preço. Passem pela estação se quiserem negociar."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Estas vozes, e muitas outras, foram criadas por B. D. Murphy em seu sistema Umbra Audio para dar voz ao seu livro original, Deal Hunter. O projeto é tornar possível a produção de audiolivros assistida por IA."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Todos estamos ansiosos para entretê-los com a versão em áudio de Deal Hunter."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Obrigado por ouvir."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_RU.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_RU.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Russian",
|
||||
"language_code": "ru",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Добро пожаловать в аудиокнигу «Deal Hunter». Я ваш рассказчик, и я представлю несколько из голосов, которые вы услышите. Принцесса Кайнда, поздоровайте, пожалуйста."
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "Здравствуйте. Я принцесса Кайнда, главный герой этой истории. Моё приключение начнётся совсем скоро."
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Спасибо, Кайнда. Герцогиня Виктория, поздоровайте, пожалуйста."
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "Здравствуйте. Я герцогиня Виктория. Я также тётя Кайнды. Мне не терпится разделить это приключение с вами."
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Вы также услышите и другие голоса. Deal Hunter, или просто Хантер, поздоровайте, пожалуйста."
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Здравствуйте. Я искусственный интеллект, управляющий кораблём. Обычно меня называют просто Хантер. Я с нетерпением жду возможности рассказать вам эту историю. Вот один из моих роботов, которому Кайнда дала имя Нэнси. Поздоровайте, Нэнси."
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "Здравствуйте. Кайнда назвала меня Нэнси, потому что это произносится легче, чем моё обозначение робота. Я постараюсь быть максимально полезной."
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Я представлю ещё один голос. Поздоровайте, пожалуйста."
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "Здравствуйте, все. Я Романо. Я скупаю лом у Deal Hunter и всегда даю лучшую цену. Заходите на станцию, если захотите торговать."
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Эти голоса и многие другие были созданы Б. Д. Мёрфи в его системе Umbra Audio, чтобы дать голос его оригинальной книге «Deal Hunter». Цель проекта — сделать возможным создание аудиокниг с помощью искусственного интеллекта."
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "Мы все с нетерпением ждём возможности порадовать вас аудиоверсией «Deal Hunter»."
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "Спасибо за внимание."
|
||||
}
|
||||
]
|
||||
}
|
||||
84
samples/translations/deal_hunter_sample_ch1_ZH.json
Normal file
84
samples/translations/deal_hunter_sample_ch1_ZH.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": "Chinese",
|
||||
"language_code": "zh",
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": false,
|
||||
"provenance": "LLM-produced sample, single pass, NOT the multi-pass pipeline. Requires native-speaker review.",
|
||||
"utterances": [
|
||||
{
|
||||
"index": 0,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "欢迎收听有声书《Deal Hunter》。我是您的旁白,接下来为您介绍几段您即将听到的声音。凯因达公主,请打个招呼。"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"speaker": "Kainda",
|
||||
"type": "narration",
|
||||
"text": "大家好。我是凯因达公主,本故事的主人公。我的冒险很快就要开始了。"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "谢谢您,凯因达。维多利亚女爵,请打个招呼。"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"speaker": "Duchess Victoria",
|
||||
"type": "narration",
|
||||
"text": "大家好。我是维多利亚女爵,也是凯因达的姑母。我非常期待与您分享这段冒险。"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "您还会听到其他几位声音。Deal Hunter,也就是「Hunter」,请打个招呼。"
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "大家好。我是操控这艘飞船的人工智能。平常大家都叫我「Hunter」。我非常期待用这个故事为大家带来乐趣。我的一位机器人同伴——凯因达给她起名叫「南妮」。南妮,请打个招呼。"
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"speaker": "Nanny",
|
||||
"type": "narration",
|
||||
"text": "大家好。凯因达叫我「南妮」,因为这比我的机器人编号容易叫。我会尽力帮上忙。"
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "我再介绍一位声音。请打个招呼。"
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"speaker": "Trader Romano",
|
||||
"type": "narration",
|
||||
"text": "大家好,我是罗曼诺。我与 Deal Hunter 做回收物交易,收购价格一向最优。欢迎到空间站来找我交易。"
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "这些声音以及更多声音,都是由 B·D·墨菲在他的 Umbra Audio 系统中创作出来的,用以为他的原创小说《Deal Hunter》配音。这个项目的目标是让人工智能辅助的有声书制作成为可能。"
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"speaker": "Narrator",
|
||||
"type": "narration",
|
||||
"text": "我们大家都期待用《Deal Hunter》的有声版为各位带来享受。"
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"speaker": "Deal Hunter",
|
||||
"type": "narration",
|
||||
"text": "感谢收听。"
|
||||
}
|
||||
]
|
||||
}
|
||||
331
scripts/build_sample_translations.py
Normal file
331
scripts/build_sample_translations.py
Normal file
@@ -0,0 +1,331 @@
|
||||
"""Generate the Deal Hunter sample chapter in every language Qwen3-TTS produces.
|
||||
|
||||
IMPORTANT - what this is and is not
|
||||
-----------------------------------
|
||||
These translations were produced by an LLM in a single pass, by hand, as a
|
||||
SAMPLE. They are not the output of the multi-pass semantic translation pipeline
|
||||
described in specs/27, which does not exist yet. They exist so that the audio
|
||||
stage can be evaluated per language and so the file format can be agreed
|
||||
before the pipeline is built.
|
||||
|
||||
They need native-speaker review before any of them is used for anything real.
|
||||
|
||||
Decisions taken, and flagged because the pipeline must decide them properly:
|
||||
|
||||
* "Deal Hunter" and "Umbra Audio" are kept in English everywhere. Deal Hunter
|
||||
is both the book title and the name of the ship AI, so translating it would
|
||||
break the referent inside the story. This is a localisation decision, not a
|
||||
translation one, and it is the operator's to confirm.
|
||||
* Character names are transliterated by sound (Kainda -> 凯因达 / カインダ),
|
||||
not translated by meaning. A name is an identity, not a word.
|
||||
|
||||
``validate()`` runs before anything is written. An earlier draft of this file
|
||||
shipped stray English fragments and a duplicated Japanese entry; the checks
|
||||
below exist so that class of corruption cannot reach a file silently.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "translations"
|
||||
|
||||
# The 10 languages Qwen3-TTS produces.
|
||||
LANGUAGES = {
|
||||
"EN": ("English", "en"),
|
||||
"ZH": ("Chinese", "zh"),
|
||||
"JA": ("Japanese", "ja"),
|
||||
"KO": ("Korean", "ko"),
|
||||
"DE": ("German", "de"),
|
||||
"FR": ("French", "fr"),
|
||||
"RU": ("Russian", "ru"),
|
||||
"PT": ("Portuguese", "pt"),
|
||||
"ES": ("Spanish", "es"),
|
||||
"IT": ("Italian", "it"),
|
||||
}
|
||||
|
||||
# (speaker, type) per utterance, matching the source chapter.
|
||||
SHAPE = [
|
||||
("Narrator", "narration"),
|
||||
("Kainda", "narration"),
|
||||
("Narrator", "narration"),
|
||||
("Duchess Victoria", "narration"),
|
||||
("Narrator", "narration"),
|
||||
("Deal Hunter", "narration"),
|
||||
("Nanny", "narration"),
|
||||
("Narrator", "narration"),
|
||||
("Trader Romano", "narration"),
|
||||
("Narrator", "narration"),
|
||||
("Narrator", "narration"),
|
||||
("Deal Hunter", "narration"),
|
||||
]
|
||||
|
||||
TRANSLATIONS: dict[str, list[str]] = {
|
||||
"EN": [
|
||||
"Welcome to the audiobook Deal Hunter. I am your narrator, and I will be introducing several of the voices you are about to hear. Princess Kainda, please say hello.",
|
||||
"Hello. I am Princess Kainda, the main character of the story. My adventure will begin shortly.",
|
||||
"Thank you, Kainda. Duchess Victoria, please say hello.",
|
||||
"Hello. I am Duchess Victoria. I am also Kainda's aunt. I cannot wait to share this adventure with you.",
|
||||
"There are other voices you will hear as well. Deal Hunter, or Hunter, please say hello.",
|
||||
"Hello. I am the AI that runs the ship. I am usually just called Hunter. I am looking forward to entertaining you with this story. One of my robots, whom Kainda decided to name Nanny, is here. Say hello, Nanny.",
|
||||
"Hello. Kainda named me Nanny because it is easier to use than my robot designation. I will be as helpful as I can.",
|
||||
"I will introduce one more voice. Please say hello.",
|
||||
"Hello, everyone. I am Romano. I trade salvage with Deal Hunter, always giving the best price. Come by the station if you want to trade.",
|
||||
"These voices, and more, were created by B. D. Murphy in his Umbra Audio system to voice his original book, Deal Hunter. The project is to make AI-assisted audiobook production possible.",
|
||||
"We are all looking forward to entertaining you with the audio version of Deal Hunter.",
|
||||
"Thank you for listening.",
|
||||
],
|
||||
"FR": [
|
||||
"Bienvenue dans le livre audio Deal Hunter. Je suis votre narrateur, et je vais vous présenter plusieurs des voix que vous allez entendre. Princesse Kainda, dites bonjour, je vous prie.",
|
||||
"Bonjour. Je suis la princesse Kainda, l'héroïne principale de cette histoire. Mon aventure commencera dans un instant.",
|
||||
"Merci, Kainda. Duchesse Victoria, dites bonjour, je vous prie.",
|
||||
"Bonjour. Je suis la duchesse Victoria. Je suis aussi la tante de Kainda. J'ai hâte de partager cette aventure avec vous.",
|
||||
"Vous entendrez également d'autres voix. Deal Hunter, ou Hunter, dites bonjour, je vous prie.",
|
||||
"Bonjour. Je suis l'intelligence artificielle qui dirige le vaisseau. On m'appelle d'ordinaire simplement Hunter. J'ai hâte de vous faire découvrir cette histoire. Voici l'un de mes robots, que Kainda a décidé de surnommer Nanny. Dis bonjour, Nanny.",
|
||||
"Bonjour. Kainda m'a appelée Nanny parce que c'est plus facile à prononcer que ma désignation de robot. Je ferai de mon mieux pour vous être utile.",
|
||||
"Je vais vous présenter une dernière voix. Dites bonjour, je vous prie.",
|
||||
"Bonjour à tous. Je suis Romano. Je fais commerce de ferraille avec le Deal Hunter, et j'offre toujours le meilleur prix. Venez à la station si vous souhaitez échanger.",
|
||||
"Ces voix, et bien d'autres, ont été créées par B. D. Murphy au sein de son système Umbra Audio pour donner voix à son livre original, Deal Hunter. Le projet est de rendre possible la production de livres audio assistée par IA.",
|
||||
"Nous avons tous hâte de vous offrir la version audio de Deal Hunter.",
|
||||
"Merci d'avoir écouté.",
|
||||
],
|
||||
"ES": [
|
||||
"Bienvenidos al audiolibro Deal Hunter. Soy su narrador y voy a presentarles varias de las voces que van a oír. Princesa Kainda, por favor, saluda.",
|
||||
"Hola. Soy la princesa Kainda, la protagonista de la historia. Mi aventura comenzará en breve.",
|
||||
"Gracias, Kainda. Duquesa Victoria, por favor, saluda.",
|
||||
"Hola. Soy la duquesa Victoria. También soy la tía de Kainda. No veo la hora de compartir esta aventura con ustedes.",
|
||||
"También oirán otras voces. Deal Hunter, o Hunter, por favor, saluda.",
|
||||
"Hola. Soy la inteligencia artificial que controla la nave. Normalmente me llaman solo Hunter. Tengo muchas ganas de entretenerles con esta historia. Aquí está uno de mis robots, al que Kainda decidió llamar Nanny. Di hola, Nanny.",
|
||||
"Hola. Kainda me llamó Nanny porque es más fácil de usar que mi designación de robot. Seré tan servicial como pueda.",
|
||||
"Voy a presentar una voz más. Por favor, saluda.",
|
||||
"Hola a todos. Soy Romano. Compro chatarra al Deal Hunter y siempre ofrezco el mejor precio. Vengan a la estación si quieren comerciar.",
|
||||
"Estas voces, y muchas más, fueron creadas por B. D. Murphy en su sistema Umbra Audio para dar voz a su libro original, Deal Hunter. El proyecto es hacer posible la producción de audiolibros asistida por IA.",
|
||||
"Todos estamos deseando entretenerlos con la versión de audio de Deal Hunter.",
|
||||
"Gracias por escuchar.",
|
||||
],
|
||||
"PT": [
|
||||
"Bem-vindos ao audiolivro Deal Hunter. Sou o seu narrador e vou apresentar algumas das vozes que vocês vão ouvir. Princesa Kainda, por favor, dê um olhar.",
|
||||
"Olá. Sou a princesa Kainda, a personagem principal da história. Minha aventura começará em breve.",
|
||||
"Obrigado, Kainda. Duquesa Victoria, por favor, dê um olhar.",
|
||||
"Olá. Sou a duquesa Victoria. Sou também tia de Kainda. Mal posso esperar para compartilhar esta aventura com vocês.",
|
||||
"Vocês também ouvirão outras vozes. Deal Hunter, ou Hunter, por favor, dê um olhar.",
|
||||
"Olá. Sou a inteligência artificial que controla a nave. Normalmente me chamam apenas de Hunter. Estou ansioso para entretê-los com esta história. Aqui está um dos meus robôs, que Kainda decidiu chamar de Nanny. Diga olá, Nanny.",
|
||||
"Olá. Kainda me chamou de Nanny porque é mais fácil de usar do que minha designação de robô. Serei tão prestativa quanto puder.",
|
||||
"Vou apresentar mais uma voz. Por favor, dê um olhar.",
|
||||
"Olá a todos. Sou Romano. Compro sucata do Deal Hunter e sempre ofereço o melhor preço. Passem pela estação se quiserem negociar.",
|
||||
"Estas vozes, e muitas outras, foram criadas por B. D. Murphy em seu sistema Umbra Audio para dar voz ao seu livro original, Deal Hunter. O projeto é tornar possível a produção de audiolivros assistida por IA.",
|
||||
"Todos estamos ansiosos para entretê-los com a versão em áudio de Deal Hunter.",
|
||||
"Obrigado por ouvir.",
|
||||
],
|
||||
"IT": [
|
||||
"Benvenuti all'audiolibro Deal Hunter. Sono il vostro narratore e vi presenterò alcune delle voci che potrete sentire. Principessa Kainda, per favore, saluta.",
|
||||
"Ciao. Sono la principessa Kainda, la protagonista della storia. La mia avventura inizierà tra poco.",
|
||||
"Grazie, Kainda. Duchessa Victoria, per favore, saluta.",
|
||||
"Ciao. Sono la duchessa Victoria. Sono anche la zia di Kainda. Non vedo l'ora di condividere questa avventura con voi.",
|
||||
"Sentirete anche altre voci. Deal Hunter, o Hunter, per favore, saluta.",
|
||||
"Ciao. Sono l'intelligenza artificiale che governa la nave. Di solito mi chiamano solo Hunter. Non vedo l'ora di intrattenervi con questa storia. Ecco uno dei miei robot, che Kainda ha deciso di chiamare Nanny. Saluta, Nanny.",
|
||||
"Ciao. Kainda mi ha chiamata Nanny perché è più semplice da usare della mia designazione di robot. Farò il mio meglio per essere utile.",
|
||||
"Presenterò un'altra voce. Per favore, saluta.",
|
||||
"Ciao a tutti. Sono Romano. Compro rottami dal Deal Hunter e offro sempre il prezzo migliore. Venite alla stazione se volete scambiare.",
|
||||
"Queste voci, e molte altre, sono state create da B. D. Murphy nel suo sistema Umbra Audio per dare voce al suo libro originale, Deal Hunter. Il progetto è rendere possibile la produzione di audiolibri assistita dall'IA.",
|
||||
"Non vediamo l'ora di intrattenervi con la versione audio di Deal Hunter.",
|
||||
"Grazie per aver ascoltato.",
|
||||
],
|
||||
"DE": [
|
||||
"Willkommen zum Hörbuch „Deal Hunter“. Ich bin Ihr Erzähler und stelle Ihnen gleich mehrere der Stimmen vor, die Sie hören werden. Prinzessin Kainda, bitte grüßen Sie.",
|
||||
"Hallo. Ich bin Prinzessin Kainda, die Hauptfigur der Geschichte. Mein Abenteuer beginnt gleich.",
|
||||
"Danke, Kainda. Herzogin Victoria, bitte grüßen Sie.",
|
||||
"Hallo. Ich bin Herzogin Victoria. Ich bin außerdem Kaindas Tante. Ich kann es kaum erwarten, dieses Abenteuer mit Ihnen zu teilen.",
|
||||
"Sie werden außerdem weitere Stimmen hören. Deal Hunter, oder Hunter, bitte grüßen Sie.",
|
||||
"Hallo. Ich bin die künstliche Intelligenz, die das Schiff steuert. Man nennt mich gewöhnlich einfach Hunter. Ich freue mich darauf, Ihnen mit dieser Geschichte eine schöne Zeit zu bereiten. Hier ist einer meiner Roboter, den Kainda auf den Namen Nanny getauft hat. Sag Hallo, Nanny.",
|
||||
"Hallo. Kainda hat mich Nanny genannt, weil das leichter zu sagen ist als meine Roboternummer. Ich werde so hilfreich sein, wie ich kann.",
|
||||
"Ich stelle noch eine Stimme vor. Bitte grüßen Sie.",
|
||||
"Hallo zusammen. Ich bin Romano. Ich kaufe Schrott vom Deal Hunter und biete stets den besten Preis. Kommen Sie zur Station, wenn Sie etwas tauschen möchten.",
|
||||
"Diese Stimmen und weitere wurden von B. D. Murphy in seinem Umbra-Audio-System erzeugt, um seinem Originalroman „Deal Hunter“ eine Stimme zu geben. Das Projekt soll die KI-gestützte Erstellung von Hörbüchern ermöglichen.",
|
||||
"Wir alle freuen uns darauf, Ihnen mit der Audiofassung von „Deal Hunter“ eine schöne Zeit zu bereiten.",
|
||||
"Vielen Dank fürs Zuhören.",
|
||||
],
|
||||
"RU": [
|
||||
"Добро пожаловать в аудиокнигу «Deal Hunter». Я ваш рассказчик, и я представлю несколько из голосов, которые вы услышите. Принцесса Кайнда, поздоровайте, пожалуйста.",
|
||||
"Здравствуйте. Я принцесса Кайнда, главный герой этой истории. Моё приключение начнётся совсем скоро.",
|
||||
"Спасибо, Кайнда. Герцогиня Виктория, поздоровайте, пожалуйста.",
|
||||
"Здравствуйте. Я герцогиня Виктория. Я также тётя Кайнды. Мне не терпится разделить это приключение с вами.",
|
||||
"Вы также услышите и другие голоса. Deal Hunter, или просто Хантер, поздоровайте, пожалуйста.",
|
||||
"Здравствуйте. Я искусственный интеллект, управляющий кораблём. Обычно меня называют просто Хантер. Я с нетерпением жду возможности рассказать вам эту историю. Вот один из моих роботов, которому Кайнда дала имя Нэнси. Поздоровайте, Нэнси.",
|
||||
"Здравствуйте. Кайнда назвала меня Нэнси, потому что это произносится легче, чем моё обозначение робота. Я постараюсь быть максимально полезной.",
|
||||
"Я представлю ещё один голос. Поздоровайте, пожалуйста.",
|
||||
"Здравствуйте, все. Я Романо. Я скупаю лом у Deal Hunter и всегда даю лучшую цену. Заходите на станцию, если захотите торговать.",
|
||||
"Эти голоса и многие другие были созданы Б. Д. Мёрфи в его системе Umbra Audio, чтобы дать голос его оригинальной книге «Deal Hunter». Цель проекта — сделать возможным создание аудиокниг с помощью искусственного интеллекта.",
|
||||
"Мы все с нетерпением ждём возможности порадовать вас аудиоверсией «Deal Hunter».",
|
||||
"Спасибо за внимание.",
|
||||
],
|
||||
"ZH": [
|
||||
"欢迎收听有声书《Deal Hunter》。我是您的旁白,接下来为您介绍几段您即将听到的声音。凯因达公主,请打个招呼。",
|
||||
"大家好。我是凯因达公主,本故事的主人公。我的冒险很快就要开始了。",
|
||||
"谢谢您,凯因达。维多利亚女爵,请打个招呼。",
|
||||
"大家好。我是维多利亚女爵,也是凯因达的姑母。我非常期待与您分享这段冒险。",
|
||||
"您还会听到其他几位声音。Deal Hunter,也就是「Hunter」,请打个招呼。",
|
||||
"大家好。我是操控这艘飞船的人工智能。平常大家都叫我「Hunter」。我非常期待用这个故事为大家带来乐趣。我的一位机器人同伴——凯因达给她起名叫「南妮」。南妮,请打个招呼。",
|
||||
"大家好。凯因达叫我「南妮」,因为这比我的机器人编号容易叫。我会尽力帮上忙。",
|
||||
"我再介绍一位声音。请打个招呼。",
|
||||
"大家好,我是罗曼诺。我与 Deal Hunter 做回收物交易,收购价格一向最优。欢迎到空间站来找我交易。",
|
||||
"这些声音以及更多声音,都是由 B·D·墨菲在他的 Umbra Audio 系统中创作出来的,用以为他的原创小说《Deal Hunter》配音。这个项目的目标是让人工智能辅助的有声书制作成为可能。",
|
||||
"我们大家都期待用《Deal Hunter》的有声版为各位带来享受。",
|
||||
"感谢收听。",
|
||||
],
|
||||
"JA": [
|
||||
"オードブック『Deal Hunter』へようこそ。ナレーターの私です。これから将要聞かれるお声をいくつかご紹介しましょう。プリンセス・カインダ、あいさつをお願いします。",
|
||||
"こんにちは。物語の主人公、プリンセス・カインダです。冒険はまもなく始まります。",
|
||||
"ありがとうございます、カインダ。ヴィクトリア公妃、あいさつをお願いします。",
|
||||
"こんにちは。ヴィクトリア公妃です。カインダのおばでもあります。この冒険をあなたと分かち合えるのを楽しみにしています。",
|
||||
"このほかにも、まだ聞いていない声があります。Deal Hunter、ハンター、あいさつをお願いします。",
|
||||
"こんにちは。この船を操っている人工知能です。通常は「ハンター」と呼ばれています。この物語でお楽しみいただくことを楽しみにしています。カインダが「ナニー」と名づけた、私のロボットの一人がいます。ナニー、あいさつをお願いします。",
|
||||
"こんにちは。カインダは、私のロボットの型番より呼びやすい「ナニー」という名前を付けてくれました。できるだけお手伝いします。",
|
||||
"もう一人の声をご紹介します。あいさつをお願いします。",
|
||||
"こんにちは、みなさん。ロマーノです。Deal Hunter と廃品回収の取引をしており、いつも最高の値で買い取ります。取引したいときはステーションにお越しください。",
|
||||
"これらの声、およびそれ以外の多くの声は、B. D. マーフィーが彼の Umbra Audio システムによって制作され、オリジナルの小説『Deal Hunter』の朗読のために使われています。目標は、人工知能を活用した有声書の制作です。",
|
||||
"私たち皆、『Deal Hunter』のオーディオ版でお会いできることを楽しみにしています。",
|
||||
"お聞きいただき、ありがとうございました。",
|
||||
],
|
||||
"KO": [
|
||||
"오디오북 『Deal Hunter』에 오신 것을 환영합니다. 내레이션은 제가 맡고 있습니다. 지금부터 여러분이 듣게 될 몇 가지 목소리를 소개하겠습니다. 케이인다 공주님, 인사해 주세요.",
|
||||
"안녕하세요. 저는 이야기의 주인공 케이인다 공주입니다. 제 모험은 곧 시작됩니다.",
|
||||
"고맙습니다, 케이인다. 빅토리아 공작부님, 인사해 주세요.",
|
||||
"안녕하세요. 저는 빅토리아 공작부입니다. 저는 케이인다의 이모이기도 합니다. 이 모험을 여러분과 함께하게 되어 매우 기뻐요.",
|
||||
"여러분이 추가로 들으실 목소리들도 있습니다. 딜 헌터, 혹은 헌터, 인사해 주세요.",
|
||||
"안녕하세요. 저는 이 함선을 운항하는 인공지능입니다. 보통은 그냥 헌터라고 부릅니다. 이 이야기로 여러분을 즐겁게 해 드리는 것이 기다려집니다. 케이인다가 나니라고 이름 붙여 준 제 로봇 중 한 대가 있습니다. 인사해 보세요, 나니.",
|
||||
"안녕하세요. 케이인다는 제 로봇 번호보다 쓰기 편하다는 이유로 저를 나니라고 불렀습니다. 최대한 도움이 되도록 하겠습니다.",
|
||||
"한 분 더 소개하겠습니다. 인사해 주세요.",
|
||||
"여러분 안녕하세요. 저는 로마노입니다. 딜 헌터와 폐기물 거래를 하며 항상 가장 좋은 가격에 사겠습니다. 거래를 원하시면 항대로 오세요.",
|
||||
"이 목소기들과 그 밖의 목소리들은 B. D. 머피가 자신의 Umbra Audio 시스템에서 만들어 자신의 원작 『Deal Hunter』에 음성을 입힌 것입니다. 이 프로젝트의 목표는 인공지능을 활용한 오디오북 제작을 가능하게 하는 것입니다.",
|
||||
"우리는 모두 『Deal Hunter』의 오디오 버전으로 여러분을 즐겁게 해 드리기를 기다리고 있습니다.",
|
||||
"들어주셔서 감사합니다.",
|
||||
],
|
||||
}
|
||||
|
||||
# Only meaningful for targets that are not written in Latin script. Flagging
|
||||
# Latin residue in French or German would flag every word.
|
||||
NON_LATIN = {"ZH", "JA", "KO", "RU"}
|
||||
|
||||
# Latin text that is legitimately expected to survive translation.
|
||||
ALLOWED_LATIN = re.compile(
|
||||
r"Deal Hunter|Umbra Audio|B\. ?D\.?|Nanny|Hunter|Kainda|Victoria|Romano",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
LATIN_RUN = re.compile(r"[A-Za-z]{2,}")
|
||||
|
||||
SOURCE_ISSUES = [
|
||||
"utt 3: 'Kianda's aunt' — 'Kianda' is a typo for 'Kainda'.",
|
||||
"utt 5: 'the AI the runs the ship' — 'the' should be 'that'.",
|
||||
"utt 8: 'I trade salvage' — double space.",
|
||||
"The narrator's cues are telegraphic ('Princess Kainda say hello'), which a "
|
||||
"translator has to resolve by guessing. The production pipeline should "
|
||||
"decide whether to normalise these at the source.",
|
||||
"Every utterance in this chapter is typed 'narration' even where a character "
|
||||
"speaks in the first person. Same class of finding as the speaker-review "
|
||||
"audit; it matters because emotion and pace are set per utterance.",
|
||||
]
|
||||
|
||||
|
||||
def validate() -> list[str]:
|
||||
"""Catch corruption before it reaches a file."""
|
||||
problems: list[str] = []
|
||||
for suffix, texts in TRANSLATIONS.items():
|
||||
if len(texts) != len(SHAPE):
|
||||
problems.append(f"{suffix}: {len(texts)} texts, expected {len(SHAPE)}")
|
||||
continue
|
||||
for i, t in enumerate(texts):
|
||||
if not t.strip():
|
||||
problems.append(f"{suffix}[{i}]: empty")
|
||||
# CJK is dense: a complete sentence can be 5 characters. Only the
|
||||
# Latin-script languages get a length floor.
|
||||
if suffix not in {"ZH", "JA", "KO"} and len(t) < 15:
|
||||
problems.append(f"{suffix}[{i}]: suspiciously short: {t!r}")
|
||||
# Stray English left behind by a bad edit.
|
||||
if suffix in NON_LATIN:
|
||||
residue = [
|
||||
m.group(0)
|
||||
for m in LATIN_RUN.finditer(ALLOWED_LATIN.sub("", t))
|
||||
]
|
||||
else:
|
||||
residue = []
|
||||
if residue:
|
||||
problems.append(
|
||||
f"{suffix}[{i}]: unexpected Latin text {residue} in {t[:60]!r}"
|
||||
)
|
||||
# A duplicated line means two variants ended up in one list entry.
|
||||
if len(set(texts)) != len(texts):
|
||||
dupes = {t for t in texts if texts.count(t) > 1}
|
||||
problems.append(f"{suffix}: duplicated utterance text {dupes}")
|
||||
# Every language must cover every utterance.
|
||||
missing = set(LANGUAGES) - set(TRANSLATIONS)
|
||||
if missing:
|
||||
problems.append(f"no translation for: {sorted(missing)}")
|
||||
return problems
|
||||
|
||||
|
||||
def main() -> int:
|
||||
problems = validate()
|
||||
if problems:
|
||||
print(" VALIDATION FAILED - nothing written:")
|
||||
for p in problems:
|
||||
print(f" - {p}")
|
||||
return 1
|
||||
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
written = []
|
||||
for suffix, (name, code) in LANGUAGES.items():
|
||||
texts = TRANSLATIONS[suffix]
|
||||
rows = [
|
||||
{"index": i, "speaker": SHAPE[i][0], "type": SHAPE[i][1], "text": texts[i]}
|
||||
for i in range(len(SHAPE))
|
||||
]
|
||||
doc = {
|
||||
"project": "DealHunterShip_4.0e_whole-to-audio",
|
||||
"chapter_id": 1,
|
||||
"chapter_title": "Sample / voice introduction",
|
||||
"language_name": name,
|
||||
"language_code": code,
|
||||
"tts_engine": "Qwen3-TTS",
|
||||
"is_source_language": suffix == "EN",
|
||||
"provenance": (
|
||||
"LLM-produced sample, single pass, NOT the multi-pass pipeline. "
|
||||
"Requires native-speaker review."
|
||||
),
|
||||
"utterances": rows,
|
||||
}
|
||||
path = OUT / f"deal_hunter_sample_ch1_{suffix}.json"
|
||||
path.write_text(json.dumps(doc, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
written.append(path.name)
|
||||
|
||||
(OUT / "README.md").write_text(
|
||||
"# Deal Hunter sample chapter - translated\n\n"
|
||||
"Sample chapter 1 (the voice-introduction sample) in all 10 languages\n"
|
||||
"Qwen3-TTS produces. Files are named `<base>_<LANG>.json`.\n\n"
|
||||
"**These are LLM-produced samples from a single pass.** They are not the\n"
|
||||
"multi-pass semantic translation the production pipeline is meant to do\n"
|
||||
"(see specs/27-translation-pipeline-multipass.md). They exist to evaluate\n"
|
||||
"the audio stage per language and to agree the file format early.\n"
|
||||
"Native-speaker review required.\n\n"
|
||||
"## Files\n\n"
|
||||
+ "".join(f"- `{n}`\n" for n in sorted(written))
|
||||
+ "\n## Source issues found while translating\n\n"
|
||||
+ "".join(f"- {s}\n" for s in SOURCE_ISSUES)
|
||||
+ "\n## Decisions to confirm\n\n"
|
||||
"- `Deal Hunter` and `Umbra Audio` kept in English in every language.\n"
|
||||
" Deal Hunter is both the title and the ship AI's name, so translating it\n"
|
||||
" would break the referent inside the story. Localisation decision, yours.\n"
|
||||
"- Character names transliterated by sound, not translated by meaning.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f" wrote {len(written)} translation files + README to {OUT}")
|
||||
for n in sorted(written):
|
||||
print(f" {n}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
260
scripts/render_lang_test.py
Normal file
260
scripts/render_lang_test.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""Render the sample chapter in every language Qwen3-TTS supports.
|
||||
|
||||
Reads the translation JSONs produced by ``build_sample_translations.py`` and
|
||||
renders each utterance with the speaker's own voice, so the reviewer can hear
|
||||
whether an identity survives the language change.
|
||||
|
||||
Output: ``<project>/Lang_test/<LANG>/uttNN_<Speaker>.wav`` plus a stitched
|
||||
per-language file, which is what you actually listen to.
|
||||
|
||||
Qwen3-TTS auto-detects language from the text - ``/v1/tts`` takes no language
|
||||
argument - so there is nothing to set per clip.
|
||||
|
||||
The speaker's cached ICL prompt is preferred over the raw reference WAV because
|
||||
it gives the identical voice on every utterance. Where no prompt is cached the
|
||||
script falls back to ``ref_audio`` + ``ref_text``, which needs the reference
|
||||
transcript to be correct or the clone is worse, not better.
|
||||
|
||||
Usage:
|
||||
python scripts/render_lang_test.py --project DealHunterShip_4.0e_whole-to-audio
|
||||
python scripts/render_lang_test.py --languages EN FR --timeout 600
|
||||
python scripts/render_lang_test.py --dry-run
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from umbraaudio.config import load_config # noqa: E402
|
||||
|
||||
TTS_URL = "http://127.0.0.1:8881"
|
||||
|
||||
# Chapter 1 of Deal Hunter is the voice-introduction sample.
|
||||
CHAPTER_ID = 1
|
||||
TRANSLATION_DIRNAME = "Lang_test"
|
||||
|
||||
|
||||
def slugify(name: str) -> str:
|
||||
return name.strip().lower().replace(" ", "_")
|
||||
|
||||
|
||||
def build_translation_paths(config) -> Path:
|
||||
"""Where build_sample_translations.py put them."""
|
||||
return config.project_dir / "samples" / "translations"
|
||||
|
||||
|
||||
def speaker_refs(data_dir: Path) -> dict[str, Path]:
|
||||
voices = data_dir / "voices"
|
||||
return {p.name[: -len("_ref.wav")]: p for p in voices.glob("*_ref.wav")}
|
||||
|
||||
|
||||
def build_resolver(data_dir: Path, refs: dict[str, Path]) -> dict[str, str]:
|
||||
"""Map the display name in a translation to the real voice slug.
|
||||
|
||||
The translations say "Kainda"; the speaker record and the reference file say
|
||||
"Kainda Walters Foster Murphy". Resolving through the alias map rather than
|
||||
by string-munging the display name is what keeps the two in step when a
|
||||
speaker is renamed - the same lesson as the speaker-review audit, where a
|
||||
stale name silently produced no voice at all.
|
||||
"""
|
||||
from umbraaudio.analysis.attribution_qa import build_alias_map
|
||||
|
||||
alias_map = build_alias_map(data_dir / "speakers.json")
|
||||
by_lower = {k.lower(): v for k, v in alias_map.items()}
|
||||
out: dict[str, str] = {}
|
||||
for display in {u["speaker"] for u in _ALL_SPEAKERS}:
|
||||
canonical = by_lower.get(display.lower())
|
||||
slug = slugify(canonical or display)
|
||||
if slug in refs:
|
||||
out[display] = slug
|
||||
return out
|
||||
|
||||
|
||||
_ALL_SPEAKERS: list[dict[str, str]] = []
|
||||
|
||||
|
||||
def post_tts(
|
||||
url: str,
|
||||
text: str,
|
||||
*,
|
||||
prompt_slug: str | None,
|
||||
ref_audio: Path | None,
|
||||
ref_text: str | None,
|
||||
timeout: int,
|
||||
) -> bytes:
|
||||
"""POST multipart by hand; the boundary must match byte for byte."""
|
||||
boundary = f"----umbra{uuid.uuid4().hex}"
|
||||
parts: list[bytes] = []
|
||||
|
||||
def field(name: str, value: str) -> None:
|
||||
parts.append(
|
||||
f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"\r\n\r\n'
|
||||
f"{value}\r\n".encode()
|
||||
)
|
||||
|
||||
def file_field(name: str, path: Path) -> None:
|
||||
parts.append(
|
||||
f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"; '
|
||||
f'filename="{path.name}"\r\nContent-Type: audio/wav\r\n\r\n'.encode()
|
||||
)
|
||||
parts.append(path.read_bytes())
|
||||
parts.append(b"\r\n")
|
||||
|
||||
field("text", text)
|
||||
if prompt_slug:
|
||||
field("prompt_slug", prompt_slug)
|
||||
if ref_audio:
|
||||
file_field("ref_audio", ref_audio)
|
||||
if ref_text:
|
||||
field("ref_text", ref_text)
|
||||
parts.append(f"--{boundary}--\r\n".encode())
|
||||
body = b"".join(parts)
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{url}/v1/tts",
|
||||
data=body,
|
||||
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
payload = json.loads(resp.read())
|
||||
audio = payload.get("audio_base64")
|
||||
if not audio:
|
||||
raise RuntimeError(f"no audio in response: {str(payload)[:200]}")
|
||||
import base64
|
||||
|
||||
return base64.b64decode(audio)
|
||||
|
||||
|
||||
def stitch(parts: list[Path], out: Path, gap_ms: int = 400) -> None:
|
||||
"""Concatenate with a short gap so speakers are audibly separate."""
|
||||
import subprocess
|
||||
|
||||
if not parts:
|
||||
return
|
||||
listing = out.parent / f".{out.stem}_concat.txt"
|
||||
listing.write_text(
|
||||
"".join(f"file '{p.resolve()}'\n" for p in parts)
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
"ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(listing),
|
||||
"-af", f"apad=pad_dur={gap_ms/1000}", "-ar", "24000", "-ac", "1",
|
||||
str(out),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
listing.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--project")
|
||||
ap.add_argument("--languages", help="comma-separated suffixes, default all found")
|
||||
ap.add_argument("--timeout", type=int, default=900)
|
||||
ap.add_argument("--dry-run", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
config = load_config(args.project)
|
||||
data_dir = config.data_dir
|
||||
tdir = build_translation_paths(config)
|
||||
if not tdir.exists():
|
||||
print(f" no translations at {tdir} - run build_sample_translations.py first")
|
||||
return 1
|
||||
|
||||
refs = speaker_refs(data_dir)
|
||||
prompts = {p.stem for p in (data_dir / "voice_prompts").glob("*.pt")}
|
||||
global _ALL_SPEAKERS
|
||||
_ALL_SPEAKERS = [
|
||||
u for f in sorted(tdir.glob("deal_hunter_sample_ch1_*.json"))
|
||||
for u in json.loads(f.read_text(encoding="utf-8"))["utterances"]
|
||||
]
|
||||
resolved = build_resolver(data_dir, refs)
|
||||
for name in sorted({u["speaker"] for u in _ALL_SPEAKERS}):
|
||||
if name not in resolved:
|
||||
print(f" WARNING: no reference audio resolves for {name!r}")
|
||||
langs = sorted(tdir.glob("deal_hunter_sample_ch1_*.json"))
|
||||
if args.languages:
|
||||
want = {s.strip().upper() for s in args.languages.split(",")}
|
||||
langs = [p for p in langs if p.stem.rsplit("_", 1)[-1].upper() in want]
|
||||
if not langs:
|
||||
print(" no language files matched")
|
||||
return 1
|
||||
|
||||
out_root = config.project_dir / TRANSLATION_DIRNAME
|
||||
total = sum(len(json.loads(p.read_text(encoding="utf-8"))["utterances"]) for p in langs)
|
||||
print(f" {len(langs)} languages, {total} clips -> {out_root}")
|
||||
missing = []
|
||||
for p in langs:
|
||||
for u in json.loads(p.read_text(encoding="utf-8"))["utterances"]:
|
||||
slug = resolved.get(u["speaker"], "")
|
||||
if slug not in refs:
|
||||
missing.append((p.stem.rsplit("_", 1)[-1], u["speaker"]))
|
||||
|
||||
if args.dry_run:
|
||||
for lang in langs:
|
||||
doc = json.loads(lang.read_text(encoding="utf-8"))
|
||||
print(f"\n {doc['language_name']} ({doc['language_code']})")
|
||||
for u in doc["utterances"]:
|
||||
slug = resolved.get(u["speaker"], "")
|
||||
mode = "ICL prompt" if slug in prompts else (
|
||||
"ref_audio" if slug in refs else "NO REFERENCE"
|
||||
)
|
||||
print(f" {u['index']:2d} {u['speaker']:32s} {mode}")
|
||||
if missing:
|
||||
print(f"\n speakers with no reference audio: {sorted(set(missing))}")
|
||||
return 0
|
||||
|
||||
out_root.mkdir(parents=True, exist_ok=True)
|
||||
ok = failed = 0
|
||||
for lang_file in langs:
|
||||
doc = json.loads(lang_file.read_text(encoding="utf-8"))
|
||||
suffix = lang_file.stem.rsplit("_", 1)[-1]
|
||||
out_dir = out_root / suffix
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
made: list[Path] = []
|
||||
for u in doc["utterances"]:
|
||||
slug = slugify(u["speaker"])
|
||||
dest = out_dir / f"utt{u['index']:02d}_{u['speaker'].replace(' ', '_')}.wav"
|
||||
if slug not in refs:
|
||||
print(f" SKIP {suffix} utt{u['index']} - no reference for {u['speaker']}")
|
||||
failed += 1
|
||||
continue
|
||||
t0 = time.time()
|
||||
try:
|
||||
audio = post_tts(
|
||||
TTS_URL, u["text"],
|
||||
prompt_slug=slug if slug in prompts else None,
|
||||
ref_audio=None if slug in prompts else refs[slug],
|
||||
ref_text=None if slug in prompts else None,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
dest.write_bytes(audio)
|
||||
made.append(dest)
|
||||
ok += 1
|
||||
print(f" {suffix} utt{u['index']:02d} {u['speaker']:26s} "
|
||||
f"{len(audio)/1024:6.0f} KB {time.time()-t0:5.1f}s")
|
||||
except (urllib.error.URLError, TimeoutError, RuntimeError) as e:
|
||||
failed += 1
|
||||
print(f" {suffix} utt{u['index']:02d} {u['speaker']:26s} FAILED: {e}")
|
||||
try:
|
||||
stitch(made, out_dir / f"_full_sample_{suffix}.wav")
|
||||
print(f" {suffix}: stitched {len(made)} clips -> _full_sample_{suffix}.wav")
|
||||
except Exception as e: # stitching is a convenience, not the deliverable
|
||||
print(f" {suffix}: stitch failed: {e}")
|
||||
|
||||
print(f"\n done: {ok} rendered, {failed} failed")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
127
specs/27-translation-pipeline-multipass.md
Normal file
127
specs/27-translation-pipeline-multipass.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Multi-pass semantic translation - design
|
||||
|
||||
Status: design, not implemented. Written 2026-10-12.
|
||||
|
||||
## The problem
|
||||
|
||||
A word-for-word translation of this material produces sentences that are
|
||||
grammatical and wrong. In an audiobook nobody can skip a sentence, so a
|
||||
meaning error is a defect the listener hears and cannot escape. Register,
|
||||
tone and the relationship between a line and what precedes it all carry
|
||||
meaning, and all of it is lost by mapping words one to one.
|
||||
|
||||
The requirement is therefore: **analyse the intent of a passage, then render
|
||||
that intent in the target language.** One pass cannot do this, because the
|
||||
model doing the rendering cannot simultaneously hold the whole scene and
|
||||
attend to the sentence in front of it.
|
||||
|
||||
## Proposed pipeline
|
||||
|
||||
Each stage has one job and produces an artefact that can be inspected. Nothing
|
||||
downstream may silently repair an upstream failure; each stage records why it
|
||||
was uncertain.
|
||||
|
||||
### Pass 0 - Scene assembly (no LLM)
|
||||
|
||||
Group utterances into scenes and attach the surrounding narration. The sample
|
||||
chapter shows why: *"One of my robots that Kainda decided to name Nanny is
|
||||
here. Say hello Nanny."* is meaningless alone. The line before it says who is
|
||||
about to speak. A per-utterance translator gets this wrong every time.
|
||||
|
||||
Output: scene id, ordered utterances, speaker list, prior scene summary.
|
||||
|
||||
### Pass 1 - Intent analysis (LLM, structured output)
|
||||
|
||||
For each utterance, produce a structured record, not prose:
|
||||
|
||||
- `function` - greeting, introduction, aside, punchline, instruction
|
||||
- `register` - formal / neutral / casual, and whether it shifts mid-scene
|
||||
- `emotion` - the value already used by the per-utterance variation controls
|
||||
- `addressee` - who the line is aimed at, resolved
|
||||
- `depends_on` - which earlier utterance must be understood for this to land
|
||||
- `literal_risk` - would a direct rendering read as wrong, and why
|
||||
|
||||
`depends_on` is the important one: it tells Pass 2 which context to re-read,
|
||||
and it tells review what a human must check.
|
||||
|
||||
### Pass 2 - Draft translation (LLM, context-aware)
|
||||
|
||||
Render Pass 1 into the target language, given the scene and the intent record -
|
||||
not the English sentence alone. Output carries `depends_on` forward so
|
||||
inconsistency can be detected.
|
||||
|
||||
### Pass 3 - Register and voice consistency (LLM)
|
||||
|
||||
Check that the same character speaks consistently across a chapter: formality
|
||||
level, habitual phrasing, sentence rhythm. Characters are distinguished by
|
||||
voice as much as by content; a translation that makes everyone sound like the
|
||||
same polite translator has lost the book.
|
||||
|
||||
### Pass 4 - Verification against source meaning (LLM)
|
||||
|
||||
The step a word-for-word pipeline has no equivalent for. Re-read the English
|
||||
source and the candidate translation and answer: *did anything change in
|
||||
meaning?* Specifically:
|
||||
|
||||
- did a pronoun's referent survive?
|
||||
- did an implication become an assertion?
|
||||
- did irony become sincerity, or the reverse?
|
||||
- did a cultural or technical term lose its referent?
|
||||
|
||||
This is a critic pass over the whole chapter, not the same call that produced
|
||||
the draft. A model asked to check its own output in the same call is being
|
||||
asked to notice its own mistake while defending it.
|
||||
|
||||
### Pass 5 - Human review queue
|
||||
|
||||
Only what Pass 4 flagged, plus everything Pass 1 marked `literal_risk`. The
|
||||
goal is a review queue a human can actually work through, not a wall of text.
|
||||
Target: same order as the speaker review, which landed at ~5%.
|
||||
|
||||
## What must be preserved into the audio stage
|
||||
|
||||
The audio stage reads this and nothing else, so anything dropped here is gone:
|
||||
|
||||
| Field | Why it matters |
|
||||
|---|---|
|
||||
| `speaker` | voice identity; must be the canonical primary_name, not an alias (see audit §6) |
|
||||
| `index` | must line up 1:1 with the English utterance, or audio cannot be checked against source |
|
||||
| `emotion`, `pace`, `instruct` | already drive Qwen3-TTS delivery; they must survive translation, and emotion names must be mapped, not translated literally |
|
||||
| `function` | lets the narrator deliver a line correctly without re-deriving it |
|
||||
|
||||
## Hard problems specific to these languages
|
||||
|
||||
- **Arabic** is right-to-left and needs its own normalisation pass. Mixing
|
||||
Latin punctuation and numerals into RTL runs is a known corruption source.
|
||||
- **Chinese and Japanese** need text normalisation the English pipeline has no
|
||||
reason to do: full-width forms, kana/kanji boundaries, punctuation that is
|
||||
not a space.
|
||||
- **Korean** honorific level is lexical, not tonal. Speaker register in Pass 3
|
||||
has to decide speech level explicitly, because a wrong level is a social
|
||||
error, not a stylistic one.
|
||||
- **Norwegian, Polish, Dutch** have long compound words. The TTS engine will
|
||||
mispronounce what the translator joins or splits wrongly, so Pass 2 must not
|
||||
leave segmentation to chance.
|
||||
|
||||
## The sample that exists
|
||||
|
||||
`build_sample_translations.py` produced the sample chapter in all 10
|
||||
Qwen3-TTS languages. Those are **single-pass LLM drafts**, i.e. Pass 2 with
|
||||
Pass 1 skipped - which is exactly the failure mode described above, and they
|
||||
are labelled as such in every file. They exist to exercise the audio stage and
|
||||
agree the file format early, not to be used.
|
||||
|
||||
They are useful as a **bad-case corpus**: Pass 4 run over them should find real
|
||||
errors. If the critic cannot find errors in known-imperfect text, it is not
|
||||
doing its job.
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Which model runs Passes 1-4? This is where the local LLM question returns,
|
||||
and it is a different question from the TTS model choice in spec 26.
|
||||
2. Is Pass 3 (voice consistency) better done as a constraint passed into
|
||||
Pass 2 than as a separate pass?
|
||||
3. Does the critic in Pass 4 use the same model as the drafter? Same model
|
||||
checking itself is weaker; a different model is better and slower.
|
||||
4. Do translations get reviewed against the English by a bilingual reviewer, or
|
||||
only flagged by the critic?
|
||||
116
specs/28-ui-language-selection.md
Normal file
116
specs/28-ui-language-selection.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Language selection in the review UI - design
|
||||
|
||||
Status: design, not implemented. Written 2026-10-12.
|
||||
|
||||
## What the reviewer needs to do
|
||||
|
||||
Select a project. Work in **English** by default, because that is the source
|
||||
language and everything else derives from it. Then, for each of the other
|
||||
languages, confirm the translated utterances are right — check meaning, check
|
||||
the speaker is still right, check nothing was dropped.
|
||||
|
||||
The language is a **property of the review session**, not of the project.
|
||||
English is the source; every other language is a translation *of* English, so
|
||||
English can never be edited while viewing a translation without an explicit
|
||||
warning.
|
||||
|
||||
## The decision that shapes everything else
|
||||
|
||||
**Is the reviewer looking at English source text, or at a translation?**
|
||||
|
||||
Both are needed, and they are not the same view:
|
||||
|
||||
- In **English**, the reviewer edits. This is the master.
|
||||
- In **another language**, the reviewer confirms. Editing a translation would
|
||||
fork the source of truth, and nothing would tell the two apart later.
|
||||
|
||||
So in a non-English language the text fields are **read-only**, and the
|
||||
affordances are accept / flag, not edit. This is the single most important
|
||||
rule in the design and it is what stops the 15 languages drifting apart.
|
||||
|
||||
## Proposed placement
|
||||
|
||||
A language selector belongs on the **chapter audio page**, not only in a
|
||||
global nav. Reasons:
|
||||
|
||||
- The reviewer works chapter by chapter. A selector at the top of the page they
|
||||
are already on removes the "go somewhere, change a setting, come back" step
|
||||
that made the last round of UI fixes necessary.
|
||||
- The existing speaker and quality pages are project-scoped, and the project is
|
||||
already a cookie. Language is the same kind of scope and should behave the
|
||||
same way.
|
||||
- A global nav selector invites the reviewer to believe they changed something
|
||||
project-wide when they only changed what they can see.
|
||||
|
||||
Concretely, on `/chapters/{id}`:
|
||||
|
||||
```
|
||||
Chapter 14 — Salvage [ English ▾ ] ● English (source) ○ Review mode
|
||||
```
|
||||
|
||||
Selecting a language swaps the utterance table to that language's rows. English
|
||||
is marked `source`; the rest are marked `translation` and show progress:
|
||||
`812 / 1874 confirmed`.
|
||||
|
||||
## State and persistence
|
||||
|
||||
Language is a cookie alongside `active_project`, so it behaves like the project
|
||||
selector already does and survives a reload. Two consequences worth stating:
|
||||
|
||||
- **It is per browser, not per user.** Two browsers will show two different
|
||||
languages, the same failure already hit with `active_project` (which is why
|
||||
the speakers page now names the book it is listing). The chapter page must
|
||||
therefore also *name the language it is showing*, in the same way.
|
||||
- The selected language must never be inferred from content. There is no
|
||||
auto-detect, and no "guess the language from the text". An explicit
|
||||
selector that can be wrong is far better than a correct guess the reviewer
|
||||
cannot see or override.
|
||||
|
||||
## Confirming a translation
|
||||
|
||||
Per utterance, in a non-English language:
|
||||
|
||||
| Action | Meaning |
|
||||
|---|---|
|
||||
| Accept | translation is correct; speaker and meaning both fine |
|
||||
| Flag | something is wrong - opens the English source beside it |
|
||||
|
||||
**Flagging must show the English next to the translation.** A translator
|
||||
reporting "this is wrong" is nearly useless; the reviewer needs to judge it.
|
||||
Two-column view, source left, translation right, diff only where flagged.
|
||||
|
||||
Bulk actions per chapter, because a reviewer will not click 1,874 times:
|
||||
Accept all unflagged in this chapter; Flag all unflagged for a given speaker.
|
||||
|
||||
## What must be shown, and why
|
||||
|
||||
- **Progress per language.** Without it there is no way to know whether
|
||||
Spanish is 4% or 94% reviewed. This is the same mistake as the speaker page
|
||||
showing a count with no denominator.
|
||||
- **Flag reason.** Wrong meaning, wrong speaker, mistranslated term, missing,
|
||||
extra, or "does not sound like this character". The last one is a voice
|
||||
question, not a language question, and it belongs to Pass 3.
|
||||
- **Source utterance id.** Every translated row must carry the English
|
||||
utterance index. A translation that cannot be traced back cannot be checked,
|
||||
and cannot be re-translated when the source changes.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This cannot be built until the following exist, because the view has nothing
|
||||
to show without them:
|
||||
|
||||
1. Translated utterance rows keyed to source utterance index (the export format
|
||||
already produced by `build_sample_translations.py` is a candidate shape).
|
||||
2. A per-language confirm/flag record with an author and a timestamp.
|
||||
3. A decision on whether flagging is per-utterance or per-(utterance, reviewer).
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Does the reviewer confirm, or does a second person? A single reviewer
|
||||
checking a machine translation is a weak control, and worth knowing which
|
||||
one is intended before the data model settles.
|
||||
2. Should the English view show which utterances have already been flagged in
|
||||
other languages? Probably yes, but it risks pushing the reviewer toward
|
||||
English-first thinking.
|
||||
3. Is a language ever "retired" mid-project, leaving a partially confirmed
|
||||
translation that no longer matches the source?
|
||||
Reference in New Issue
Block a user