93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
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())
|