feat(xiaoyuzhou): Chinese punctuation prompt + optional --polish flag (#291)
Adds a punctuation-aware Whisper prompt and an optional --polish step (free Llama 3.3 70B on Groq) for Chinese podcast transcripts. Maintainer follow-up on the branch: replaced ASCII quotes that bash swallowed inside the prompt (hexdump-verified), added the same prompt to the 429-retry call, and documented --polish as optional. bash -n passes.
This commit is contained in:
@@ -1,11 +1,29 @@
|
||||
#!/bin/bash
|
||||
# 小宇宙播客转文字脚本
|
||||
# 用法: bash transcribe.sh <小宇宙链接> [输出文件路径]
|
||||
# 用法: bash transcribe.sh [--polish] <小宇宙链接> [输出文件路径]
|
||||
# 环境变量: GROQ_API_KEY (必须)
|
||||
#
|
||||
# --polish: 转录后调用 Groq Llama 3.3 70B 给文稿补中文标点+合理分段
|
||||
# (Whisper 对中文标点支持较弱,开启后阅读体验显著更好)
|
||||
|
||||
set -e
|
||||
|
||||
URL="${1:?用法: bash transcribe.sh <小宇宙链接> [输出文件路径]}"
|
||||
POLISH=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--polish) POLISH=1; shift ;;
|
||||
--) shift; break ;;
|
||||
-h|--help)
|
||||
echo "用法: bash transcribe.sh [--polish] <小宇宙链接> [输出文件路径]"
|
||||
exit 0 ;;
|
||||
--*)
|
||||
echo "未知选项: $1" >&2
|
||||
exit 1 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
URL="${1:?用法: bash transcribe.sh [--polish] <小宇宙链接> [输出文件路径]}"
|
||||
OUTPUT="${2:-/tmp/podcast_transcript.txt}"
|
||||
TMPDIR="/tmp/xiaoyuzhou_$$"
|
||||
|
||||
@@ -99,6 +117,7 @@ for i in $(seq 0 $((NUM_CHUNKS - 1))); do
|
||||
-F file="@$TMPDIR/chunk_${i}.mp3" \
|
||||
-F model="whisper-large-v3" \
|
||||
-F language="zh" \
|
||||
-F prompt="以下是一段中文普通话播客录音,请输出包含完整中文标点(,。?!:;“”‘’)的转写文本。" \
|
||||
-F response_format="text")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||
@@ -122,6 +141,7 @@ for i in $(seq 0 $((NUM_CHUNKS - 1))); do
|
||||
-F file="@$TMPDIR/chunk_${i}.mp3" \
|
||||
-F model="whisper-large-v3" \
|
||||
-F language="zh" \
|
||||
-F prompt="以下是一段中文普通话播客录音,请输出包含完整中文标点(,。?!:;“”‘’)的转写文本。" \
|
||||
-F response_format="text")
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
@@ -140,6 +160,81 @@ for i in $(seq 0 $((NUM_CHUNKS - 1))); do
|
||||
echo "✅ ($CHARS 字)"
|
||||
done
|
||||
|
||||
# Step 6.5 (可选): 用 Llama 3.3 70B 给文稿补标点+分段
|
||||
if [ "$POLISH" = "1" ]; then
|
||||
echo "✨ 正在润色(Llama 3.3 70B 加标点+分段)..."
|
||||
for i in $(seq 0 $((NUM_CHUNKS - 1))); do
|
||||
echo -n " 段 $((i+1))/$NUM_CHUNKS... "
|
||||
IN_FILE="$TMPDIR/transcript_${i}.txt" \
|
||||
OUT_FILE="$TMPDIR/polished_${i}.txt" \
|
||||
GROQ_API_KEY="$GROQ_API_KEY" \
|
||||
python3 <<'PY'
|
||||
import json, os, sys, urllib.request, urllib.error
|
||||
|
||||
KEY = os.environ["GROQ_API_KEY"]
|
||||
IN = os.environ["IN_FILE"]
|
||||
OUT = os.environ["OUT_FILE"]
|
||||
|
||||
MODEL = "llama-3.3-70b-versatile"
|
||||
MAX_DEPTH = 3
|
||||
PROMPT_TMPL = (
|
||||
"以下是一段中文普通话播客的语音转写片段,由于 Whisper 对中文标点支持较弱,"
|
||||
"整段几乎没有标点。请你**只做一件事**:在合适位置补充中文标点(,。!?:;),"
|
||||
"可以适度分段。\n\n"
|
||||
"**严格要求**:\n"
|
||||
"- 不得修改、删除、增加任何汉字或英文/数字\n"
|
||||
"- 不得改写、润色、总结\n"
|
||||
"- 不得添加任何解释、前言、后记\n"
|
||||
"- 直接输出加好标点+合理分段后的全文\n\n"
|
||||
"原文:\n{}"
|
||||
)
|
||||
|
||||
def call_groq(text):
|
||||
body = json.dumps({
|
||||
"model": MODEL,
|
||||
"temperature": 0.2,
|
||||
"max_completion_tokens": 8192,
|
||||
"messages": [{"role": "user", "content": PROMPT_TMPL.format(text)}],
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
"https://api.groq.com/openai/v1/chat/completions",
|
||||
data=body,
|
||||
headers={
|
||||
"Authorization": f"Bearer {KEY}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "agent-reach-xiaoyuzhou/1.0",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=180) as r:
|
||||
resp = json.load(r)
|
||||
return (
|
||||
resp["choices"][0]["message"]["content"].strip(),
|
||||
resp["choices"][0].get("finish_reason"),
|
||||
)
|
||||
|
||||
def polish(text, depth=0):
|
||||
try:
|
||||
out, fr = call_groq(text)
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.stderr.write(f"polish HTTP {e.code}: {e.read().decode(errors='replace')[:200]}\n")
|
||||
return text # fallback to raw
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"polish error: {e}\n")
|
||||
return text
|
||||
if fr != "length" or depth >= MAX_DEPTH:
|
||||
return out
|
||||
# 输出被截断:从中点切两半递归处理
|
||||
mid = len(text) // 2
|
||||
return polish(text[:mid], depth + 1) + polish(text[mid:], depth + 1)
|
||||
|
||||
content = open(IN, encoding="utf-8").read().strip()
|
||||
result = polish(content)
|
||||
open(OUT, "w", encoding="utf-8").write(result + "\n")
|
||||
print(f"✅ ({len(result)} 字)")
|
||||
PY
|
||||
done
|
||||
fi
|
||||
|
||||
# Step 7: 合并输出
|
||||
echo "📄 正在合并文字稿..."
|
||||
|
||||
@@ -149,12 +244,19 @@ echo "📄 正在合并文字稿..."
|
||||
echo "来源: $URL"
|
||||
echo "时长: ${DURATION_MIN}分${DURATION_SEC}秒"
|
||||
echo "转录时间: $(date '+%Y-%m-%d %H:%M')"
|
||||
if [ "$POLISH" = "1" ]; then
|
||||
echo "润色: Groq Llama 3.3 70B"
|
||||
fi
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
|
||||
for i in $(seq 0 $((NUM_CHUNKS - 1))); do
|
||||
cat "$TMPDIR/transcript_${i}.txt"
|
||||
if [ "$POLISH" = "1" ] && [ -f "$TMPDIR/polished_${i}.txt" ]; then
|
||||
cat "$TMPDIR/polished_${i}.txt"
|
||||
else
|
||||
cat "$TMPDIR/transcript_${i}.txt"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
} > "$OUTPUT"
|
||||
|
||||
@@ -71,13 +71,15 @@ bili rank -n 10
|
||||
|
||||
## 小宇宙播客 / Xiaoyuzhou Podcast
|
||||
|
||||
### 转录单集播客
|
||||
### 转录单集播客(可选 --polish 增强标点)
|
||||
|
||||
```bash
|
||||
# 输出 Markdown 文件到 /tmp/
|
||||
~/.agent-reach/tools/xiaoyuzhou/transcribe.sh "https://www.xiaoyuzhoufm.com/episode/EPISODE_ID"
|
||||
# 输出 Markdown 文件到 /tmp/。--polish 让 Llama 3.3 70B 给文稿补中文标点+合理分段
|
||||
~/.agent-reach/tools/xiaoyuzhou/transcribe.sh --polish "https://www.xiaoyuzhoufm.com/episode/EPISODE_ID"
|
||||
```
|
||||
|
||||
> 转写 prompt 已要求 Whisper 输出中文标点;若标点效果仍不理想,可加 `--polish` 用 Groq 上免费的 Llama 3.3 70B 补标点+合理分段(9 分钟播客约多 ~7 秒)。每次转写多一轮 LLM 调用,按需使用。
|
||||
|
||||
### 前置要求
|
||||
|
||||
1. **ffmpeg**: `brew install ffmpeg`
|
||||
|
||||
Reference in New Issue
Block a user