Skip to content

Commit e75322a

Browse files
committed
Refactor: package restructure with AIOS
- New package youtube_auto_dub/ with pyproject.toml and CLI entry point - Split 546-line engines.py into transcriber, renderer, segmenter, config - Split 182-line core_utils.py into exceptions, utils - Moved main.py logic into pipeline.py; main.py is now a 6-line wrapper - Moved language_map.json into package as package data - Added 13 tests (models, googlev4, tts) - Added CI pipeline (ruff + pytest on 3.10/3.11/3.12) - Leftover root language_map.json cleaned up - Updated latest_langmap_generate.py path
1 parent 179ec9f commit e75322a

34 files changed

Lines changed: 1444 additions & 1156 deletions

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-python@v5
15+
with:
16+
python-version: "3.12"
17+
- name: Install dependencies
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install ruff
21+
- name: Lint with Ruff
22+
run: ruff check youtube_auto_dub/
23+
24+
test:
25+
needs: lint
26+
strategy:
27+
matrix:
28+
python-version: ["3.10", "3.11", "3.12"]
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v4
32+
- uses: actions/setup-python@v5
33+
with:
34+
python-version: ${{ matrix.python-version }}
35+
- name: Install system dependencies
36+
run: sudo apt-get update && sudo apt-get install -y ffmpeg
37+
- name: Install package
38+
run: |
39+
python -m pip install --upgrade pip
40+
pip install .[dev]
41+
- name: Test with pytest
42+
run: python -m pytest tests/ -v

.gitignore

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
# Python
12
__pycache__/
2-
*.pyc
3+
*.py[cod]
4+
*.egg-info/
5+
dist/
6+
build/
7+
.pytest_cache/
8+
*.egg
9+
10+
# Project runtime
311
.cache/
412
temp/
513
output/
614
*.mp4
715
*.wav
8-
*.mp3
16+
*.mp3
17+
18+
# IDE
19+
.vscode/
20+
.idea/
21+
*.swp
22+
*.swo

README.md

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# youtube-auto-dub
22

