initial commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import json
|
||||
import asyncio
|
||||
import httpx
|
||||
from loguru import logger
|
||||
from typing import List, Dict
|
||||
|
||||
OLLAMA_URL = "http://127.0.0.1:11434/api/generate"
|
||||
MODEL_NAME = "gemma4:e2b"
|
||||
INPUT_FILE = "data/output.json"
|
||||
OUTPUT_FILE = "data/scripts.json"
|
||||
|
||||
async def generate_script(client: httpx.AsyncClient, headline: str, content: str, attempt: int = 1) -> str:
|
||||
system_prompt = (
|
||||
"You are an expert newscaster script writer. Your sole purpose is to convert articles into scripts for a TTS engine. "
|
||||
"CRITICAL RULE: Provide ONLY the literal words to be spoken by the news anchor. "
|
||||
"STRICTLY FORBIDDEN: Do not include peripheral annotations, stage directions, or non-spoken text. "
|
||||
"Specifically, do NOT include:\n"
|
||||
"- Bracketed text (e.g., [Music], [Pause], [Cut to B-roll])\n"
|
||||
"- Parenthetical notes (e.g., (smiling), (slowly))\n"
|
||||
"- Speaker labels or names (e.g., 'Anchor:', 'Reporter:', 'Host:')\n"
|
||||
"- Scene headings, timestamps, or markdown formatting.\n"
|
||||
"The output must be a clean stream of spoken text ready for a teleprompter."
|
||||
)
|
||||
prompt = f"Write a detailed, lengthy newscaster's script based on the following text:\n\n```text\n{content}```"
|
||||
payload = {
|
||||
"model": MODEL_NAME,
|
||||
"prompt": prompt,
|
||||
"system": system_prompt,
|
||||
"stream": False
|
||||
}
|
||||
|
||||
try:
|
||||
logger.info(f"Generating script for: {headline} (Attempt {attempt})")
|
||||
# High timeout for slow local LLM
|
||||
response = await client.post(OLLAMA_URL, json=payload, timeout=600.0)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
return result.get("response", "")
|
||||
except (httpx.HTTPStatusError, httpx.RequestError) as e:
|
||||
if attempt < 3:
|
||||
wait = attempt * 10
|
||||
logger.warning(f"Error occurred for {headline}: {e}. Retrying in {wait}s...")
|
||||
await asyncio.sleep(wait)
|
||||
return await generate_script(client, headline, content, attempt + 1)
|
||||
logger.error(f"Max retries reached for {headline}. Final error: {e}")
|
||||
return ""
|
||||
except Exception:
|
||||
logger.exception(f"Unexpected error occurred while requesting script for {headline}")
|
||||
return ""
|
||||
|
||||
async def main():
|
||||
try:
|
||||
with open(INPUT_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
logger.error(f"Failed to read {INPUT_FILE}: {e}")
|
||||
return
|
||||
|
||||
articles = data.get("articles", [])
|
||||
if not articles:
|
||||
logger.warning("No articles found in the input file.")
|
||||
return
|
||||
|
||||
results = []
|
||||
async with httpx.AsyncClient() as client:
|
||||
for i, article in enumerate(articles):
|
||||
headline = article.get("headline", "Unknown Headline")
|
||||
body = article.get("body", "")
|
||||
|
||||
if not body:
|
||||
logger.warning(f"Skipping article {headline} because body is empty.")
|
||||
continue
|
||||
|
||||
script = await generate_script(client, headline, body)
|
||||
if script:
|
||||
results.append({
|
||||
"headline": headline,
|
||||
"script": script
|
||||
})
|
||||
# Incremental save
|
||||
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(results, f, indent=4, ensure_ascii=False)
|
||||
else:
|
||||
logger.warning(f"Failed to generate script for {headline}")
|
||||
|
||||
# Avoid overloading Ollama
|
||||
await asyncio.sleep(2)
|
||||
|
||||
logger.info(f"Completed processing. Total scripts written: {len(results)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user