Spaces:
Sleeping
Sleeping
Commit
·
a38b4f9
0
Parent(s):
Initial commit: media-gen-api with Streamlit UI
Browse files- media-gen-api/.devcontainer/devcontainer.json +33 -0
- media-gen-api/.gitignore +44 -0
- media-gen-api/.vs/VSWorkspaceState.json +6 -0
- media-gen-api/.vs/ai_gov_comm/FileContentIndex/09e5affd-6ff5-45b8-b6bb-c44b5b94954f.vsidx +0 -0
- media-gen-api/.vs/ai_gov_comm/v17/.wsuo +0 -0
- media-gen-api/.vs/ai_gov_comm/v17/DocumentLayout.json +12 -0
- media-gen-api/Dockerfile +34 -0
- media-gen-api/PRIVACY_POLICY.md +38 -0
- media-gen-api/Procfile +1 -0
- media-gen-api/Project_structure.docx +0 -0
- media-gen-api/README.md +94 -0
- media-gen-api/app/__init__.py +0 -0
- media-gen-api/app/api/__init__.py +0 -0
- media-gen-api/app/api/v1/__init__.py +0 -0
- media-gen-api/app/api/v1/audio.py +27 -0
- media-gen-api/app/api/v1/download.py +23 -0
- media-gen-api/app/api/v1/image.py +19 -0
- media-gen-api/app/api/v1/metrics.py +24 -0
- media-gen-api/app/api/v1/ppt.py +24 -0
- media-gen-api/app/api/v1/utils.py +9 -0
- media-gen-api/app/api/v1/video.py +20 -0
- media-gen-api/app/auth/auth.py +32 -0
- media-gen-api/app/core/config.py +6 -0
- media-gen-api/app/db.py +7 -0
- media-gen-api/app/main.py +70 -0
- media-gen-api/app/models.py +14 -0
- media-gen-api/app/services/audio_service.py +38 -0
- media-gen-api/app/services/image_service.py +38 -0
- media-gen-api/app/services/ppt_service.py +34 -0
- media-gen-api/app/services/video_service.py +35 -0
- media-gen-api/assets/default.jpg +0 -0
- media-gen-api/assets/logo_watermark.png +0 -0
- media-gen-api/backend/media_gen.py +187 -0
- media-gen-api/docs/index.html +62 -0
- media-gen-api/docs/privacy.html +68 -0
- media-gen-api/nixpacks.toml +19 -0
- media-gen-api/openapi.json +1 -0
- media-gen-api/packages.txt +3 -0
- media-gen-api/requirements.txt +19 -0
- media-gen-api/screenshots/generated_audio_sample.png +0 -0
- media-gen-api/screenshots/home_page.png +0 -0
- media-gen-api/scripts/preload_content.py +17 -0
- media-gen-api/streamlit_ui.py +75 -0
media-gen-api/.devcontainer/devcontainer.json
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "Python 3",
|
3 |
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
4 |
+
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
|
5 |
+
"customizations": {
|
6 |
+
"codespaces": {
|
7 |
+
"openFiles": [
|
8 |
+
"README.md",
|
9 |
+
"app.py"
|
10 |
+
]
|
11 |
+
},
|
12 |
+
"vscode": {
|
13 |
+
"settings": {},
|
14 |
+
"extensions": [
|
15 |
+
"ms-python.python",
|
16 |
+
"ms-python.vscode-pylance"
|
17 |
+
]
|
18 |
+
}
|
19 |
+
},
|
20 |
+
"updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
|
21 |
+
"postAttachCommand": {
|
22 |
+
"server": "streamlit run app.py --server.enableCORS false --server.enableXsrfProtection false"
|
23 |
+
},
|
24 |
+
"portsAttributes": {
|
25 |
+
"8501": {
|
26 |
+
"label": "Application",
|
27 |
+
"onAutoForward": "openPreview"
|
28 |
+
}
|
29 |
+
},
|
30 |
+
"forwardPorts": [
|
31 |
+
8501
|
32 |
+
]
|
33 |
+
}
|
media-gen-api/.gitignore
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Python
|
2 |
+
*.pyc
|
3 |
+
__pycache__/
|
4 |
+
*.pyo
|
5 |
+
*.pyd
|
6 |
+
*.log
|
7 |
+
|
8 |
+
# Virtual env
|
9 |
+
.venv/
|
10 |
+
venv/
|
11 |
+
ENV/
|
12 |
+
env/
|
13 |
+
|
14 |
+
# Environment secrets
|
15 |
+
.env
|
16 |
+
|
17 |
+
# Streamlit cache
|
18 |
+
.streamlit/
|
19 |
+
.metadata/
|
20 |
+
|
21 |
+
# Pytest cache
|
22 |
+
.pytest_cache/
|
23 |
+
|
24 |
+
# VS Code / PyCharm / IDE files
|
25 |
+
.vscode/
|
26 |
+
.idea/
|
27 |
+
.project
|
28 |
+
.pydevproject
|
29 |
+
|
30 |
+
# RStudio / Mac / Windows artifacts
|
31 |
+
.Rhistory
|
32 |
+
.DS_Store
|
33 |
+
Thumbs.db
|
34 |
+
|
35 |
+
# Output folders
|
36 |
+
outputs/
|
37 |
+
|
38 |
+
|
39 |
+
logs/
|
40 |
+
__pycache__/
|
41 |
+
outputs/
|
42 |
+
generated/
|
43 |
+
*.pyc
|
44 |
+
*.log
|
media-gen-api/.vs/VSWorkspaceState.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"ExpandedNodes": [
|
3 |
+
""
|
4 |
+
],
|
5 |
+
"PreviewInSolutionExplorer": false
|
6 |
+
}
|
media-gen-api/.vs/ai_gov_comm/FileContentIndex/09e5affd-6ff5-45b8-b6bb-c44b5b94954f.vsidx
ADDED
Binary file (84.3 kB). View file
|
|
media-gen-api/.vs/ai_gov_comm/v17/.wsuo
ADDED
Binary file (8.19 kB). View file
|
|
media-gen-api/.vs/ai_gov_comm/v17/DocumentLayout.json
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"Version": 1,
|
3 |
+
"WorkspaceRootPath": "C:\\Users\\fimba\\OneDrive\\Desktop\\PythonProjects\\ai_gov_comm\\",
|
4 |
+
"Documents": [],
|
5 |
+
"DocumentGroupContainers": [
|
6 |
+
{
|
7 |
+
"Orientation": 0,
|
8 |
+
"VerticalTabListWidth": 256,
|
9 |
+
"DocumentGroups": []
|
10 |
+
}
|
11 |
+
]
|
12 |
+
}
|
media-gen-api/Dockerfile
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use stable Python image
|
2 |
+
FROM python:3.10
|
3 |
+
|
4 |
+
# Install OS dependencies for Pillow and others
|
5 |
+
RUN apt-get update && apt-get install -y \
|
6 |
+
build-essential \
|
7 |
+
libjpeg-dev \
|
8 |
+
zlib1g-dev \
|
9 |
+
libpng-dev \
|
10 |
+
libfreetype6-dev \
|
11 |
+
&& rm -rf /var/lib/apt/lists/*
|
12 |
+
|
13 |
+
# Set working directory
|
14 |
+
WORKDIR /app
|
15 |
+
|
16 |
+
# Copy requirement files first for caching
|
17 |
+
COPY requirements.txt ./
|
18 |
+
|
19 |
+
# Install Python dependencies
|
20 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
21 |
+
|
22 |
+
# Copy the app files
|
23 |
+
COPY . /app
|
24 |
+
|
25 |
+
# Expose port (the port is dynamic and set by the platform, but 8000 is a good fallback)
|
26 |
+
EXPOSE 8000
|
27 |
+
|
28 |
+
# Create a script to start Streamlit with dynamic PORT
|
29 |
+
RUN echo '#!/bin/bash\n\
|
30 |
+
export PORT=${PORT:-8000}\n\
|
31 |
+
streamlit run app.py --server.port=$PORT --server.address=0.0.0.0' > /start.sh && chmod +x /start.sh
|
32 |
+
|
33 |
+
# Run the script
|
34 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
media-gen-api/PRIVACY_POLICY.md
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
---
|
3 |
+
|
4 |
+
### ✅ `PRIVACY_POLICY.md`
|
5 |
+
|
6 |
+
```markdown
|
7 |
+
# 🔐 Privacy Policy for OSNarayana Media Generator
|
8 |
+
|
9 |
+
Effective Date: July 19, 2025
|
10 |
+
|
11 |
+
Your privacy is important to us. This app is built to prioritize user data security and transparency.
|
12 |
+
|
13 |
+
## 1. What We Collect
|
14 |
+
|
15 |
+
- **No personal data** is collected, stored, or shared by this application.
|
16 |
+
- All prompts, audio, and media are **processed locally** or via **user-provided API keys** to external services (e.g., ElevenLabs, Unsplash).
|
17 |
+
|
18 |
+
## 2. API Usage
|
19 |
+
|
20 |
+
- If you use external services (like ElevenLabs or Unsplash), you are subject to their respective [Terms of Service](https://www.elevenlabs.io/terms) and [Privacy Policies](https://www.elevenlabs.io/privacy).
|
21 |
+
- Your API keys are stored **locally** in your environment file (`.env`) and never uploaded.
|
22 |
+
|
23 |
+
## 3. Data Storage
|
24 |
+
|
25 |
+
- Generated images, audio, and videos are stored **only on your local machine** under the `outputs/` directory.
|
26 |
+
- You can delete any generated content at your discretion.
|
27 |
+
|
28 |
+
## 4. Analytics & Ads
|
29 |
+
|
30 |
+
- This app contains **no ads**, **no tracking**, and **no analytics**.
|
31 |
+
|
32 |
+
## 5. Changes to Policy
|
33 |
+
|
34 |
+
Any updates to this policy will be reflected in this file on the [GitHub repository](https://github.com/your-username/osnarayana-media-generator).
|
35 |
+
|
36 |
+
## 6. Contact
|
37 |
+
|
38 |
+
For questions or issues, contact: **[email protected]**
|
media-gen-api/Procfile
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
web: streamlit run app.py --server.port=8000 --server.address=0.0.0.0
|
media-gen-api/Project_structure.docx
ADDED
Binary file (14 kB). View file
|
|
media-gen-api/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 🎙️ Media Generation API
|
2 |
+
|
3 |
+
A FastAPI-based backend to generate audio, images, video, and PPT from user inputs.
|
4 |
+
Supports BLEU/CLIP metrics, token-based authentication, and stores metadata in SQLite/Postgres.
|
5 |
+
|
6 |
+
A modular, RESTful FastAPI solution that converts text input into:
|
7 |
+
- 🎥 Video
|
8 |
+
- 🖼️ Image/Graphics
|
9 |
+
- 🔊 Audio
|
10 |
+
|
11 |
+
|
12 |
+
---
|
13 |
+
|
14 |
+
## 🚀 Features
|
15 |
+
|
16 |
+
- Text → Video: Tone, domain, and environment-aware video generation.
|
17 |
+
- Text → Audio: Context-aware voice synthesis with emotional tone and language support.
|
18 |
+
- Text → Graphics: Visual generation using parameter-based prompts.
|
19 |
+
- BLEU/CLIP metrics for prompt-output fidelity.
|
20 |
+
- Token-based authentication for secure API use.
|
21 |
+
- Dockerized for easy deployment
|
22 |
+
- Optional Streamlit/React UI
|
23 |
+
- Swagger UI: `http://localhost:8000/docs`
|
24 |
+
|
25 |
+
---
|
26 |
+
|
27 |
+
### 📁 Project Structure
|
28 |
+
media-gen-api/
|
29 |
+
├── app/
|
30 |
+
│ ├── api/v1/ # Versioned API endpoints
|
31 |
+
│ ├── auth/ # Token-based auth
|
32 |
+
│ ├── services/ # Core media generation logic
|
33 |
+
│ └── main.py # FastAPI entry point
|
34 |
+
├── tests/ # Unit/integration tests
|
35 |
+
├── requirements.txt
|
36 |
+
└── README.md
|
37 |
+
|
38 |
+
---
|
39 |
+
|
40 |
+
## 📦 Installation
|
41 |
+
🚀 Run Locally
|
42 |
+
1. Clone repo & create virtual environment
|
43 |
+
|
44 |
+
git clone https://github.com/yourorg/media-gen-api.git
|
45 |
+
cd media-gen-api
|
46 |
+
python -m venv .venv
|
47 |
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
48 |
+
|
49 |
+
2. Install dependencies
|
50 |
+
|
51 |
+
pip install -r requirements.txt
|
52 |
+
|
53 |
+
3. Run the API
|
54 |
+
|
55 |
+
uvicorn app.main:app --reload
|
56 |
+
|
57 |
+
Access docs: http://127.0.0.1:8000/docs
|
58 |
+
|
59 |
+
---
|
60 |
+
### 🔐 Authentication
|
61 |
+
Use Bearer <your_token> in the Authorize button or headers.
|
62 |
+
|
63 |
+
---
|
64 |
+
### 📡 API Endpoints Summary
|
65 |
+
| Endpoint | Method | Description |
|
66 |
+
|--------------------------|--------|---------------------------|
|
67 |
+
| /api/v1/audio/generate | POST | Generate audio from text |
|
68 |
+
| /api/v1/image/generate | POST | Generate image from text |
|
69 |
+
| /api/v1/video/generate | POST | Generate video from text |
|
70 |
+
| /api/v1/download | GET | Download generated file |
|
71 |
+
|
72 |
+
---
|
73 |
+
###📦 Deployment (Streamlit/Optional UI)
|
74 |
+
Option 1: Run with Streamlit (for demo)
|
75 |
+
streamlit run streamlit_ui.py
|
76 |
+
|
77 |
+
Option 2: Docker (Production-ready)
|
78 |
+
docker build -t media-gen-api .
|
79 |
+
docker run -p 8000:8000 media-gen-api
|
80 |
+
|
81 |
+
---
|
82 |
+
### 📊 Metrics Logging (Optional)
|
83 |
+
- BLEU score and CLIPScore (WIP)
|
84 |
+
- Latency, GPU/CPU tracking
|
85 |
+
- Log file: logs/generation.log
|
86 |
+
|
87 |
+
---
|
88 |
+
#### 📋 Submission Checklist
|
89 |
+
- ✅ RESTful modular architecture
|
90 |
+
- ✅ Multi-format (MP4, PNG, WAV)
|
91 |
+
- ✅ Token Auth + Swagger UI
|
92 |
+
- ✅ Compatible with DD/PIB via API
|
93 |
+
- ✅ Streamlit demo app (optional)
|
94 |
+
|
media-gen-api/app/__init__.py
ADDED
File without changes
|
media-gen-api/app/api/__init__.py
ADDED
File without changes
|
media-gen-api/app/api/v1/__init__.py
ADDED
File without changes
|
media-gen-api/app/api/v1/audio.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/api/v1/audio.py
|
2 |
+
from fastapi import APIRouter, HTTPException, Query, Depends, Request
|
3 |
+
from pydantic import BaseModel
|
4 |
+
from app.services.audio_service import generate_audio_file # ✅ Only this import
|
5 |
+
from app.auth.auth import verify_token
|
6 |
+
|
7 |
+
|
8 |
+
router = APIRouter()
|
9 |
+
|
10 |
+
class AudioInput(BaseModel):
|
11 |
+
text: str
|
12 |
+
voice: str = "default"
|
13 |
+
language: str = "en"
|
14 |
+
|
15 |
+
@router.post("/generate", dependencies=[Depends(verify_token)])
|
16 |
+
def generate_audio_endpoint(payload: AudioInput):
|
17 |
+
try:
|
18 |
+
file_path = generate_audio_file(payload.text, payload.voice, payload.language)
|
19 |
+
|
20 |
+
return {
|
21 |
+
"status": "success",
|
22 |
+
"type": "audio",
|
23 |
+
"file_path": file_path,
|
24 |
+
"download_url": f"/api/v1/download?file_path={file_path}"
|
25 |
+
}
|
26 |
+
except Exception as e:
|
27 |
+
raise HTTPException(status_code=500, detail=str(e))
|
media-gen-api/app/api/v1/download.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, HTTPException, Query, Depends, Request
|
2 |
+
from fastapi.responses import FileResponse
|
3 |
+
from app.auth.auth import verify_token
|
4 |
+
import os
|
5 |
+
|
6 |
+
router = APIRouter()
|
7 |
+
|
8 |
+
@router.get("/", dependencies=[Depends(verify_token)])
|
9 |
+
def download_file(file_path: str = Query(..., description="Relative path from project root")):
|
10 |
+
# Sanitize the input path
|
11 |
+
file_path = os.path.normpath(file_path)
|
12 |
+
|
13 |
+
# Absolute path (from project root)
|
14 |
+
abs_path = os.path.join(os.getcwd(), file_path)
|
15 |
+
|
16 |
+
if not os.path.isfile(abs_path):
|
17 |
+
raise HTTPException(status_code=404, detail="File not found")
|
18 |
+
|
19 |
+
return FileResponse(
|
20 |
+
path=abs_path,
|
21 |
+
filename=os.path.basename(abs_path),
|
22 |
+
media_type='application/octet-stream'
|
23 |
+
)
|
media-gen-api/app/api/v1/image.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, HTTPException, Query, Depends, Request
|
2 |
+
from pydantic import BaseModel
|
3 |
+
from app.services.image_service import generate_image_file
|
4 |
+
from app.auth.auth import verify_token
|
5 |
+
|
6 |
+
router = APIRouter()
|
7 |
+
|
8 |
+
class ImageInput(BaseModel):
|
9 |
+
prompt: str
|
10 |
+
style: str = "default"
|
11 |
+
|
12 |
+
@router.post("/generate", dependencies=[Depends(verify_token)]) # ✅ FIX: POST, not GET
|
13 |
+
def generate_image(payload: ImageInput):
|
14 |
+
filename = generate_image_file(payload.prompt, payload.style)
|
15 |
+
return {
|
16 |
+
"message": "Image generated successfully",
|
17 |
+
"filename": filename,
|
18 |
+
"download_url": f"/api/v1/download?file_path=generated/image/{filename}"
|
19 |
+
}
|
media-gen-api/app/api/v1/metrics.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/api/v1/metrics.py
|
2 |
+
from fastapi import APIRouter, HTTPException, Query, Depends, Request
|
3 |
+
from typing import List
|
4 |
+
from sklearn.metrics import accuracy_score
|
5 |
+
from nltk.translate.bleu_score import sentence_bleu
|
6 |
+
from sentence_transformers import SentenceTransformer, util
|
7 |
+
from app.auth.auth import verify_token
|
8 |
+
|
9 |
+
router = APIRouter()
|
10 |
+
model = SentenceTransformer("clip-ViT-B-32") # for CLIP-like semantic score
|
11 |
+
|
12 |
+
@router.post("/evaluate/bleu", dependencies=[Depends(verify_token)])
|
13 |
+
def compute_bleu(reference: str, candidate: str):
|
14 |
+
ref_tokens = [reference.split()]
|
15 |
+
cand_tokens = candidate.split()
|
16 |
+
score = sentence_bleu(ref_tokens, cand_tokens)
|
17 |
+
return {"metric": "BLEU", "score": score}
|
18 |
+
|
19 |
+
@router.post("/evaluate/clipscore")
|
20 |
+
def compute_clip_score(reference: str, candidate: str):
|
21 |
+
ref_emb = model.encode(reference, convert_to_tensor=True)
|
22 |
+
cand_emb = model.encode(candidate, convert_to_tensor=True)
|
23 |
+
score = util.cos_sim(ref_emb, cand_emb).item()
|
24 |
+
return {"metric": "CLIPScore", "score": score}
|
media-gen-api/app/api/v1/ppt.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/api/v1/ppt.py
|
2 |
+
from fastapi import APIRouter, HTTPException, Query, Depends, Request
|
3 |
+
from pydantic import BaseModel
|
4 |
+
from typing import List
|
5 |
+
from app.services.ppt_service import generate_ppt_file
|
6 |
+
from app.auth.auth import verify_token
|
7 |
+
|
8 |
+
router = APIRouter()
|
9 |
+
|
10 |
+
class Slide(BaseModel):
|
11 |
+
title: str
|
12 |
+
content: str
|
13 |
+
|
14 |
+
class PPTInput(BaseModel):
|
15 |
+
slides: List[Slide]
|
16 |
+
|
17 |
+
@router.post("/generate", dependencies=[Depends(verify_token)])
|
18 |
+
def generate_ppt(payload: PPTInput):
|
19 |
+
filename = generate_ppt_file([slide.dict() for slide in payload.slides])
|
20 |
+
return {
|
21 |
+
"message": "PPT generated successfully",
|
22 |
+
"filename": filename,
|
23 |
+
"download_url": f"/api/v1/download?file_path=generated/ppt/{filename}"
|
24 |
+
}
|
media-gen-api/app/api/v1/utils.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/api/v1/utils.py
|
2 |
+
from fastapi.responses import FileResponse
|
3 |
+
|
4 |
+
def download_file(file_path: str):
|
5 |
+
return FileResponse(
|
6 |
+
path=file_path,
|
7 |
+
filename=file_path.split("/")[-1],
|
8 |
+
media_type="application/octet-stream"
|
9 |
+
)
|
media-gen-api/app/api/v1/video.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, HTTPException, Query, Depends, Request
|
2 |
+
from pydantic import BaseModel
|
3 |
+
from app.services.video_service import generate_video_file
|
4 |
+
from app.auth.auth import verify_token
|
5 |
+
|
6 |
+
|
7 |
+
router = APIRouter()
|
8 |
+
|
9 |
+
class VideoInput(BaseModel):
|
10 |
+
script: str
|
11 |
+
duration: int = 10
|
12 |
+
|
13 |
+
@router.post("/generate", dependencies=[Depends(verify_token)]) # ✅ FIX: POST, not GET
|
14 |
+
def generate_video(payload: VideoInput):
|
15 |
+
filename = generate_video_file(payload.script, payload.duration)
|
16 |
+
return {
|
17 |
+
"message": "Video generated successfully",
|
18 |
+
"filename": filename,
|
19 |
+
"download_url": f"/api/v1/download?file_path=generated/video/{filename}"
|
20 |
+
}
|
media-gen-api/app/auth/auth.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import Request, HTTPException, status
|
2 |
+
from fastapi import Depends, HTTPException, status
|
3 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
4 |
+
|
5 |
+
|
6 |
+
# Replace with a more secure method later (e.g., from .env)
|
7 |
+
API_TOKEN = "my_secure_token_123" # we can generate a random one using uuid or secrets
|
8 |
+
|
9 |
+
def verify_token(request: Request):
|
10 |
+
token = request.headers.get("Authorization")
|
11 |
+
if not token or token.replace("Bearer ", "") != API_TOKEN:
|
12 |
+
raise HTTPException(
|
13 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
14 |
+
detail="Invalid or missing token",
|
15 |
+
)
|
16 |
+
return request
|
17 |
+
|
18 |
+
security = HTTPBearer()
|
19 |
+
|
20 |
+
VALID_TOKENS = {"token123", "mysecuretoken"} # Your valid tokens
|
21 |
+
|
22 |
+
def auth_required(
|
23 |
+
credentials: HTTPAuthorizationCredentials = Depends(security),
|
24 |
+
):
|
25 |
+
token = credentials.credentials
|
26 |
+
if token not in VALID_TOKENS:
|
27 |
+
raise HTTPException(
|
28 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
29 |
+
detail="Invalid or missing token"
|
30 |
+
)
|
31 |
+
return token
|
32 |
+
|
media-gen-api/app/core/config.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
class Settings:
|
4 |
+
API_KEY: str = os.getenv("API_KEY", "dummy-api-key")
|
5 |
+
|
6 |
+
settings = Settings()
|
media-gen-api/app/db.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import create_engine
|
2 |
+
from sqlalchemy.orm import sessionmaker
|
3 |
+
|
4 |
+
DATABASE_URL = "sqlite:///./media_gen.db"
|
5 |
+
|
6 |
+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
7 |
+
SessionLocal = sessionmaker(bind=engine, autoflush=False)
|
media-gen-api/app/main.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/main.py
|
2 |
+
from fastapi import FastAPI
|
3 |
+
from app.api.v1 import audio, video, image, ppt, metrics, download
|
4 |
+
import logging
|
5 |
+
import os
|
6 |
+
import sentry_sdk
|
7 |
+
from fastapi import FastAPI, Depends
|
8 |
+
from fastapi.security import HTTPBearer
|
9 |
+
from app.api.v1.audio import router as audio_router
|
10 |
+
from app.api.v1.video import router as video_router
|
11 |
+
from app.api.v1.image import router as image_router
|
12 |
+
from app.api.v1.ppt import router as ppt_router
|
13 |
+
from app.api.v1.metrics import router as metrics_router
|
14 |
+
from app.api.v1.download import router as download_router
|
15 |
+
|
16 |
+
from fastapi.openapi.utils import get_openapi
|
17 |
+
from fastapi.security import HTTPBearer
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
# Create logs directory if it doesn't exist
|
22 |
+
os.makedirs("logs", exist_ok=True)
|
23 |
+
|
24 |
+
app = FastAPI(
|
25 |
+
title="Media Generator API",
|
26 |
+
description="Generate audio, video, image, and PPT from text",
|
27 |
+
version="1.0.0"
|
28 |
+
)
|
29 |
+
|
30 |
+
app.include_router(audio_router, prefix="/api/v1/audio", tags=["Audio"])
|
31 |
+
app.include_router(video_router, prefix="/api/v1/video", tags=["Video"])
|
32 |
+
app.include_router(image_router, prefix="/api/v1/image", tags=["Image"])
|
33 |
+
app.include_router(ppt_router, prefix="/api/v1/ppt", tags=["PPT"])
|
34 |
+
app.include_router(metrics_router, prefix="/api/v1/metrics", tags=["Metrics"])
|
35 |
+
app.include_router(download_router, prefix="/api/v1/download", tags=["Download"])
|
36 |
+
|
37 |
+
logging.basicConfig(
|
38 |
+
level=logging.INFO,
|
39 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
40 |
+
handlers=[logging.FileHandler("logs/app.log"), logging.StreamHandler()]
|
41 |
+
)
|
42 |
+
|
43 |
+
sentry_sdk.init(dsn="https://a1d84892719d25dca7c04804931d2e82@o4509752680775680.ingest.de.sentry.io/4509752685166672", traces_sample_rate=1.0)
|
44 |
+
|
45 |
+
|
46 |
+
security = HTTPBearer()
|
47 |
+
|
48 |
+
|
49 |
+
def custom_openapi():
|
50 |
+
if app.openapi_schema:
|
51 |
+
return app.openapi_schema
|
52 |
+
openapi_schema = get_openapi(
|
53 |
+
title="Media Generator API",
|
54 |
+
version="1.0.0",
|
55 |
+
description="Generate audio, video, image, and PPT from text",
|
56 |
+
routes=app.routes,
|
57 |
+
)
|
58 |
+
openapi_schema["components"]["securitySchemes"] = {
|
59 |
+
"HTTPBearer": {
|
60 |
+
"type": "http",
|
61 |
+
"scheme": "bearer"
|
62 |
+
}
|
63 |
+
}
|
64 |
+
for path in openapi_schema["paths"].values():
|
65 |
+
for method in path.values():
|
66 |
+
method["security"] = [{"HTTPBearer": []}]
|
67 |
+
app.openapi_schema = openapi_schema
|
68 |
+
return app.openapi_schema
|
69 |
+
|
70 |
+
app.openapi = custom_openapi
|
media-gen-api/app/models.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import Column, Integer, String, DateTime
|
2 |
+
from sqlalchemy.ext.declarative import declarative_base
|
3 |
+
from datetime import datetime
|
4 |
+
|
5 |
+
Base = declarative_base()
|
6 |
+
|
7 |
+
class MediaGeneration(Base):
|
8 |
+
__tablename__ = "media_generations"
|
9 |
+
|
10 |
+
id = Column(Integer, primary_key=True, index=True)
|
11 |
+
media_type = Column(String)
|
12 |
+
prompt = Column(String)
|
13 |
+
file_path = Column(String)
|
14 |
+
timestamp = Column(DateTime, default=datetime.utcnow)
|
media-gen-api/app/services/audio_service.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/services/audio_service.py
|
2 |
+
from gtts import gTTS
|
3 |
+
import os
|
4 |
+
from datetime import datetime
|
5 |
+
from app.db import SessionLocal
|
6 |
+
from app.models import MediaGeneration
|
7 |
+
import logging
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
def generate_audio_file(text: str, voice: str = "default", language: str = "en") -> str:
|
11 |
+
try:
|
12 |
+
tts = gTTS(text=text, lang=language, slow=False)
|
13 |
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
14 |
+
filename = f"audio_{timestamp}.mp3"
|
15 |
+
output_dir = "generated/audio"
|
16 |
+
os.makedirs(output_dir, exist_ok=True)
|
17 |
+
file_path = os.path.join(output_dir, filename)
|
18 |
+
tts.save(file_path)
|
19 |
+
logger.info(f"Generated Audio: {filename}")
|
20 |
+
return file_path
|
21 |
+
except:
|
22 |
+
logger.error(f"Audio Generation Failed: {str(e)}")
|
23 |
+
raise
|
24 |
+
|
25 |
+
|
26 |
+
from app.db import SessionLocal
|
27 |
+
from app.models import MediaGeneration
|
28 |
+
|
29 |
+
def save_metadata(media_type, prompt, file_path):
|
30 |
+
db = SessionLocal()
|
31 |
+
record = MediaGeneration(
|
32 |
+
media_type=media_type,
|
33 |
+
prompt=prompt,
|
34 |
+
file_path=file_path,
|
35 |
+
)
|
36 |
+
db.add(record)
|
37 |
+
db.commit()
|
38 |
+
db.close()
|
media-gen-api/app/services/image_service.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/services/image_service.py
|
2 |
+
import os
|
3 |
+
from datetime import datetime
|
4 |
+
from app.db import SessionLocal
|
5 |
+
from app.models import MediaGeneration
|
6 |
+
import logging
|
7 |
+
logger = logging.getLogger(__name__)
|
8 |
+
|
9 |
+
|
10 |
+
def generate_image_file(prompt: str, style: str = "default") -> str:
|
11 |
+
try:
|
12 |
+
# Simulate saving a generated image file
|
13 |
+
filename = f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
14 |
+
folder = "generated/image"
|
15 |
+
os.makedirs(folder, exist_ok=True)
|
16 |
+
|
17 |
+
# Placeholder: Simulate image generation by writing prompt text to a file
|
18 |
+
with open(os.path.join(folder, filename), "w") as f:
|
19 |
+
f.write(f"Prompt: {prompt}\nStyle: {style}")
|
20 |
+
logger.info(f"Generated Image: {filename}")
|
21 |
+
return filename
|
22 |
+
except:
|
23 |
+
logger.error(f"Image Geneartion failed: {str(e)}")
|
24 |
+
raise
|
25 |
+
|
26 |
+
from app.db import SessionLocal
|
27 |
+
from app.models import MediaGeneration
|
28 |
+
|
29 |
+
def save_metadata(media_type, prompt, file_path):
|
30 |
+
db = SessionLocal()
|
31 |
+
record = MediaGeneration(
|
32 |
+
media_type=media_type,
|
33 |
+
prompt=prompt,
|
34 |
+
file_path=file_path,
|
35 |
+
)
|
36 |
+
db.add(record)
|
37 |
+
db.commit()
|
38 |
+
db.close()
|
media-gen-api/app/services/ppt_service.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/services/ppt_service.py
|
2 |
+
import os
|
3 |
+
from datetime import datetime
|
4 |
+
from app.db import SessionLocal
|
5 |
+
from app.models import MediaGeneration
|
6 |
+
import logging
|
7 |
+
logger = logging.getLogger(__name__)
|
8 |
+
|
9 |
+
def generate_ppt_file(slides: list[dict]) -> str:
|
10 |
+
try:
|
11 |
+
filename = f"ppt_{datetime.now().strftime('%Y%m%d_%H%M%S')}.ppt"
|
12 |
+
folder = "generated/ppt"
|
13 |
+
os.makedirs(folder, exist_ok=True)
|
14 |
+
|
15 |
+
with open(os.path.join(folder, filename), "w") as f:
|
16 |
+
for i, slide in enumerate(slides, 1):
|
17 |
+
f.write(f"Slide {i}:\nTitle: {slide['title']}\nContent: {slide['content']}\n\n")
|
18 |
+
logger.info(f"Generated PPT: {filename}")
|
19 |
+
return filename
|
20 |
+
except:
|
21 |
+
logger.error(f"PPT Generation failed: {str(e)}")
|
22 |
+
raise
|
23 |
+
|
24 |
+
|
25 |
+
def save_metadata(media_type, prompt, file_path):
|
26 |
+
db = SessionLocal()
|
27 |
+
record = MediaGeneration(
|
28 |
+
media_type=media_type,
|
29 |
+
prompt=prompt,
|
30 |
+
file_path=file_path,
|
31 |
+
)
|
32 |
+
db.add(record)
|
33 |
+
db.commit()
|
34 |
+
db.close()
|
media-gen-api/app/services/video_service.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/services/video_service.py
|
2 |
+
import os
|
3 |
+
from datetime import datetime
|
4 |
+
from app.db import SessionLocal
|
5 |
+
from app.models import MediaGeneration
|
6 |
+
import logging
|
7 |
+
logger = logging.getLogger(__name__)
|
8 |
+
|
9 |
+
def generate_video_file(script: str, duration: int = 10) -> str:
|
10 |
+
try:
|
11 |
+
# Simulate saving a generated video file
|
12 |
+
filename = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
|
13 |
+
folder = "generated/video"
|
14 |
+
os.makedirs(folder, exist_ok=True)
|
15 |
+
|
16 |
+
# Placeholder: Simulate video generation by writing script info to a file
|
17 |
+
with open(os.path.join(folder, filename), "w") as f:
|
18 |
+
f.write(f"Script: {script}\nDuration: {duration} seconds")
|
19 |
+
logger.info(f"Generated Video: {filename}")
|
20 |
+
return filename
|
21 |
+
except:
|
22 |
+
logger.error(f"Video generation failed: {str(e)}")
|
23 |
+
raise
|
24 |
+
|
25 |
+
|
26 |
+
def save_metadata(media_type, prompt, file_path):
|
27 |
+
db = SessionLocal()
|
28 |
+
record = MediaGeneration(
|
29 |
+
media_type=media_type,
|
30 |
+
prompt=prompt,
|
31 |
+
file_path=file_path,
|
32 |
+
)
|
33 |
+
db.add(record)
|
34 |
+
db.commit()
|
35 |
+
db.close()
|
media-gen-api/assets/default.jpg
ADDED
![]() |
media-gen-api/assets/logo_watermark.png
ADDED
![]() |
media-gen-api/backend/media_gen.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ✅ Updated media_gen.py with file logging + UI debug toggle
|
2 |
+
import os
|
3 |
+
import re
|
4 |
+
import logging
|
5 |
+
import streamlit as st
|
6 |
+
import requests
|
7 |
+
from PIL import Image, UnidentifiedImageError
|
8 |
+
from io import BytesIO
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from moviepy.editor import ImageClip, AudioFileClip
|
11 |
+
from elevenlabs import generate, save, set_api_key
|
12 |
+
from googletrans import Translator
|
13 |
+
from PIL import ImageEnhance, Image
|
14 |
+
import tempfile
|
15 |
+
|
16 |
+
# Load env vars
|
17 |
+
load_dotenv()
|
18 |
+
|
19 |
+
# Logging setup
|
20 |
+
logging.basicConfig(
|
21 |
+
filename="app.log",
|
22 |
+
level=logging.INFO,
|
23 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
24 |
+
)
|
25 |
+
|
26 |
+
# Constants
|
27 |
+
OUTPUT_DIR = "outputs"
|
28 |
+
DEFAULT_IMAGE = "assets/fallback.jpg"
|
29 |
+
WATERMARK_PATH = "assets/logo_watermark.png"
|
30 |
+
UNSPLASH_ACCESS_KEY = os.getenv("UNSPLASH_ACCESS_KEY")
|
31 |
+
|
32 |
+
os.makedirs("outputs/audio", exist_ok=True)
|
33 |
+
os.makedirs("outputs/images", exist_ok=True)
|
34 |
+
os.makedirs("outputs/videos", exist_ok=True)
|
35 |
+
|
36 |
+
def translate_text(text, target_lang):
|
37 |
+
return Translator().translate(text, dest=target_lang).text
|
38 |
+
|
39 |
+
def sanitize_filename(text):
|
40 |
+
return re.sub(r'\W+', '_', text).lower()[:50]
|
41 |
+
|
42 |
+
def apply_watermark(image_path, watermark_path=WATERMARK_PATH):
|
43 |
+
try:
|
44 |
+
base = Image.open(image_path).convert("RGBA")
|
45 |
+
watermark = Image.open(watermark_path).convert("RGBA").resize((100, 100))
|
46 |
+
base.paste(watermark, (base.width - 110, base.height - 110), watermark)
|
47 |
+
base.convert("RGB").save(image_path)
|
48 |
+
except Exception as e:
|
49 |
+
logging.error(f"Watermarking failed: {e}")
|
50 |
+
st.write(f"❌ Watermarking failed: {e}")
|
51 |
+
|
52 |
+
def use_fallback_image(prompt, add_watermark=False):
|
53 |
+
try:
|
54 |
+
fallback_path = DEFAULT_IMAGE
|
55 |
+
output_path = f"outputs/images/{sanitize_filename(prompt)}.jpg"
|
56 |
+
with Image.open(fallback_path) as img:
|
57 |
+
img.save(output_path)
|
58 |
+
if add_watermark:
|
59 |
+
apply_watermark(output_path)
|
60 |
+
return output_path
|
61 |
+
except UnidentifiedImageError:
|
62 |
+
logging.error("Could not open fallback image.")
|
63 |
+
st.write("❌ Could not open fallback image.")
|
64 |
+
return None
|
65 |
+
|
66 |
+
def generate_gtts_fallback(prompt, output_path, lang="en", debug_mode=False):
|
67 |
+
try:
|
68 |
+
from gtts import gTTS
|
69 |
+
tts = gTTS(text=prompt, lang=lang)
|
70 |
+
tts.save(output_path)
|
71 |
+
logging.info(f"gTTS fallback audio saved to {output_path}")
|
72 |
+
if debug_mode:
|
73 |
+
st.write(f"✅ Fallback audio (gTTS) saved to {output_path}")
|
74 |
+
return output_path
|
75 |
+
except Exception as e:
|
76 |
+
logging.error(f"gTTS fallback failed: {e}")
|
77 |
+
st.write(f"❌ gTTS fallback failed: {str(e)}")
|
78 |
+
return None
|
79 |
+
|
80 |
+
|
81 |
+
def generate_image(prompt, file_tag, add_watermark=False, dark_mode=False, debug_mode=False):
|
82 |
+
try:
|
83 |
+
# Enhance prompt if dark mode is enabled
|
84 |
+
if dark_mode:
|
85 |
+
prompt += " at night, dark theme, low light, moody lighting"
|
86 |
+
|
87 |
+
url = f"https://api.unsplash.com/photos/random?query={requests.utils.quote(prompt)}&client_id={UNSPLASH_ACCESS_KEY}"
|
88 |
+
response = requests.get(url, timeout=10)
|
89 |
+
response.raise_for_status()
|
90 |
+
image_url = response.json()["urls"]["regular"]
|
91 |
+
image_response = requests.get(image_url, timeout=10)
|
92 |
+
image_response.raise_for_status()
|
93 |
+
|
94 |
+
output_path = f"outputs/images/{sanitize_filename(prompt)}.jpg"
|
95 |
+
img = Image.open(BytesIO(image_response.content))
|
96 |
+
img.convert("RGB").save(output_path)
|
97 |
+
|
98 |
+
if add_watermark:
|
99 |
+
apply_watermark(output_path)
|
100 |
+
|
101 |
+
return output_path
|
102 |
+
|
103 |
+
except Exception as e:
|
104 |
+
logging.error(f"Image generation failed: {e}")
|
105 |
+
st.write("🔁 Unsplash failed. Using fallback.")
|
106 |
+
st.write(f"❌ Image generation failed: {e}")
|
107 |
+
return use_fallback_image(prompt, add_watermark=add_watermark)
|
108 |
+
|
109 |
+
|
110 |
+
# ✅ Updated generate_audio with proper language handling
|
111 |
+
|
112 |
+
def generate_audio(prompt, output_path, debug_mode=False, lang="en"):
|
113 |
+
try:
|
114 |
+
api_key = os.getenv("ELEVEN_API_KEY") or st.secrets.get("ELEVEN_API_KEY", None)
|
115 |
+
|
116 |
+
# Use gTTS for non-English languages
|
117 |
+
if lang != "en":
|
118 |
+
if debug_mode:
|
119 |
+
st.write(f"🌐 Non-English language selected: {lang}. Using gTTS.")
|
120 |
+
return generate_gtts_fallback(prompt, output_path, lang=lang, debug_mode=debug_mode)
|
121 |
+
|
122 |
+
if api_key:
|
123 |
+
if debug_mode:
|
124 |
+
st.write(f"✅ ELEVEN_API_KEY loaded: {api_key[:4]}...****")
|
125 |
+
|
126 |
+
set_api_key(api_key)
|
127 |
+
if debug_mode:
|
128 |
+
st.write(f"🎧 Generating audio for prompt: {prompt}")
|
129 |
+
|
130 |
+
try:
|
131 |
+
audio = generate(text=prompt, voice="Aria", model="eleven_monolingual_v1")
|
132 |
+
save(audio, output_path)
|
133 |
+
logging.info(f"Audio saved successfully to {output_path}")
|
134 |
+
|
135 |
+
if debug_mode:
|
136 |
+
st.write(f"🔍 File exists after save? {os.path.exists(output_path)}")
|
137 |
+
st.write(f"✅ Audio saved successfully to {output_path}")
|
138 |
+
return output_path
|
139 |
+
|
140 |
+
except Exception as e:
|
141 |
+
logging.warning(f"ElevenLabs failed: {e}")
|
142 |
+
if debug_mode:
|
143 |
+
st.write(f"⚠️ ElevenLabs failed: {str(e)}")
|
144 |
+
st.write("🔁 Falling back to gTTS...")
|
145 |
+
return generate_gtts_fallback(prompt, output_path, lang=lang, debug_mode=debug_mode)
|
146 |
+
|
147 |
+
else:
|
148 |
+
logging.warning("ELEVEN_API_KEY not found")
|
149 |
+
if debug_mode:
|
150 |
+
st.write("❌ ELEVEN_API_KEY not found. Falling back to gTTS.")
|
151 |
+
return generate_gtts_fallback(prompt, output_path, lang=lang, debug_mode=debug_mode)
|
152 |
+
|
153 |
+
except Exception as e:
|
154 |
+
logging.error(f"Exception during audio generation setup: {e}")
|
155 |
+
if debug_mode:
|
156 |
+
st.write(f"❌ Exception during audio generation setup: {str(e)}")
|
157 |
+
st.write("🔁 Falling back to gTTS...")
|
158 |
+
return generate_gtts_fallback(prompt, output_path, lang=lang, debug_mode=debug_mode)
|
159 |
+
|
160 |
+
|
161 |
+
def generate_video(prompt, image_path, audio_path, output_path, add_watermark=False, dark_mode=False):
|
162 |
+
try:
|
163 |
+
# If dark_mode, darken the image temporarily
|
164 |
+
if dark_mode:
|
165 |
+
with Image.open(image_path) as img:
|
166 |
+
enhancer = ImageEnhance.Brightness(img)
|
167 |
+
darker_img = enhancer.enhance(0.5) # Reduce brightness to 50%
|
168 |
+
|
169 |
+
# Save to a temporary file
|
170 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
|
171 |
+
temp_image_path = tmp.name
|
172 |
+
darker_img.save(temp_image_path)
|
173 |
+
image_path = temp_image_path
|
174 |
+
|
175 |
+
audio_clip = AudioFileClip(audio_path)
|
176 |
+
image_clip = ImageClip(image_path).set_duration(audio_clip.duration).resize(height=720)
|
177 |
+
video = image_clip.set_audio(audio_clip)
|
178 |
+
|
179 |
+
output_path = f"outputs/videos/{sanitize_filename(prompt)}.mp4"
|
180 |
+
video.write_videofile(output_path, fps=24, codec="libx264", audio_codec="aac", verbose=False, logger=None)
|
181 |
+
return output_path
|
182 |
+
|
183 |
+
except Exception as e:
|
184 |
+
logging.error(f"Video generation failed: {e}")
|
185 |
+
st.write(f"❌ Video generation failed: {e}")
|
186 |
+
return None
|
187 |
+
|
media-gen-api/docs/index.html
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
6 |
+
<meta name="description" content="OSN Media Generator - Built by O.S.Narayana. Create media content using AI with no ads or in-app purchases."/>
|
7 |
+
<title>OSN Media Generator</title>
|
8 |
+
<style>
|
9 |
+
body {
|
10 |
+
font-family: Arial, sans-serif;
|
11 |
+
background-color: #f3f4f6;
|
12 |
+
color: #111827;
|
13 |
+
text-align: center;
|
14 |
+
padding: 50px 20px;
|
15 |
+
}
|
16 |
+
.container {
|
17 |
+
max-width: 600px;
|
18 |
+
margin: auto;
|
19 |
+
background: #ffffff;
|
20 |
+
padding: 30px;
|
21 |
+
border-radius: 12px;
|
22 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
23 |
+
}
|
24 |
+
h1 {
|
25 |
+
color: #1f2937;
|
26 |
+
}
|
27 |
+
a {
|
28 |
+
color: #2563eb;
|
29 |
+
text-decoration: none;
|
30 |
+
}
|
31 |
+
a:hover {
|
32 |
+
text-decoration: underline;
|
33 |
+
}
|
34 |
+
.footer {
|
35 |
+
margin-top: 40px;
|
36 |
+
font-size: 0.9em;
|
37 |
+
color: #6b7280;
|
38 |
+
}
|
39 |
+
</style>
|
40 |
+
</head>
|
41 |
+
<body>
|
42 |
+
<div class="container">
|
43 |
+
<h1>🎮 OSN Media Generator</h1>
|
44 |
+
<p>
|
45 |
+
Welcome to the official homepage of <strong>OSN Media Generator</strong>, created by <strong>O.S.Narayana</strong>.
|
46 |
+
</p>
|
47 |
+
<p>
|
48 |
+
This app helps you generate audio, images, and videos using AI tools like ElevenLabs, Unsplash, and Streamlit.
|
49 |
+
</p>
|
50 |
+
<p>
|
51 |
+
👉 Try the app now: <br>
|
52 |
+
<a href="https://osnarayana-media-generator.streamlit.app/" target="_blank">Launch OSN Media Generator</a>
|
53 |
+
</p>
|
54 |
+
<p>
|
55 |
+
📄 View our <a href="privacy.html" target="_blank">Privacy Policy</a>
|
56 |
+
</p>
|
57 |
+
<div class="footer">
|
58 |
+
© 2025 OSN Media. Built with 💗 by O.S.Narayana.
|
59 |
+
</div>
|
60 |
+
</div>
|
61 |
+
</body>
|
62 |
+
</html>
|
media-gen-api/docs/privacy.html
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Privacy Policy - OSN Media Generator</title>
|
7 |
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
8 |
+
<meta http-equiv="Pragma" content="no-cache" />
|
9 |
+
<meta http-equiv="Expires" content="0" />
|
10 |
+
<style>
|
11 |
+
body {
|
12 |
+
font-family: Arial, sans-serif;
|
13 |
+
margin: 2rem;
|
14 |
+
padding: 0;
|
15 |
+
background-color: #f9f9f9;
|
16 |
+
color: #333;
|
17 |
+
}
|
18 |
+
h1, h2, h3 { color: #222; }
|
19 |
+
a { color: #1a73e8; text-decoration: none; }
|
20 |
+
a:hover { text-decoration: underline; }
|
21 |
+
code { background-color: #eee; padding: 2px 4px; border-radius: 4px; }
|
22 |
+
|
23 |
+
</style>
|
24 |
+
</head>
|
25 |
+
<body>
|
26 |
+
<h1>🔐 Privacy Policy for OSN Media Generator</h1>
|
27 |
+
|
28 |
+
<p>Effective Date: July 19, 2025</p>
|
29 |
+
|
30 |
+
<p>Your privacy is important to us. This app is built to prioritize user data security and transparency.</p>
|
31 |
+
|
32 |
+
<h2>1. What We Collect</h2>
|
33 |
+
|
34 |
+
<ul>
|
35 |
+
<li><strong>No personal data</strong> is collected, stored, or shared by this application.</li>
|
36 |
+
<li>All prompts, audio, and media are <strong>processed locally</strong> or via <strong>user-provided API keys</strong> to external services (e.g., ElevenLabs, Unsplash).</li>
|
37 |
+
</ul>
|
38 |
+
|
39 |
+
<h2>2. API Usage</h2>
|
40 |
+
|
41 |
+
<ul>
|
42 |
+
<li>If you use external services (like ElevenLabs or Unsplash), you are subject to their respective <a href="https://www.elevenlabs.io/terms">Terms of Service</a> and <a href="https://www.elevenlabs.io/privacy">Privacy Policies</a>.</li>
|
43 |
+
<li>Your API keys are stored <strong>locally</strong> in your environment file (<code>.env</code>) and never uploaded.</li>
|
44 |
+
</ul>
|
45 |
+
|
46 |
+
<h2>3. Data Storage</h2>
|
47 |
+
|
48 |
+
<ul>
|
49 |
+
<li>Generated images, audio, and videos are stored <strong>only on your local machine</strong> under the <code>outputs/</code> directory.</li>
|
50 |
+
<li>You can delete any generated content at your discretion.</li>
|
51 |
+
</ul>
|
52 |
+
|
53 |
+
<h2>4. Analytics & Ads</h2>
|
54 |
+
|
55 |
+
<ul>
|
56 |
+
<li>This app contains <strong>no ads</strong>, <strong>no tracking</strong>, and <strong>no analytics</strong>.</li>
|
57 |
+
</ul>
|
58 |
+
|
59 |
+
<h2>5. Changes to Policy</h2>
|
60 |
+
|
61 |
+
<p>Any updates to this policy will be reflected in this file on the <a href="https://github.com/sivaogeti/osnarayana-media-generator">GitHub repository</a>.</p>
|
62 |
+
|
63 |
+
<h2>6. Contact</h2>
|
64 |
+
|
65 |
+
<p>For questions or issues, contact: <strong>[email protected]</strong></p>
|
66 |
+
|
67 |
+
</body>
|
68 |
+
</html>
|
media-gen-api/nixpacks.toml
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[phases.setup]
|
2 |
+
nixpkgs = [
|
3 |
+
"python311",
|
4 |
+
"zlib",
|
5 |
+
"libjpeg",
|
6 |
+
"gcc",
|
7 |
+
"pkg-config"
|
8 |
+
]
|
9 |
+
|
10 |
+
[phases.build]
|
11 |
+
cmds = [
|
12 |
+
"python -m venv /opt/venv",
|
13 |
+
". /opt/venv/bin/activate",
|
14 |
+
"pip install --upgrade pip",
|
15 |
+
"pip install -r requirements.txt"
|
16 |
+
]
|
17 |
+
|
18 |
+
[start]
|
19 |
+
cmd = "streamlit run app.py --server.port 8000 --server.address 0.0.0.0"
|
media-gen-api/openapi.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"openapi":"3.1.0","info":{"title":"Media Generator API","description":"Generate audio, video, image, and PPT from text","version":"1.0.0"},"paths":{"/api/v1/audio/generate":{"post":{"tags":["Audio"],"summary":"Generate Audio Endpoint","operationId":"generate_audio_endpoint_api_v1_audio_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/video/generate":{"post":{"tags":["Video"],"summary":"Generate Video","operationId":"generate_video_api_v1_video_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/image/generate":{"post":{"tags":["Image"],"summary":"Generate Image","operationId":"generate_image_api_v1_image_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/ppt/generate":{"post":{"tags":["PPT"],"summary":"Generate Ppt","operationId":"generate_ppt_api_v1_ppt_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PPTInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/metrics/evaluate/bleu":{"post":{"tags":["Metrics"],"summary":"Compute Bleu","operationId":"compute_bleu_api_v1_metrics_evaluate_bleu_post","parameters":[{"name":"reference","in":"query","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"candidate","in":"query","required":true,"schema":{"type":"string","title":"Candidate"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/metrics/evaluate/clipscore":{"post":{"tags":["Metrics"],"summary":"Compute Clip Score","operationId":"compute_clip_score_api_v1_metrics_evaluate_clipscore_post","parameters":[{"name":"reference","in":"query","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"candidate","in":"query","required":true,"schema":{"type":"string","title":"Candidate"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/download/":{"get":{"tags":["Download"],"summary":"Download File","operationId":"download_file_api_v1_download__get","parameters":[{"name":"file_path","in":"query","required":true,"schema":{"type":"string","description":"Relative path from project root","title":"File Path"},"description":"Relative path from project root"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"AudioInput":{"properties":{"text":{"type":"string","title":"Text"},"voice":{"type":"string","title":"Voice","default":"default"},"language":{"type":"string","title":"Language","default":"en"}},"type":"object","required":["text"],"title":"AudioInput"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageInput":{"properties":{"prompt":{"type":"string","title":"Prompt"},"style":{"type":"string","title":"Style","default":"default"}},"type":"object","required":["prompt"],"title":"ImageInput"},"PPTInput":{"properties":{"slides":{"items":{"$ref":"#/components/schemas/Slide"},"type":"array","title":"Slides"}},"type":"object","required":["slides"],"title":"PPTInput"},"Slide":{"properties":{"title":{"type":"string","title":"Title"},"content":{"type":"string","title":"Content"}},"type":"object","required":["title","content"],"title":"Slide"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VideoInput":{"properties":{"script":{"type":"string","title":"Script"},"duration":{"type":"integer","title":"Duration","default":10}},"type":"object","required":["script"],"title":"VideoInput"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}
|
media-gen-api/packages.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
ffmpeg
|
2 |
+
libjpeg-dev
|
3 |
+
zlib1g-dev
|
media-gen-api/requirements.txt
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
requests
|
3 |
+
python-dotenv
|
4 |
+
gtts
|
5 |
+
moviepy==1.0.3
|
6 |
+
google-api-python-client
|
7 |
+
google-auth-httplib2
|
8 |
+
google-auth-oauthlib
|
9 |
+
google-auth
|
10 |
+
pillow==9.5.0
|
11 |
+
googletrans==4.0.0-rc1
|
12 |
+
elevenlabs
|
13 |
+
deep_translator
|
14 |
+
gTTS
|
15 |
+
sentry-sdk
|
16 |
+
scikit-learn
|
17 |
+
nltk
|
18 |
+
sentence_transformers
|
19 |
+
sqlalchemy
|
media-gen-api/screenshots/generated_audio_sample.png
ADDED
![]() |
media-gen-api/screenshots/home_page.png
ADDED
![]() |
media-gen-api/scripts/preload_content.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import shutil
|
3 |
+
from datetime import datetime
|
4 |
+
|
5 |
+
SOURCE_DIR = "generated"
|
6 |
+
ARCHIVE_DIR = f"archive_{datetime.now().strftime('%Y%m%d_%H%M')}"
|
7 |
+
|
8 |
+
def archive_all():
|
9 |
+
if not os.path.exists(SOURCE_DIR):
|
10 |
+
print("No content to archive.")
|
11 |
+
return
|
12 |
+
|
13 |
+
shutil.copytree(SOURCE_DIR, ARCHIVE_DIR)
|
14 |
+
print(f"Archived to {ARCHIVE_DIR}")
|
15 |
+
|
16 |
+
if __name__ == "__main__":
|
17 |
+
archive_all()
|
media-gen-api/streamlit_ui.py
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# streamlit_ui.py
|
2 |
+
import streamlit as st
|
3 |
+
import requests
|
4 |
+
import base64
|
5 |
+
|
6 |
+
st.set_page_config(page_title="AI Media Generator", layout="wide")
|
7 |
+
st.title("🎙️🖼️🎞️ AI Media Generator")
|
8 |
+
|
9 |
+
API_BASE = "http://localhost:8000" # change if deployed
|
10 |
+
|
11 |
+
# Helper to play audio/video in Streamlit
|
12 |
+
def render_media(file_bytes, media_type, label):
|
13 |
+
b64 = base64.b64encode(file_bytes).decode()
|
14 |
+
if media_type == "audio":
|
15 |
+
st.audio(f"data:audio/wav;base64,{b64}", format="audio/wav")
|
16 |
+
elif media_type == "video":
|
17 |
+
st.video(f"data:video/mp4;base64,{b64}")
|
18 |
+
elif media_type == "image":
|
19 |
+
st.image(file_bytes, caption=label, use_column_width=True)
|
20 |
+
|
21 |
+
# Sidebar inputs
|
22 |
+
st.sidebar.header("🛠️ Settings")
|
23 |
+
TOKEN = st.sidebar.text_input("🔑 API Token", type="password")
|
24 |
+
HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
|
25 |
+
|
26 |
+
# Tabs
|
27 |
+
tab = st.sidebar.radio("Select Task", ["Text to Audio", "Text to Image", "Text to Video"])
|
28 |
+
|
29 |
+
if tab == "Text to Audio":
|
30 |
+
st.subheader("🎤 Text to Audio")
|
31 |
+
text = st.text_area("Enter text")
|
32 |
+
voice = st.selectbox("Choose voice/language", ["en-US", "hi-IN", "te-IN", "ta-IN"])
|
33 |
+
|
34 |
+
if st.button("🔊 Generate Audio"):
|
35 |
+
with st.spinner("Generating audio..."):
|
36 |
+
r = requests.post(f"{API_BASE}/audio/generate", json={"text": text, "voice": voice}, headers=HEADERS)
|
37 |
+
if r.status_code == 200:
|
38 |
+
render_media(r.content, "audio", "Generated Audio")
|
39 |
+
else:
|
40 |
+
st.error(f"❌ Failed: {r.json().get('detail')}")
|
41 |
+
|
42 |
+
elif tab == "Text to Image":
|
43 |
+
st.subheader("🖼️ Text to Image")
|
44 |
+
prompt = st.text_area("Enter image prompt")
|
45 |
+
model = st.selectbox("Choose model", ["sdxl", "deepfloyd", "kandinsky"])
|
46 |
+
|
47 |
+
if st.button("🧠 Generate Image"):
|
48 |
+
with st.spinner("Generating image..."):
|
49 |
+
r = requests.post(f"{API_BASE}/image/generate", json={"prompt": prompt, "model": model}, headers=HEADERS)
|
50 |
+
if r.status_code == 200:
|
51 |
+
render_media(r.content, "image", "Generated Image")
|
52 |
+
else:
|
53 |
+
st.error(f"❌ Failed: {r.json().get('detail')}")
|
54 |
+
|
55 |
+
elif tab == "Text to Video":
|
56 |
+
st.subheader("🎞️ Text to Video")
|
57 |
+
prompt = st.text_area("Enter video prompt")
|
58 |
+
tone = st.selectbox("Tone", ["formal", "casual", "emotional", "documentary"])
|
59 |
+
domain = st.selectbox("Domain", ["health", "education", "governance", "entertainment"])
|
60 |
+
environment = st.selectbox("Environment", ["urban", "rural", "nature", "futuristic"])
|
61 |
+
|
62 |
+
if st.button("🎬 Generate Video"):
|
63 |
+
with st.spinner("Generating video..."):
|
64 |
+
r = requests.post(
|
65 |
+
f"{API_BASE}/video/generate",
|
66 |
+
json={"prompt": prompt, "tone": tone, "domain": domain, "environment": environment},
|
67 |
+
headers=HEADERS
|
68 |
+
)
|
69 |
+
if r.status_code == 200:
|
70 |
+
render_media(r.content, "video", "Generated Video")
|
71 |
+
else:
|
72 |
+
st.error(f"❌ Failed: {r.json().get('detail')}")
|
73 |
+
|
74 |
+
st.sidebar.markdown("---")
|
75 |
+
st.sidebar.info("Built with ❤️ for AI GovTech Challenge 2025")
|