oneword-ai 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oneword_ai-0.1.0.dist-info/METADATA +237 -0
- oneword_ai-0.1.0.dist-info/RECORD +15 -0
- oneword_ai-0.1.0.dist-info/WHEEL +5 -0
- oneword_ai-0.1.0.dist-info/entry_points.txt +3 -0
- oneword_ai-0.1.0.dist-info/licenses/license.txt +7 -0
- oneword_ai-0.1.0.dist-info/top_level.txt +1 -0
- onewordai/__init__.py +2 -0
- onewordai/api/__init__.py +1 -0
- onewordai/api/main.py +262 -0
- onewordai/cli.py +67 -0
- onewordai/core/__init__.py +4 -0
- onewordai/core/engine.py +368 -0
- onewordai/web/app.js +361 -0
- onewordai/web/index.html +154 -0
- onewordai/web/style.css +485 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oneword-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A minimalist one-word subtitle generator with Neobrutalism UI
|
|
5
|
+
Author-email: Ambrish <ambrishyadav1110@gmail.com>
|
|
6
|
+
License: Copyright 2025 Ambrish
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
13
|
+
Project-URL: Homepage, https://github.com/Ambrishyadav-byte/OnewordAI
|
|
14
|
+
Project-URL: Bug Tracker, https://github.com/Ambrishyadav-byte/OnewordAI/issues
|
|
15
|
+
Keywords: subtitles,ai,whisper,content-creation,video-editing
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: license.txt
|
|
19
|
+
Requires-Dist: openai-whisper>=20231117
|
|
20
|
+
Requires-Dist: torch>=2.0.0
|
|
21
|
+
Requires-Dist: fastapi>=0.100.0
|
|
22
|
+
Requires-Dist: uvicorn[standard]>=0.23.0
|
|
23
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
24
|
+
Requires-Dist: ffmpeg-python>=0.2.0
|
|
25
|
+
Requires-Dist: gradio>=4.0.0
|
|
26
|
+
Requires-Dist: tqdm>=4.65.0
|
|
27
|
+
Requires-Dist: aiofiles>=23.0.0
|
|
28
|
+
Requires-Dist: transformers>=4.30.0
|
|
29
|
+
Requires-Dist: accelerate>=0.20.0
|
|
30
|
+
Requires-Dist: huggingface_hub>=0.16.0
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# π¬ OneWord AI - Subtitle Generator
|
|
34
|
+
|
|
35
|
+
<div align="center">
|
|
36
|
+
|
|
37
|
+
**Generate cinematic one-word subtitles from video/audio using Whisper AI**
|
|
38
|
+
|
|
39
|
+
[](https://www.python.org/)
|
|
40
|
+
[](license.txt)
|
|
41
|
+
[](https://github.com/openai/whisper)
|
|
42
|
+
|
|
43
|
+
Perfect for creators making high-energy reels, shorts, and TikToks!
|
|
44
|
+
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## β¨ Features
|
|
50
|
+
|
|
51
|
+
- π― **Three Subtitle Modes**: One Word, Two Word Punch, Phrase Mode
|
|
52
|
+
- π **Multi-Language**: Auto-detect or specify (English, Hindi, Urdu, Spanish)
|
|
53
|
+
- π€ **Multiple Models**: Medium, Large, and **Hindi2Hinglish** (Oriserve/Prime) π
|
|
54
|
+
- π¦ **Python Package**: Installable via pip with `oneword-cli` and `oneword-web` commands
|
|
55
|
+
- π» **Local CLI**: Robust command-line tool for batch processing
|
|
56
|
+
- π **Web UI**: Beautiful Neobrutalism-styled web interface
|
|
57
|
+
- βοΈ **Cloud Ready**: Works on Google Colab and Hugging Face Spaces
|
|
58
|
+
- π³ **Docker Support**: Containerized deployment
|
|
59
|
+
|
|
60
|
+
## π Quick Start
|
|
61
|
+
|
|
62
|
+
### Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Clone the repository
|
|
66
|
+
git clone https://github.com/Ambrishyadav-byte/OnewordAI.git
|
|
67
|
+
cd OnewordAI
|
|
68
|
+
|
|
69
|
+
# Install as a package
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Prerequisites**: Ensure [FFmpeg](https://ffmpeg.org/) is installed on your system.
|
|
74
|
+
|
|
75
|
+
### Usage
|
|
76
|
+
|
|
77
|
+
#### π₯οΈ CLI (Command Line)
|
|
78
|
+
|
|
79
|
+
See [CLI.md](CLI.md) for full documentation.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Basic usage
|
|
83
|
+
oneword-cli -i video.mp4
|
|
84
|
+
|
|
85
|
+
# Full options
|
|
86
|
+
oneword-cli -i video.mp4 -m medium -lang hi -mode oneword
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### π Web UI
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Start server & open browser
|
|
93
|
+
oneword-web
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Features:
|
|
97
|
+
- Drag & drop file upload
|
|
98
|
+
- Real-time progress tracking
|
|
99
|
+
- Instant SRT download
|
|
100
|
+
- Responsive Neobrutalism design
|
|
101
|
+
|
|
102
|
+
#### βοΈ Google Colab
|
|
103
|
+
|
|
104
|
+
[](https://colab.research.google.com/)
|
|
105
|
+
|
|
106
|
+
1. Open `OneWord_Colab.ipynb` in Colab
|
|
107
|
+
2. Run all cells
|
|
108
|
+
3. Upload your video
|
|
109
|
+
4. Download your SRT!
|
|
110
|
+
|
|
111
|
+
#### π€ Hugging Face Space
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python app_gradio.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or deploy to Hugging Face Spaces for a hosted version!
|
|
118
|
+
|
|
119
|
+
## π Subtitle Modes
|
|
120
|
+
|
|
121
|
+
| Mode | Description | Use Case |
|
|
122
|
+
|------|-------------|----------|
|
|
123
|
+
| **One Word** | Each word = separate subtitle | High-energy, attention-grabbing content |
|
|
124
|
+
| **Two Word Punch** | Groups of 2 words | Punchy, impactful messaging |
|
|
125
|
+
| **Phrase Mode** | Full sentence segments | Traditional subtitle style |
|
|
126
|
+
|
|
127
|
+
## π¨ Web UI Preview
|
|
128
|
+
|
|
129
|
+
The web interface features a stunning **Neobrutalism** design:
|
|
130
|
+
- Bold black borders
|
|
131
|
+
- Vibrant color palette
|
|
132
|
+
- Sharp shadows
|
|
133
|
+
- Grid background pattern
|
|
134
|
+
- Smooth animations
|
|
135
|
+
|
|
136
|
+
## π³ Docker Deployment
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Build image
|
|
140
|
+
docker build -t oneword-ai .
|
|
141
|
+
|
|
142
|
+
# Run container
|
|
143
|
+
docker run -p 8000:8000 oneword-ai
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## π Project Structure
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
minimalist-one-word-subtitle-generator/
|
|
150
|
+
βββ onewordai/ # Source code (package)
|
|
151
|
+
β βββ core/
|
|
152
|
+
β β βββ __init__.py
|
|
153
|
+
β β βββ engine.py # Core subtitle generation logic
|
|
154
|
+
β βββ api/
|
|
155
|
+
β β βββ __init__.py
|
|
156
|
+
β β βββ main.py # FastAPI backend
|
|
157
|
+
β βββ web/
|
|
158
|
+
β βββ index.html # Web UI
|
|
159
|
+
β βββ style.css # Neobrutalism styles
|
|
160
|
+
β βββ app.js # Frontend logic
|
|
161
|
+
βββ cli.py # CLI interface
|
|
162
|
+
βββ app_gradio.py # Gradio app for HF Spaces
|
|
163
|
+
βββ OneWord_Colab.ipynb # Google Colab notebook
|
|
164
|
+
βββ Dockerfile # Docker configuration
|
|
165
|
+
βββ requirements.txt # Python dependencies
|
|
166
|
+
βββ README.md
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## π οΈ Development
|
|
170
|
+
|
|
171
|
+
### API Endpoints
|
|
172
|
+
|
|
173
|
+
- `POST /upload` - Upload video/audio file
|
|
174
|
+
- `POST /process` - Start transcription job
|
|
175
|
+
- `GET /status/{job_id}` - Check job progress
|
|
176
|
+
- `GET /download/{job_id}` - Download generated SRT
|
|
177
|
+
|
|
178
|
+
### Requirements
|
|
179
|
+
|
|
180
|
+
- Python 3.8+
|
|
181
|
+
- FFmpeg
|
|
182
|
+
- PyTorch
|
|
183
|
+
- OpenAI Whisper
|
|
184
|
+
- FastAPI (for web server)
|
|
185
|
+
- Gradio (for HF Spaces)
|
|
186
|
+
|
|
187
|
+
## π‘ Tips for Creators
|
|
188
|
+
|
|
189
|
+
### Video Editing Workflow
|
|
190
|
+
|
|
191
|
+
1. Generate SRT using OneWord AI
|
|
192
|
+
2. Import into your editor:
|
|
193
|
+
- **CapCut**: Text β Local Captions β Upload
|
|
194
|
+
- **VN Editor**: Text β SRT β Import
|
|
195
|
+
- **Premiere Pro**: File β Import β Captions
|
|
196
|
+
3. Apply animations (Pop, Spring, Bounce)
|
|
197
|
+
4. Customize colors and fonts
|
|
198
|
+
|
|
199
|
+
### Best Practices
|
|
200
|
+
|
|
201
|
+
- Use **Tiny** model for quick drafts
|
|
202
|
+
- Use **Base** model for production (best balance)
|
|
203
|
+
- Use **Small** model for technical/complex content
|
|
204
|
+
- **One Word** mode works best for 30-60 sec reels
|
|
205
|
+
- Enable language selection for multilingual content
|
|
206
|
+
|
|
207
|
+
## π€ Contributing
|
|
208
|
+
|
|
209
|
+
Contributions welcome! Open an issue or submit a PR.
|
|
210
|
+
|
|
211
|
+
Ideas for improvements:
|
|
212
|
+
- Auto-capitalization for emphasis
|
|
213
|
+
- Color-coded keywords
|
|
214
|
+
- Export with burned-in subtitles
|
|
215
|
+
- Batch processing multiple files
|
|
216
|
+
|
|
217
|
+
## π License
|
|
218
|
+
|
|
219
|
+
MIT License - see [license.txt](license.txt)
|
|
220
|
+
|
|
221
|
+
## π€ Credits
|
|
222
|
+
|
|
223
|
+
- [OpenAI Whisper](https://github.com/openai/whisper) - Speech recognition
|
|
224
|
+
- [FastAPI](https://fastapi.tiangolo.com/) - Backend framework
|
|
225
|
+
- [Gradio](https://gradio.app/) - ML web interfaces
|
|
226
|
+
|
|
227
|
+
Built with β€οΈ by [Ambrish](https://github.com/ambrish-yadav)
|
|
228
|
+
|
|
229
|
+
Follow for updates: [@ambrish.yadav.1](https://instagram.com/ambrish.yadav.1)
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
<div align="center">
|
|
234
|
+
|
|
235
|
+
β Star this repo if you find it useful!
|
|
236
|
+
|
|
237
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
oneword_ai-0.1.0.dist-info/licenses/license.txt,sha256=ZlGk1ZMpgK_LQB1VTiN27JbvU-KL31byRxgM7wAmEu4,1060
|
|
2
|
+
onewordai/__init__.py,sha256=DW7ib3o1W0lchP960Mqdg_a-_aPPhPWgYKu5efR56ME,73
|
|
3
|
+
onewordai/cli.py,sha256=kz8epolGelaSfbOXdIB8IaiwUBLaKD8uyP6RBGOkCMM,2008
|
|
4
|
+
onewordai/api/__init__.py,sha256=W5bQNZGz2YLMXx1hC9e_eKhbf_GNFVYy3MC3D7gugUc,20
|
|
5
|
+
onewordai/api/main.py,sha256=QGyg4q72QkodF4HFAz6KOA8d3AlucisUB1OAw2XpANk,7964
|
|
6
|
+
onewordai/core/__init__.py,sha256=oTsOiTuWDAh1O6N9ZXfNFuiCmP3R99dBkEFljt3LI6Q,114
|
|
7
|
+
onewordai/core/engine.py,sha256=CqHVfvRjXSHgkjcPagYEhvCGwM2ywSlhyTvTran7MK0,15631
|
|
8
|
+
onewordai/web/app.js,sha256=mOp_RRear_N8RTpNEsYs2oj6mkji_AaKXlpxFsAtH-M,11166
|
|
9
|
+
onewordai/web/index.html,sha256=r1OSgeb35diHKmj1uWGnd-ltPBPz6aQp5mmcp2jTDmY,5995
|
|
10
|
+
onewordai/web/style.css,sha256=InsF-b_e5-zRhwaUHKf7cHnenNv87zA82VjcjqIsOnE,10485
|
|
11
|
+
oneword_ai-0.1.0.dist-info/METADATA,sha256=v5LBayOqcOffEvF6G7JXLumYGzdocKO1C5pzGy0ZeaE,7621
|
|
12
|
+
oneword_ai-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
oneword_ai-0.1.0.dist-info/entry_points.txt,sha256=qJ4u04rCiYtI5fCyPRBfTzW_-xc05BCSEct8YHzXX9Y,97
|
|
14
|
+
oneword_ai-0.1.0.dist-info/top_level.txt,sha256=otwwf_hwyKfUYUJnculUSznHz9iCFJRrHgIHyLQRbfE,10
|
|
15
|
+
oneword_ai-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 Ambrish
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
onewordai
|
onewordai/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API package."""
|
onewordai/api/main.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI backend for OneWord AI Subtitle Generator.
|
|
3
|
+
Handles file uploads, transcription processing, and SRT downloads.
|
|
4
|
+
"""
|
|
5
|
+
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, BackgroundTasks
|
|
6
|
+
from fastapi.responses import FileResponse, JSONResponse
|
|
7
|
+
from fastapi.staticfiles import StaticFiles
|
|
8
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import uuid
|
|
11
|
+
import shutil
|
|
12
|
+
import asyncio
|
|
13
|
+
from typing import Optional, Dict
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
from onewordai.core.engine import SubtitleGenerator
|
|
17
|
+
|
|
18
|
+
app = FastAPI(title="OneWord AI Subtitle Generator")
|
|
19
|
+
|
|
20
|
+
# CORS middleware
|
|
21
|
+
app.add_middleware(
|
|
22
|
+
CORSMiddleware,
|
|
23
|
+
allow_origins=["*"],
|
|
24
|
+
allow_credentials=True,
|
|
25
|
+
allow_methods=["*"],
|
|
26
|
+
allow_headers=["*"],
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Directories
|
|
30
|
+
UPLOAD_DIR = Path("uploads")
|
|
31
|
+
OUTPUT_DIR = Path("outputs")
|
|
32
|
+
UPLOAD_DIR.mkdir(exist_ok=True)
|
|
33
|
+
OUTPUT_DIR.mkdir(exist_ok=True)
|
|
34
|
+
|
|
35
|
+
# Job storage (in-memory, replace with Redis for production)
|
|
36
|
+
jobs: Dict[str, dict] = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class JobStatus:
|
|
40
|
+
PENDING = "pending"
|
|
41
|
+
PROCESSING = "processing"
|
|
42
|
+
COMPLETED = "completed"
|
|
43
|
+
FAILED = "failed"
|
|
44
|
+
CANCELLED = "cancelled"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def process_video_task(
|
|
48
|
+
job_id: str,
|
|
49
|
+
input_path: str,
|
|
50
|
+
model: str,
|
|
51
|
+
language: Optional[str],
|
|
52
|
+
mode: str
|
|
53
|
+
):
|
|
54
|
+
"""Background task to process video."""
|
|
55
|
+
try:
|
|
56
|
+
jobs[job_id]["status"] = JobStatus.PROCESSING
|
|
57
|
+
jobs[job_id]["progress"] = 0
|
|
58
|
+
|
|
59
|
+
# Create generator
|
|
60
|
+
generator = SubtitleGenerator(model_name=model)
|
|
61
|
+
|
|
62
|
+
# Process
|
|
63
|
+
output_path = OUTPUT_DIR / f"{job_id}.srt"
|
|
64
|
+
|
|
65
|
+
def progress_callback(percent):
|
|
66
|
+
jobs[job_id]["progress"] = percent
|
|
67
|
+
|
|
68
|
+
def status_callback(msg):
|
|
69
|
+
jobs[job_id]["status_message"] = msg
|
|
70
|
+
|
|
71
|
+
generator.process(
|
|
72
|
+
input_path=input_path,
|
|
73
|
+
output_path=str(output_path),
|
|
74
|
+
language=language,
|
|
75
|
+
mode=mode,
|
|
76
|
+
progress_callback=progress_callback,
|
|
77
|
+
status_callback=status_callback
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
jobs[job_id]["status"] = JobStatus.COMPLETED
|
|
81
|
+
jobs[job_id]["progress"] = 100
|
|
82
|
+
jobs[job_id]["output_file"] = str(output_path)
|
|
83
|
+
jobs[job_id]["completed_at"] = datetime.now().isoformat()
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
jobs[job_id]["status"] = JobStatus.FAILED
|
|
87
|
+
jobs[job_id]["error"] = str(e)
|
|
88
|
+
print(f"β Job {job_id} failed: {e}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.get("/api/health")
|
|
93
|
+
async def health_check():
|
|
94
|
+
"""Health check."""
|
|
95
|
+
return {"status": "ok", "service": "OneWord AI Subtitle Generator"}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.post("/api/upload")
|
|
99
|
+
async def upload_file(file: UploadFile = File(...)):
|
|
100
|
+
"""
|
|
101
|
+
Upload a video/audio file.
|
|
102
|
+
Returns a file_id for use in processing.
|
|
103
|
+
"""
|
|
104
|
+
if not file.filename:
|
|
105
|
+
raise HTTPException(status_code=400, detail="No file provided")
|
|
106
|
+
|
|
107
|
+
# Generate unique file ID
|
|
108
|
+
file_id = str(uuid.uuid4())
|
|
109
|
+
file_ext = Path(file.filename).suffix
|
|
110
|
+
file_path = UPLOAD_DIR / f"{file_id}{file_ext}"
|
|
111
|
+
|
|
112
|
+
# Save file
|
|
113
|
+
with file_path.open("wb") as buffer:
|
|
114
|
+
shutil.copyfileobj(file.file, buffer)
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"file_id": file_id,
|
|
118
|
+
"filename": file.filename,
|
|
119
|
+
"size": file_path.stat().st_size
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.post("/api/process")
|
|
124
|
+
async def process_file(
|
|
125
|
+
background_tasks: BackgroundTasks,
|
|
126
|
+
file_id: str = Form(...),
|
|
127
|
+
model: str = Form("medium"),
|
|
128
|
+
language: Optional[str] = Form(None),
|
|
129
|
+
mode: str = Form("oneword")
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
Start processing a previously uploaded file.
|
|
133
|
+
Returns a job_id to track progress.
|
|
134
|
+
"""
|
|
135
|
+
# Find uploaded file
|
|
136
|
+
uploaded_files = list(UPLOAD_DIR.glob(f"{file_id}.*"))
|
|
137
|
+
if not uploaded_files:
|
|
138
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
139
|
+
|
|
140
|
+
input_path = str(uploaded_files[0])
|
|
141
|
+
|
|
142
|
+
# Validate inputs
|
|
143
|
+
allowed_models = ["medium", "large", "Oriserve/Whisper-Hindi2Hinglish-Prime"]
|
|
144
|
+
if model not in allowed_models:
|
|
145
|
+
raise HTTPException(status_code=400, detail="Invalid model")
|
|
146
|
+
|
|
147
|
+
if language and language not in ["hi", "en", "ur", "es"]:
|
|
148
|
+
raise HTTPException(status_code=400, detail="Invalid language")
|
|
149
|
+
|
|
150
|
+
if mode not in ["oneword", "twoword", "phrase"]:
|
|
151
|
+
raise HTTPException(status_code=400, detail="Invalid mode")
|
|
152
|
+
|
|
153
|
+
# Create job
|
|
154
|
+
job_id = str(uuid.uuid4())
|
|
155
|
+
jobs[job_id] = {
|
|
156
|
+
"job_id": job_id,
|
|
157
|
+
"file_id": file_id,
|
|
158
|
+
"status": JobStatus.PENDING,
|
|
159
|
+
"progress": 0,
|
|
160
|
+
"status_message": "π¦ Checking model... (First-time download ~1.5GB)" if "/" in model else "π Preparing to transcribe...",
|
|
161
|
+
"model": model,
|
|
162
|
+
"language": language,
|
|
163
|
+
"mode": mode,
|
|
164
|
+
"created_at": datetime.now().isoformat()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Start background task
|
|
168
|
+
background_tasks.add_task(
|
|
169
|
+
process_video_task,
|
|
170
|
+
job_id, input_path, model, language, mode
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return {"job_id": job_id}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.get("/api/status/{job_id}")
|
|
177
|
+
async def get_status(job_id: str):
|
|
178
|
+
"""Get processing status for a job."""
|
|
179
|
+
if job_id not in jobs:
|
|
180
|
+
raise HTTPException(status_code=404, detail="Job not found")
|
|
181
|
+
|
|
182
|
+
return jobs[job_id]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@app.get("/api/download/{job_id}")
|
|
186
|
+
async def download_srt(job_id: str):
|
|
187
|
+
"""Download the generated SRT file."""
|
|
188
|
+
if job_id not in jobs:
|
|
189
|
+
raise HTTPException(status_code=404, detail="Job not found")
|
|
190
|
+
|
|
191
|
+
job = jobs[job_id]
|
|
192
|
+
|
|
193
|
+
if job["status"] != JobStatus.COMPLETED:
|
|
194
|
+
raise HTTPException(status_code=400, detail="Job not completed yet")
|
|
195
|
+
|
|
196
|
+
output_file = Path(job["output_file"])
|
|
197
|
+
if not output_file.exists():
|
|
198
|
+
raise HTTPException(status_code=404, detail="Output file not found")
|
|
199
|
+
|
|
200
|
+
return FileResponse(
|
|
201
|
+
output_file,
|
|
202
|
+
media_type="application/x-subrip",
|
|
203
|
+
filename=f"subtitles_{job['mode']}.srt"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@app.post("/api/cancel/{job_id}")
|
|
208
|
+
async def cancel_job(job_id: str):
|
|
209
|
+
"""Cancel a running job."""
|
|
210
|
+
if job_id not in jobs:
|
|
211
|
+
raise HTTPException(status_code=404, detail="Job not found")
|
|
212
|
+
|
|
213
|
+
job = jobs[job_id]
|
|
214
|
+
|
|
215
|
+
# Only cancel if still processing
|
|
216
|
+
if job["status"] in [JobStatus.PENDING, JobStatus.PROCESSING]:
|
|
217
|
+
jobs[job_id]["status"] = JobStatus.CANCELLED
|
|
218
|
+
jobs[job_id]["status_message"] = "β Cancelled by user"
|
|
219
|
+
return {"message": "Job cancelled successfully"}
|
|
220
|
+
else:
|
|
221
|
+
return {"message": f"Job already {job['status']}"}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# Mount static files (web UI) at root
|
|
225
|
+
try:
|
|
226
|
+
# Try resolving relative to package install location
|
|
227
|
+
package_root = Path(__file__).parent.parent
|
|
228
|
+
web_dir = package_root / "web"
|
|
229
|
+
|
|
230
|
+
if not web_dir.exists():
|
|
231
|
+
# Fallback for local development if running from root
|
|
232
|
+
web_dir = Path("onewordai/web")
|
|
233
|
+
|
|
234
|
+
app.mount("/", StaticFiles(directory=str(web_dir), html=True), name="web")
|
|
235
|
+
except Exception as e:
|
|
236
|
+
print(f"β οΈ Warning: Could not mount web UI: {e}")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
import uvicorn
|
|
241
|
+
print("\n⨠OneWord AI running at: http://localhost:8000\n")
|
|
242
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
243
|
+
|
|
244
|
+
# Entry point for package
|
|
245
|
+
def start_server():
|
|
246
|
+
"""Start the web server (used by oneword-web command)."""
|
|
247
|
+
import uvicorn
|
|
248
|
+
import webbrowser
|
|
249
|
+
import threading
|
|
250
|
+
import time
|
|
251
|
+
|
|
252
|
+
def open_browser():
|
|
253
|
+
time.sleep(2)
|
|
254
|
+
print("π Opening browser at http://localhost:8000")
|
|
255
|
+
webbrowser.open("http://localhost:8000")
|
|
256
|
+
|
|
257
|
+
threading.Thread(target=open_browser, daemon=True).start()
|
|
258
|
+
|
|
259
|
+
# Run server
|
|
260
|
+
print("β¨ Starting OneWord AI Web Server...")
|
|
261
|
+
# Use reload=False for production package usage
|
|
262
|
+
uvicorn.run("onewordai.api.main:app", host="0.0.0.0", port=8000, reload=False)
|
onewordai/cli.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI interface for OneWord AI Subtitle Generator.
|
|
3
|
+
"""
|
|
4
|
+
import argparse
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from .core.engine import SubtitleGenerator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
description="OneWord AI - Generate cinematic one-word subtitles using Whisper"
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"-i", "--input",
|
|
15
|
+
required=True,
|
|
16
|
+
help="Path to input video/audio file"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"-m", "--model",
|
|
20
|
+
default="medium",
|
|
21
|
+
choices=["medium", "large", "Oriserve/Whisper-Hindi2Hinglish-Prime"], help="Whisper model to use (default: medium)"
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"-lang", "--language",
|
|
25
|
+
default=None,
|
|
26
|
+
choices=["hi", "en", "ur", "es"],
|
|
27
|
+
help="Language code (hi=Hindi, en=English, ur=Urdu, es=Spanish). Auto-detect if not specified."
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"-mode", "--mode",
|
|
31
|
+
default="oneword",
|
|
32
|
+
choices=["oneword", "twoword", "phrase"],
|
|
33
|
+
help="Subtitle mode: oneword (default), twoword (punch effect), phrase (full segment)"
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"-o", "--output",
|
|
37
|
+
help="Output SRT file path (optional, auto-generated if not specified)"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
|
|
42
|
+
# Validate input file
|
|
43
|
+
input_path = Path(args.input)
|
|
44
|
+
if not input_path.exists():
|
|
45
|
+
print(f"β Error: Input file '{args.input}' not found.")
|
|
46
|
+
return 1
|
|
47
|
+
|
|
48
|
+
# Create generator
|
|
49
|
+
generator = SubtitleGenerator(model_name=args.model)
|
|
50
|
+
|
|
51
|
+
# Process
|
|
52
|
+
try:
|
|
53
|
+
output_file = generator.process(
|
|
54
|
+
input_path=str(input_path),
|
|
55
|
+
output_path=args.output,
|
|
56
|
+
language=args.language,
|
|
57
|
+
mode=args.mode
|
|
58
|
+
)
|
|
59
|
+
print(f"\nπ Done! SRT file: {output_file}")
|
|
60
|
+
return 0
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"\nβ Error: {e}")
|
|
63
|
+
return 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
exit(main())
|