3+
[![CI](https://github.com/mangodxd/youtube-auto-dub/actions/workflows/ci.yml/badge.svg)](https://github.com/mangodxd/youtube-auto-dub/actions/workflows/ci.yml)
4+
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6+
37
A Python pipeline that takes a YouTube URL and spits out a dubbed/subtitled video. Feed it a link, pick a target language, and it handles the rest — downloading, transcribing, translating, synthesizing speech, and rendering the final video.
48

59
We built this because existing tools were either too manual, too expensive, or too locked-in. This one runs locally, stays free, and gives you full control over the output.
@@ -26,7 +30,7 @@ YouTube URL → Download → Transcribe (Whisper) → Chunk → Translate → TT
2630

2731
### Prerequisites
2832

29-
- Python 3.8+
33+
- Python 3.10+
3034
- FFmpeg in your PATH
3135
- (Optional) CUDA for faster Whisper inference
3236

@@ -42,10 +46,16 @@ sudo apt install ffmpeg
4246

4347
### Install
4448

49+
```bash
50+
pip install youtube-auto-dub
51+
```
52+
53+
Or install from source:
54+
4555
```bash
4656
git clone https://github.com/mangodxd/youtube-auto-dub.git
4757
cd youtube-auto-dub
48-
pip install -r requirements.txt
58+
pip install .
4959
```
5060

5161
For GPU support:
@@ -59,19 +69,25 @@ pip install torch torchvision torchaudio --index-url https://download.pytorch.or
5969

6070
```bash
6171
# Dub + subtitles in Vietnamese (default)
72+
youtube-auto-dub "https://youtube.com/watch?v=VIDEO_ID"
73+
74+
# Or via python -m
75+
python -m youtube_auto_dub "https://youtube.com/watch?v=VIDEO_ID"
76+
77+
# Or legacy entry point (still works)
6278
python main.py "https://youtube.com/watch?v=VIDEO_ID"
6379

6480
# Just subtitles, in Spanish
65-
python main.py "https://youtube.com/watch?v=VIDEO_ID" --mode sub --lang es
81+
youtube-auto-dub "https://youtube.com/watch?v=VIDEO_ID" --mode sub --lang es
6682

6783
# Dubbing only, French, female voice
68-
python main.py "https://youtube.com/watch?v=VIDEO_ID" --mode dub --lang fr --gender female
84+
youtube-auto-dub "https://youtube.com/watch?v=VIDEO_ID" --mode dub --lang fr --gender female
6985

7086
# Different languages for subs and dub
71-
python main.py "https://youtube.com/watch?v=VIDEO_ID" --mode both --lang_sub en --lang_dub vi
87+
youtube-auto-dub "https://youtube.com/watch?v=VIDEO_ID" --mode both --lang_sub en --lang_dub vi
7288

7389
# Age-restricted or private video — pull cookies from your browser
74-
python main.py "https://youtube.com/watch?v=VIDEO_ID" --lang es --browser chrome
90+
youtube-auto-dub "https://youtube.com/watch?v=VIDEO_ID" --lang es --browser chrome
7591
```
7692

7793
### All options
@@ -91,7 +107,7 @@ python main.py "https://youtube.com/watch?v=VIDEO_ID" --lang es --browser chrome
91107

92108
## Language & voice config
93109

94-
Voices are mapped in `language_map.json`. Edit it to change defaults or add new languages:
110+
Voices are mapped in `language_map.json` inside the package. Edit it to change defaults or add new languages:
95111

96112
```json
97113
{
@@ -113,20 +129,37 @@ Common codes: `es` · `fr` · `de` · `it` · `pt` · `ja` · `ko` · `zh` · `a
113129

114130
```
115131
youtube-auto-dub/
116-
├── main.py # Entry point, CLI, pipeline orchestration
117-
├── language_map.json # Language → voice mappings
118-
├── requirements.txt
119-
└── src/
132+
├── pyproject.toml # Package config, dependencies, CLI entry
133+
├── main.py # Legacy entry point (thin wrapper)
134+
├── language_map.json # (root copy kept for reference)
135+
└── youtube_auto_dub/ # Installable Python package
136+
├── __init__.py # Package version
137+
├── __main__.py # python -m youtube_auto_dub support
138+
├── cli.py # Thin CLI adapter (argparse → pipeline)
139+
├── pipeline.py # Pipeline orchestration logic
120140
├── models.py # Dataclasses (SubtitleSegment, ProjectContext)
121141
├── youtube.py # yt-dlp wrapper, audio extraction
122-
├── media.py # Chunking, SRT generation, audio mixing, FFmpeg render
142+
├── media.py # Chunking, SRT, mixing, FFmpeg render
123143
├── googlev4.py # Google Translate (RPC + scrape fallback)
124-
├── tts.py # Edge TTS synthesis
125-
└── ui.py # Rich console logger
126-
127-
.cache/ # Downloaded videos (persists between runs)
128-
output/ # Final rendered videos
129-
temp/ # Intermediate files (cleared each run)
144+
├── tts.py # Edge TTS synthesis with retry
145+
├── transcriber.py # Whisper transcription + device manager
146+
├── renderer.py # FFmpeg helper utilities
147+
├── segmenter.py # Smart segmentation (dynamic chunking)
148+
├── config.py # Config manager, language data loading
149+
├── exceptions.py # Custom exception hierarchy
150+
├── utils.py # General-purpose utilities
151+
├── engine.py # Legacy AI Engine (preserved for BC)
152+
└── language_map.json # Language → voice mappings (package data)
153+
├── tests/
154+
│ ├── test_models.py # Model dataclass tests
155+
│ ├── test_googlev4.py # Translation shortcut tests (no network)
156+
│ └── test_tts.py # Voice lookup tests
157+
.tests/
158+
├── .github/workflows/
159+
│ └── ci.yml # Ruff lint + pytest on 3.10, 3.11, 3.12
160+
├── .cache/ # Downloaded videos (persists between runs)
161+
├── output/ # Final rendered videos
162+
└── temp/ # Intermediate files (cleared each run)
130163
```
131164

132165
---
@@ -168,6 +201,17 @@ On the backlog:
168201

169202
Issues and PRs are open. If you're adding a new language to `language_map.json`, please include both a male and female voice where Edge TTS supports it.
170203

204+
```bash
205+
# Dev install
206+
pip install -e ".[dev]"
207+
208+
# Run tests
209+
pytest tests/ -v
210+
211+
# Lint
212+
ruff check .
213+
```
214+
171215
---
172216

173217
## License
@@ -176,4 +220,4 @@ MIT. See [LICENSE](LICENSE).
176220

177221
---
178222

179-
*Built by Nguyen Cong Thuan Huy ([@mangodxd](https://github.com/mangodxd))*
223+
*Built by Nguyen Cong Thuan Huy ([@mangodxd](https://github.com/mangodxd))*

latest_langmap_generate.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import asyncio
22
import json
3-
import edge_tts
43
from pathlib import Path
5-
from typing import Dict, Any
4+
from typing import Any, Dict
5+
6+
import edge_tts
67

78
BASE_DIR = Path(__file__).resolve().parent
8-
LANG_MAP_FILE = BASE_DIR / "language_map.json"
9+
LANG_MAP_FILE = BASE_DIR / "youtube_auto_dub" / "language_map.json"
910

1011
async def generate_lang_map() -> None:
1112
print("[*] Connecting to Microsoft Edge TTS API...")
12-
13+
1314
try:
1415
#fetch all available voices
1516
voices = await edge_tts.list_voices()
@@ -18,47 +19,47 @@ async def generate_lang_map() -> None:
1819
return
1920

2021
print(f"[*] Processing {len(voices)} raw voice entries...")
21-
22+
2223
# Structure: { "vi": { "name": "vi-VN", "voices": { "male": [], "female": [] } } }
2324
lang_map: Dict[str, Any] = {}
24-
25+
2526
for v in voices:
2627
if "Neural" not in v["ShortName"]:
2728
continue
28-
29+
2930
short_name = v["ShortName"] # e.g., "vi-VN-NamMinhNeural"
3031
locale = v["Locale"] # e.g., "vi-VN"
3132
gender = v["Gender"].lower() # "male" or "female"
3233
lang_code = locale.split('-')[0] # ISO Language Code (e.g., 'vi' from 'vi-VN')
33-
34+
3435
if lang_code not in lang_map:
3536
lang_map[lang_code] = {
36-
"name": locale,
37+
"name": locale,
3738
"voices": {"male": [], "female": []}
3839
}
39-
40+
4041
if gender in lang_map[lang_code]["voices"]:
4142
lang_map[lang_code]["voices"][gender].append(short_name)
42-
43-
43+
44+
4445
final_map = {
45-
k: v for k, v in lang_map.items()
46+
k: v for k, v in lang_map.items()
4647
if v["voices"]["male"] or v["voices"]["female"]
4748
}
4849

4950
try:
5051
with open(LANG_MAP_FILE, "w", encoding="utf-8") as f:
5152
json.dump(final_map, f, ensure_ascii=False, indent=2)
52-
53+
5354
print(f"\n[+] SUCCESS! Generated configuration for {len(final_map)} languages.")
5455
print(f" File saved to -> {LANG_MAP_FILE}")
55-
56+
5657
if "vi" in final_map:
5758
print("\n[*] Preview (Vietnamese):")
5859
print(json.dumps(final_map["vi"], indent=2))
59-
60+
6061
except Exception as e:
6162
print(f"[!] ERROR: Failed to write JSON file: {e}")
6263

6364
if __name__ == "__main__":
64-
asyncio.run(generate_lang_map())
65+
asyncio.run(generate_lang_map())

0 commit comments

Comments
 (0)