lettercast-api / app.py
lettercast's picture
initial commit
2b609ac
import traceback
import gradio as gr
import requests
import time
import os
import re
from typing import Dict, Tuple, Optional, Union
# Read from environment variables with default values if not set
LETTERCAST_API_BASE = os.environ.get("LETTERCAST_API_BASE", "https://app.lettercast.ai/v1")
WAIT_TIME_S = 20
DEFAULT_API_TOKEN = os.environ.get("LETTERCAST_DEFAULT_API_TOKEN") # Replace with your default token
def is_valid_url(url: str) -> bool:
url_pattern = re.compile(
r'^https?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain...
r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
return url_pattern.match(url) is not None
def create_pod(url: Optional[str], file: Optional[str],
html_content: Optional[str], detail_level: str, create_video: bool,
progress: gr.Progress = gr.Progress()) -> Tuple[Dict, str, Optional[str], Optional[str]]:
# Validate that only one input method is provided
input_count = sum(bool(x) for x in (url, file, html_content))
if input_count != 1:
return {"error": "Please provide exactly one of: URL, PDF file, or HTML content."}, "Error", None, None
if url and not is_valid_url(url):
return {"error": "Invalid URL format. Please enter a valid URL."}, "Error", None, None
token = DEFAULT_API_TOKEN
headers = {"Authorization": f"Bearer {token}"}
data = {
"detail_level": detail_level,
"create_video": create_video,
}
if url:
data["url"] = url
elif html_content:
data["html_content"] = html_content
endpoint_url = f"{LETTERCAST_API_BASE}/pods"
try:
if file is not None:
with open(file, "rb") as f:
files = {"file": (os.path.basename(file), f, "application/pdf")}
response = requests.post(endpoint_url, headers=headers, data=data, files=files)
else:
headers["Content-Type"] = "application/json"
response = requests.post(endpoint_url, headers=headers, json=data)
if response.status_code == 200:
resp_data = response.json()
pod_id = resp_data.get("pod_id")
if pod_id:
# Trigger the status check immediately if we have a valid pod_id
status, _, audio_file, video_file = check_pod_status(pod_id, progress)
return resp_data, status, audio_file, video_file
else:
return resp_data, "Pod created, but no pod_id received.", None, None
else:
error_message = f"Error {response.status_code}: {response.text}"
return {"error": error_message}, "Pod creation failed", None, None
except Exception as e:
error_message = f"Error creating pod: {str(e)}"
return {"error": error_message}, "Error", None, None
def check_pod_status(pod_id: str,
progress: gr.Progress = gr.Progress()) -> Tuple[str, Optional[str], Optional[str], Optional[str]]:
token = DEFAULT_API_TOKEN
headers = {"Authorization": f"Bearer {token}"}
max_retries = 30 # Max wait time of 10 minutes
for i in range(max_retries):
time.sleep(WAIT_TIME_S)
progress(i / max_retries, desc="Checking pod status...")
try:
response = requests.get(f"{LETTERCAST_API_BASE}/pods/{pod_id}", headers=headers)
if response.status_code == 200:
pod = response.json()
if pod['generation_status'] == 'done':
audio_file = download_media(pod_id, 'audio')
video_file = download_media(pod_id, 'video')
return "Pod is ready!", pod_id, audio_file, video_file
elif pod['generation_status'] == 'failed':
return "Pod creation failed.", None, None, None
else:
continue
except Exception as e:
return f"Error checking pod status: {str(e)}", None, None, None
return "Pod generation timed out.", None, None, None
def download_media(pod_id: str, media_type: str) -> Optional[str]:
token = DEFAULT_API_TOKEN
headers = {"Authorization": f"Bearer {token}"}
url = f"{LETTERCAST_API_BASE}/pods/{pod_id}/{media_type}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
file_extension = 'mp3' if media_type == 'audio' else 'mp4'
file_path = f'pod_{media_type}_{pod_id}.{file_extension}'
with open(file_path, 'wb') as file:
file.write(response.content)
return file_path
else:
return None
with gr.Blocks() as demo:
gr.Markdown("## Try the [Lettercast.ai](https://lettercast.ai) API")
gr.Markdown("""
Provide exactly one of the following:
- Enter a URL
- Upload a PDF file
- Enter HTML content
The API home is at our [Developer Hub](https://developer.lettercast.ai).
""")
with gr.Row():
url = gr.Textbox(label="URL", placeholder="Enter a URL")
detail_level = gr.Dropdown(
choices=["summary_discussion", "summary_single_host"],
label="Pod Type",
value="summary_discussion"
)
with gr.Row():
file = gr.File(label="Upload a PDF file", type="filepath")
create_video = gr.Checkbox(label="Create Video (PDF only)", value=False)
with gr.Row():
html_content = gr.Textbox(label="HTML Content", placeholder="Enter HTML content", lines=5)
create_pod_btn = gr.Button("Create Pod")
output = gr.JSON(label="Response")
status_output = gr.Textbox(label="Status")
audio_player = gr.Audio(label="Generated Audio", visible=False, autoplay=True)
video_player = gr.Video(label="Generated Video", visible=False)
def update_ui(result, status, audio_file, video_file):
audio_visible = audio_file is not None
video_visible = video_file is not None
return (
result,
status,
audio_file,
gr.update(visible=audio_visible),
video_file,
gr.update(visible=video_visible)
)
create_pod_btn.click(
fn=lambda *args: update_ui(*create_pod(*args)),
inputs=[url, file, html_content, detail_level, create_video],
outputs=[output, status_output, audio_player, audio_player, video_player, video_player],
show_progress="full",
show_api=False
)
demo.launch()