diff --git a/akt1/kapitel01.md b/akt1/kapitel01.md index 9af622e..3ae447f 100644 --- a/akt1/kapitel01.md +++ b/akt1/kapitel01.md @@ -80,7 +80,7 @@ Julian nickte langsam. Ein winziges, fast unmerkliches Lächeln stahl sich in se „Einhörner“, bemerkte Amara sarkastisch, „Einhörner und Feenstaub“. -Elena sah Amara von der Seite an, ein wehmütiges Lächeln auf den Lippen. „Das sagst ausgerechnet du, die seit seit Jahren glücklich verheiratet ist? Du und Darryl seid wahrscheinlich das stabilste Paar, das ich in meinem gesamten Umfeld kenne.“ +Elena sah Amara von der Seite an, ein wehmütiges Lächeln auf den Lippen. „Das sagst ausgerechnet du, die seit Jahren glücklich verheiratet ist? Du und Darryl seid wahrscheinlich das stabilste Paar, das ich in meinem gesamten Umfeld kenne.“ Amara zuckte mit den Schultern, doch ihr Blick wurde weicher. „Mein Leben hat genug Achterbahnfahrten, El. Das weiß niemand besser als du.“ diff --git a/speak.sh b/speak.sh index 0769709..e987ec5 100755 --- a/speak.sh +++ b/speak.sh @@ -1,5 +1,17 @@ #!/bin/sh +# Resolve input to absolute path before changing directory +INPUT_FILE="$1" +case "$INPUT_FILE" in + /*) ;; + *) INPUT_FILE="$PWD/$INPUT_FILE" ;; +esac + +if test -z "$INPUT_FILE" || test ! -f "$INPUT_FILE"; then + echo "Usage: $0 " >&2 + exit 1 +fi + cd "$(dirname "$0")" if test -f .env; then @@ -8,18 +20,75 @@ if test -f .env; then set +a fi -OUT="${1:-output.wav}" +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT -TMP_TEXT=$(mktemp) -trap 'rm -f "$TMP_TEXT"' EXIT -cat > "$TMP_TEXT" +# Split input into paragraph files +awk -v tmpdir="$TMP_DIR" ' + BEGIN { RS = "" } + { + n++ + fname = tmpdir "/para" n ".txt" + print > fname + close(fname) + } +' "$INPUT_FILE" + +PARA_COUNT=$(awk 'BEGIN { RS = "" } { n++ } END { print n+0 }' "$INPUT_FILE") + +if test "$PARA_COUNT" -eq 0; then + echo "Error: no paragraphs found in input file" >&2 + exit 1 +fi + +CHUNKS=$(( (PARA_COUNT + 2) / 3 )) REF_AUDIO_B64=$(base64 -w 0 elena-referenz.wav) -ESCAPED_TEXT=$(awk '{gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t"); if (NR>1) printf "\\n"; printf "%s", $0}' "$TMP_TEXT") -printf '{"text":"%s","language":"German","ref_text":"Das Café Laube roch nach einer Mischung aus frisch gerösteten Bohnen, süßlichen Lilien und dem metallischen Unterton von Regen","trim_sample":true,"auto_denoise":false,"high_fidelity":false,"ref_audio_b64":"%s"}' "$ESCAPED_TEXT" "$REF_AUDIO_B64" | \ -curl -X POST "https://vonkaiser-audiodojo.chutes.ai/tts/qwen3clone" \ - -H "Authorization: Bearer $CHUTES_API_KEY" \ - -H "Content-Type: application/json" \ - --data-binary @- \ - --output "$OUT" +CHUNK=1 +while test "$CHUNK" -le "$CHUNKS"; do + OUT=$(printf "part%03d.wav" "$CHUNK") + + if test -f "$OUT"; then + echo "Skipping existing file: $OUT" >&2 + CHUNK=$((CHUNK + 1)) + continue + fi + + START=$(( (CHUNK - 1) * 3 + 1 )) + END=$(( START + 2 )) + + TMP_TEXT="$TMP_DIR/chunk$CHUNK.txt" + : > "$TMP_TEXT" + + P=$START + FIRST=1 + while test "$P" -le "$END"; do + PARA_FILE="$TMP_DIR/para$P.txt" + if test -f "$PARA_FILE"; then + if test "$FIRST" -eq 1; then + FIRST=0 + else + printf "\n\n" >> "$TMP_TEXT" + fi + cat "$PARA_FILE" >> "$TMP_TEXT" + fi + P=$((P + 1)) + done + + ESCAPED_TEXT=$(awk '{gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t"); if (NR>1) printf "\\n"; printf "%s", $0}' "$TMP_TEXT") + + printf '{"text":"%s","language":"German","ref_text":"Das Café Laube roch nach einer Mischung aus frisch gerösteten Bohnen, süßlichen Lilien und dem metallischen Unterton von Regen","trim_sample":true,"auto_denoise":false,"high_fidelity":false,"ref_audio_b64":"%s"}' "$ESCAPED_TEXT" "$REF_AUDIO_B64" | \ + curl -f -s -S -X POST "https://vonkaiser-audiodojo.chutes.ai/tts/qwen3clone" \ + -H "Authorization: Bearer $CHUTES_API_KEY" \ + -H "Content-Type: application/json" \ + --data-binary @- \ + --output "$OUT" + + if test $? -ne 0; then + rm -f "$OUT" + echo "Warning: failed to generate part $CHUNK, skipping" >&2 + fi + + CHUNK=$((CHUNK + 1)) +done