lattifai 1.2.0__py3-none-any.whl → 1.2.2__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.
Files changed (64) hide show
  1. lattifai/__init__.py +0 -24
  2. lattifai/alignment/__init__.py +10 -1
  3. lattifai/alignment/lattice1_aligner.py +66 -58
  4. lattifai/alignment/lattice1_worker.py +1 -6
  5. lattifai/alignment/punctuation.py +38 -0
  6. lattifai/alignment/segmenter.py +1 -1
  7. lattifai/alignment/sentence_splitter.py +350 -0
  8. lattifai/alignment/text_align.py +440 -0
  9. lattifai/alignment/tokenizer.py +91 -220
  10. lattifai/caption/__init__.py +82 -6
  11. lattifai/caption/caption.py +335 -1143
  12. lattifai/caption/formats/__init__.py +199 -0
  13. lattifai/caption/formats/base.py +211 -0
  14. lattifai/caption/formats/gemini.py +722 -0
  15. lattifai/caption/formats/json.py +194 -0
  16. lattifai/caption/formats/lrc.py +309 -0
  17. lattifai/caption/formats/nle/__init__.py +9 -0
  18. lattifai/caption/formats/nle/audition.py +561 -0
  19. lattifai/caption/formats/nle/avid.py +423 -0
  20. lattifai/caption/formats/nle/fcpxml.py +549 -0
  21. lattifai/caption/formats/nle/premiere.py +589 -0
  22. lattifai/caption/formats/pysubs2.py +642 -0
  23. lattifai/caption/formats/sbv.py +147 -0
  24. lattifai/caption/formats/tabular.py +338 -0
  25. lattifai/caption/formats/textgrid.py +193 -0
  26. lattifai/caption/formats/ttml.py +652 -0
  27. lattifai/caption/formats/vtt.py +469 -0
  28. lattifai/caption/parsers/__init__.py +9 -0
  29. lattifai/caption/{text_parser.py → parsers/text_parser.py} +4 -2
  30. lattifai/caption/standardize.py +636 -0
  31. lattifai/caption/utils.py +474 -0
  32. lattifai/cli/__init__.py +2 -1
  33. lattifai/cli/caption.py +108 -1
  34. lattifai/cli/transcribe.py +4 -9
  35. lattifai/cli/youtube.py +4 -1
  36. lattifai/client.py +48 -84
  37. lattifai/config/__init__.py +11 -1
  38. lattifai/config/alignment.py +9 -2
  39. lattifai/config/caption.py +267 -23
  40. lattifai/config/media.py +20 -0
  41. lattifai/diarization/__init__.py +41 -1
  42. lattifai/mixin.py +36 -18
  43. lattifai/transcription/base.py +6 -1
  44. lattifai/transcription/lattifai.py +19 -54
  45. lattifai/utils.py +81 -13
  46. lattifai/workflow/__init__.py +28 -4
  47. lattifai/workflow/file_manager.py +2 -5
  48. lattifai/youtube/__init__.py +43 -0
  49. lattifai/youtube/client.py +1170 -0
  50. lattifai/youtube/types.py +23 -0
  51. lattifai-1.2.2.dist-info/METADATA +615 -0
  52. lattifai-1.2.2.dist-info/RECORD +76 -0
  53. {lattifai-1.2.0.dist-info → lattifai-1.2.2.dist-info}/entry_points.txt +1 -2
  54. lattifai/caption/gemini_reader.py +0 -371
  55. lattifai/caption/gemini_writer.py +0 -173
  56. lattifai/cli/app_installer.py +0 -142
  57. lattifai/cli/server.py +0 -44
  58. lattifai/server/app.py +0 -427
  59. lattifai/workflow/youtube.py +0 -577
  60. lattifai-1.2.0.dist-info/METADATA +0 -1133
  61. lattifai-1.2.0.dist-info/RECORD +0 -57
  62. {lattifai-1.2.0.dist-info → lattifai-1.2.2.dist-info}/WHEEL +0 -0
  63. {lattifai-1.2.0.dist-info → lattifai-1.2.2.dist-info}/licenses/LICENSE +0 -0
  64. {lattifai-1.2.0.dist-info → lattifai-1.2.2.dist-info}/top_level.txt +0 -0
@@ -1,142 +0,0 @@
1
- """CLI tool to install lai-app (frontend web application)."""
2
-
3
- import platform
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
-
8
- from lattifai.utils import safe_print
9
-
10
-
11
- def check_command_exists(cmd: str) -> bool:
12
- """Check if a command exists in PATH."""
13
- try:
14
- subprocess.run([cmd, "--version"], check=True, capture_output=True, text=True)
15
- return True
16
- except (subprocess.CalledProcessError, FileNotFoundError):
17
- return False
18
-
19
-
20
- def install_nodejs():
21
- """Install Node.js based on the operating system."""
22
- system = platform.system().lower()
23
-
24
- safe_print("📦 Node.js not found. Installing Node.js...\n")
25
-
26
- try:
27
- if system == "darwin": # macOS
28
- # Check if Homebrew is installed
29
- if check_command_exists("brew"):
30
- safe_print("🍺 Using Homebrew to install Node.js...")
31
- subprocess.run(["brew", "install", "node"], check=True)
32
- safe_print("✓ Node.js installed via Homebrew\n")
33
- else:
34
- safe_print("❌ Homebrew not found.")
35
- print(" Please install Homebrew first:")
36
- print(
37
- ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
38
- )
39
- print("\n Or install Node.js manually from: https://nodejs.org/")
40
- sys.exit(1)
41
-
42
- elif system == "linux":
43
- # Try common package managers
44
- if check_command_exists("apt"):
45
- safe_print("🐧 Using apt to install Node.js...")
46
- subprocess.run(["sudo", "apt", "update"], check=True)
47
- subprocess.run(["sudo", "apt", "install", "-y", "nodejs", "npm"], check=True)
48
- safe_print("✓ Node.js installed via apt\n")
49
- elif check_command_exists("yum"):
50
- safe_print("🐧 Using yum to install Node.js...")
51
- subprocess.run(["sudo", "yum", "install", "-y", "nodejs", "npm"], check=True)
52
- safe_print("✓ Node.js installed via yum\n")
53
- elif check_command_exists("dnf"):
54
- safe_print("🐧 Using dnf to install Node.js...")
55
- subprocess.run(["sudo", "dnf", "install", "-y", "nodejs", "npm"], check=True)
56
- safe_print("✓ Node.js installed via dnf\n")
57
- elif check_command_exists("pacman"):
58
- safe_print("🐧 Using pacman to install Node.js...")
59
- subprocess.run(["sudo", "pacman", "-S", "--noconfirm", "nodejs", "npm"], check=True)
60
- safe_print("✓ Node.js installed via pacman\n")
61
- else:
62
- safe_print("❌ No supported package manager found (apt/yum/dnf/pacman).")
63
- print(" Please install Node.js manually from: https://nodejs.org/")
64
- sys.exit(1)
65
-
66
- elif system == "windows":
67
- safe_print("❌ Automatic installation on Windows is not supported.")
68
- print(" Please download and install Node.js from: https://nodejs.org/")
69
- print(" Then run this command again.")
70
- sys.exit(1)
71
-
72
- else:
73
- safe_print(f"❌ Unsupported operating system: {system}")
74
- print(" Please install Node.js manually from: https://nodejs.org/")
75
- sys.exit(1)
76
-
77
- # Verify installation
78
- if not check_command_exists("npm"):
79
- safe_print("❌ Node.js installation verification failed.")
80
- print(" Please restart your terminal and try again.")
81
- sys.exit(1)
82
-
83
- except subprocess.CalledProcessError as e:
84
- safe_print(f"\n❌ Error during Node.js installation: {e}")
85
- print(" Please install Node.js manually from: https://nodejs.org/")
86
- sys.exit(1)
87
-
88
-
89
- def main():
90
- """Install lai-app Node.js application."""
91
- # Get the app directory relative to this package
92
- app_dir = Path(__file__).parent.parent.parent.parent / "app"
93
-
94
- if not app_dir.exists():
95
- safe_print(f"❌ Error: app directory not found at {app_dir}")
96
- print(" Make sure you're in the lattifai-python repository.")
97
- sys.exit(1)
98
-
99
- safe_print("🚀 Installing lai-app (LattifAI Web Application)...\n")
100
-
101
- # Check if npm is installed, if not, install Node.js
102
- if not check_command_exists("npm"):
103
- install_nodejs()
104
- else:
105
- npm_version = subprocess.run(["npm", "--version"], capture_output=True, text=True, check=True).stdout.strip()
106
- safe_print(f"✓ npm is already installed (v{npm_version})\n")
107
-
108
- # Change to app directory and run installation
109
- try:
110
- safe_print(f"📁 Working directory: {app_dir}\n")
111
-
112
- # Install dependencies
113
- safe_print("📦 Installing dependencies...")
114
- subprocess.run(["npm", "install"], cwd=app_dir, check=True)
115
- safe_print("✓ Dependencies installed\n")
116
-
117
- # Build the application
118
- safe_print("🔨 Building application...")
119
- subprocess.run(["npm", "run", "build"], cwd=app_dir, check=True)
120
- safe_print("✓ Application built\n")
121
-
122
- # Link globally
123
- safe_print("🔗 Linking lai-app command globally...")
124
- subprocess.run(["npm", "link"], cwd=app_dir, check=True)
125
- safe_print("✓ lai-app command linked globally\n")
126
-
127
- safe_print("=" * 60)
128
- safe_print("✅ lai-app installed successfully!")
129
- safe_print("=" * 60)
130
- safe_print("\n🎉 You can now run:")
131
- print(" lai-app # Start the web application")
132
- print(" lai-app --help # Show help")
133
- print(" lai-app --port 8080 # Use custom port")
134
- safe_print("\n📖 For more information, see app/CLI_USAGE.md\n")
135
-
136
- except subprocess.CalledProcessError as e:
137
- safe_print(f"\n❌ Error during installation: {e}")
138
- sys.exit(1)
139
-
140
-
141
- if __name__ == "__main__":
142
- main()
lattifai/cli/server.py DELETED
@@ -1,44 +0,0 @@
1
- import argparse
2
- import os
3
-
4
- import colorful
5
- import uvicorn
6
-
7
- from lattifai.utils import safe_print
8
-
9
-
10
- def main():
11
- """Launch the LattifAI Web Interface."""
12
- parser = argparse.ArgumentParser(description="LattifAI Backend Server")
13
- parser.add_argument(
14
- "-p",
15
- "--port",
16
- type=int,
17
- default=8001,
18
- help="Port to run the server on (default: 8001)",
19
- )
20
- parser.add_argument(
21
- "--host",
22
- type=str,
23
- default="0.0.0.0",
24
- help="Host to bind the server to (default: 0.0.0.0)",
25
- )
26
- parser.add_argument(
27
- "--no-reload",
28
- action="store_true",
29
- help="Disable auto-reload on code changes",
30
- )
31
-
32
- args = parser.parse_args()
33
-
34
- safe_print(colorful.bold_green("🚀 Launching LattifAI Backend Server..."))
35
- print(colorful.cyan(f"Server running at http://localhost:{args.port}"))
36
- print(colorful.yellow(f"Host: {args.host}"))
37
- print(colorful.yellow(f"Auto-reload: {'disabled' if args.no_reload else 'enabled'}"))
38
- print()
39
-
40
- uvicorn.run("lattifai.server.app:app", host=args.host, port=args.port, reload=not args.no_reload, log_level="info")
41
-
42
-
43
- if __name__ == "__main__":
44
- main()
lattifai/server/app.py DELETED
@@ -1,427 +0,0 @@
1
- import asyncio
2
- import os
3
- import subprocess
4
- import sys
5
- import tempfile
6
- from pathlib import Path
7
- from typing import Optional
8
-
9
- # Load environment variables from .env file
10
- from dotenv import find_dotenv, load_dotenv
11
- from fastapi import BackgroundTasks, FastAPI, File, Form, Request, UploadFile
12
- from fastapi.middleware.cors import CORSMiddleware
13
- from fastapi.responses import JSONResponse
14
-
15
- # Try to find and load .env file from current directory or parent directories
16
- load_dotenv(find_dotenv(usecwd=True))
17
-
18
-
19
- app = FastAPI(title="LattifAI Web Interface")
20
-
21
- print(f"LOADING APP FROM: {__file__}")
22
-
23
- # Lazy-initialized client - will be created on first use
24
- _client = None
25
-
26
-
27
- def get_client():
28
- """Get or create the LattifAI client (lazy initialization)."""
29
- global _client
30
- if _client is None:
31
- from lattifai.client import LattifAI
32
-
33
- _client = LattifAI()
34
- return _client
35
-
36
-
37
- @app.on_event("startup")
38
- async def startup_event():
39
- print("Listing all registered routes:")
40
- for route in app.routes:
41
- print(f"Route: {route.path} - {route.name}")
42
-
43
-
44
- @app.middleware("http")
45
- async def log_requests(request: Request, call_next):
46
- print(f"INCOMING REQUEST: {request.method} {request.url}")
47
- response = await call_next(request)
48
- print(f"OUTGOING RESPONSE: {response.status_code}")
49
- return response
50
-
51
-
52
- app.add_middleware(
53
- CORSMiddleware,
54
- allow_origins=["*"], # Allow all origins for dev
55
- allow_credentials=True,
56
- allow_methods=["*"],
57
- allow_headers=["*"],
58
- )
59
-
60
-
61
- @app.get("/health")
62
- async def health_check():
63
- """Health check endpoint for server status monitoring."""
64
- return {"status": "ok", "message": "LattifAI backend server is running"}
65
-
66
-
67
- def mask_api_key(key: str) -> str:
68
- """Mask API key for display, showing only first 6 and last 4 characters."""
69
- if len(key) <= 10:
70
- return "*" * len(key)
71
- return key[:6] + "*" * (len(key) - 10) + key[-4:]
72
-
73
-
74
- @app.get("/api/keys")
75
- async def get_api_keys():
76
- """Get status of API keys from environment variables."""
77
- lattifai_key = os.environ.get("LATTIFAI_API_KEY", "")
78
- gemini_key = os.environ.get("GEMINI_API_KEY", "")
79
-
80
- return {
81
- "lattifai": {
82
- "exists": bool(lattifai_key),
83
- "masked_value": mask_api_key(lattifai_key) if lattifai_key else None,
84
- "create_url": "https://lattifai.com/dashboard/api-keys",
85
- },
86
- "gemini": {
87
- "exists": bool(gemini_key),
88
- "masked_value": mask_api_key(gemini_key) if gemini_key else None,
89
- "create_url": "https://aistudio.google.com/apikey",
90
- },
91
- }
92
-
93
-
94
- @app.post("/api/keys")
95
- async def save_api_keys(request: Request):
96
- """Save API keys to environment variables and optionally to .env file."""
97
- try:
98
- data = await request.json()
99
- lattifai_key = data.get("lattifai_key", "").strip()
100
- gemini_key = data.get("gemini_key", "").strip()
101
- save_to_file = data.get("save_to_file", False) # Optional: save to .env file
102
-
103
- # Always update environment variables in current process
104
- if lattifai_key:
105
- os.environ["LATTIFAI_API_KEY"] = lattifai_key
106
- if gemini_key:
107
- os.environ["GEMINI_API_KEY"] = gemini_key
108
-
109
- # Reset client to force re-initialization with new keys
110
- global _client
111
- _client = None
112
-
113
- result = {
114
- "status": "success",
115
- "message": "API keys updated in environment variables",
116
- }
117
-
118
- # Optionally save to .env file for persistence
119
- if save_to_file:
120
- # Find the .env file path
121
- env_path = find_dotenv(usecwd=True)
122
- if not env_path:
123
- # Create .env in current working directory
124
- env_path = Path.cwd() / ".env"
125
-
126
- # Read existing .env content
127
- env_lines = []
128
- if Path(env_path).exists():
129
- with open(env_path, "r") as f:
130
- env_lines = f.readlines()
131
-
132
- # Update or add API keys
133
- updated_lines = []
134
- lattifai_updated = False
135
- gemini_updated = False
136
-
137
- for line in env_lines:
138
- if line.strip().startswith("LATTIFAI_API_KEY=") or line.strip().startswith("#LATTIFAI_API_KEY="):
139
- if lattifai_key:
140
- updated_lines.append(f"LATTIFAI_API_KEY={lattifai_key}\n")
141
- lattifai_updated = True
142
- else:
143
- updated_lines.append(line) # Keep existing or commented out
144
- elif line.strip().startswith("GEMINI_API_KEY=") or line.strip().startswith("#GEMINI_API_KEY="):
145
- if gemini_key:
146
- updated_lines.append(f"GEMINI_API_KEY={gemini_key}\n")
147
- gemini_updated = True
148
- else:
149
- updated_lines.append(line) # Keep existing or commented out
150
- else:
151
- updated_lines.append(line)
152
-
153
- # Add new keys if they weren't in the file
154
- if lattifai_key and not lattifai_updated:
155
- updated_lines.append(f"LATTIFAI_API_KEY={lattifai_key}\n")
156
- if gemini_key and not gemini_updated:
157
- updated_lines.append(f"GEMINI_API_KEY={gemini_key}\n")
158
-
159
- # Write back to .env file
160
- with open(env_path, "w") as f:
161
- f.writelines(updated_lines)
162
-
163
- result["message"] = "API keys saved to environment variables and .env file"
164
- result["env_path"] = str(env_path)
165
-
166
- return result
167
-
168
- except Exception as e:
169
- import traceback
170
-
171
- traceback.print_exc()
172
- return JSONResponse(status_code=500, content={"error": str(e), "traceback": traceback.format_exc()})
173
-
174
-
175
- @app.post("/api/utils/select-directory")
176
- async def select_directory():
177
- """
178
- Open a native directory selection dialog on the server (local machine).
179
- Returns the selected path.
180
- """
181
- try:
182
- path = ""
183
- if sys.platform == "darwin":
184
- # Use AppleScript for macOS - it's cleaner than Tkinter on Mac
185
- script = """
186
- try
187
- set theFolder to choose folder with prompt "Select Output Directory"
188
- POSIX path of theFolder
189
- on error
190
- return ""
191
- end try
192
- """
193
- result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
194
- if result.returncode == 0:
195
- path = result.stdout.strip()
196
-
197
- # Fallback to Tkinter if path is still empty (e.g. not mac or mac script failed)
198
- # Note: Tkinter might not be installed or might fail in some environments
199
- if not path and sys.platform != "darwin":
200
- try:
201
- import tkinter
202
- from tkinter import filedialog
203
-
204
- root = tkinter.Tk()
205
- root.withdraw() # Hide main window
206
- root.wm_attributes("-topmost", 1) # Bring to front
207
- path = filedialog.askdirectory(title="Select Output Directory")
208
- root.destroy()
209
- except ImportError:
210
- pass
211
- except Exception as e:
212
- print(f"Tkinter dialog failed: {e}")
213
-
214
- return {"path": path}
215
- except Exception as e:
216
- # Don't fail the request, just return empty path or error logged
217
- print(f"Directory selection failed: {e}")
218
- return {"path": "", "error": str(e)}
219
-
220
-
221
- @app.post("/align")
222
- async def align_files(
223
- background_tasks: BackgroundTasks,
224
- media_file: Optional[UploadFile] = File(None),
225
- caption_file: Optional[UploadFile] = File(None),
226
- local_media_path: Optional[str] = Form(None),
227
- local_caption_path: Optional[str] = Form(None),
228
- local_output_dir: Optional[str] = Form(None),
229
- youtube_url: Optional[str] = Form(None),
230
- youtube_output_dir: Optional[str] = Form(None),
231
- split_sentence: bool = Form(True),
232
- normalize_text: bool = Form(False),
233
- output_format: str = Form("srt"),
234
- transcription_model: str = Form("nvidia/parakeet-tdt-0.6b-v3"),
235
- alignment_model: str = Form("LattifAI/Lattice-1"),
236
- ):
237
- # Check if LATTIFAI_API_KEY is set
238
- if not os.environ.get("LATTIFAI_API_KEY"):
239
- return JSONResponse(
240
- status_code=400,
241
- content={
242
- "error": "LATTIFAI_API_KEY is not set. Please set the environment variable or add it to your .env file.",
243
- "help_url": "https://lattifai.com/dashboard/api-keys",
244
- },
245
- )
246
-
247
- if not media_file and not youtube_url and not local_media_path:
248
- return JSONResponse(
249
- status_code=400, content={"error": "Either media file, local media path, or YouTube URL must be provided."}
250
- )
251
-
252
- # Get lazily initialized client
253
- client = get_client()
254
- if not client:
255
- # This should rarely happen due to lazy init, but just in case
256
- return JSONResponse(
257
- status_code=500,
258
- content={
259
- "error": "LattifAI client not initialized. Please check API key configuration.",
260
- },
261
- )
262
-
263
- media_path = None
264
- caption_path = None
265
- temp_files_to_delete = []
266
-
267
- try:
268
- if media_file:
269
- # Save uploaded media file to a temporary location
270
- with tempfile.NamedTemporaryFile(delete=False, suffix=Path(media_file.filename).suffix) as tmp_media:
271
- content = await media_file.read()
272
- tmp_media.write(content)
273
- media_path = tmp_media.name
274
- temp_files_to_delete.append(media_path)
275
-
276
- if caption_file:
277
- # Save uploaded caption file to a temporary location
278
- with tempfile.NamedTemporaryFile(
279
- delete=False, suffix=Path(caption_file.filename).suffix
280
- ) as tmp_caption:
281
- content = await caption_file.read()
282
- tmp_caption.write(content)
283
- caption_path = tmp_caption.name
284
- temp_files_to_delete.append(caption_path)
285
-
286
- elif local_media_path:
287
- media_path = local_media_path
288
- if not Path(media_path).exists():
289
- return JSONResponse(status_code=400, content={"error": f"Local media file not found: {media_path}"})
290
-
291
- if local_caption_path:
292
- caption_path = local_caption_path
293
- if not Path(caption_path).exists():
294
- return JSONResponse(
295
- status_code=400, content={"error": f"Local caption file not found: {caption_path}"}
296
- )
297
-
298
- # Process in thread pool to not block event loop
299
- loop = asyncio.get_event_loop()
300
- result_caption = await loop.run_in_executor(
301
- None,
302
- process_alignment,
303
- media_path,
304
- youtube_url,
305
- youtube_output_dir,
306
- caption_path,
307
- local_output_dir,
308
- split_sentence,
309
- normalize_text,
310
- transcription_model,
311
- alignment_model,
312
- output_format,
313
- )
314
-
315
- # Convert result to dict with specified output format
316
- caption_content = result_caption.to_string(format=output_format)
317
-
318
- return {
319
- "status": "success",
320
- "segments": [
321
- {
322
- "start": seg.start,
323
- "end": seg.end,
324
- "text": seg.text,
325
- "speaker": seg.speaker if hasattr(seg, "speaker") else None,
326
- }
327
- for seg in result_caption.alignments
328
- ],
329
- "caption_content": caption_content,
330
- "output_format": output_format,
331
- }
332
-
333
- except Exception as e:
334
- import traceback
335
-
336
- traceback.print_exc()
337
- return JSONResponse(status_code=500, content={"error": str(e), "traceback": traceback.format_exc()})
338
-
339
-
340
- def process_alignment(
341
- media_path,
342
- youtube_url,
343
- youtube_output_dir,
344
- caption_path,
345
- local_output_dir,
346
- split_sentence,
347
- normalize_text,
348
- transcription_model,
349
- alignment_model,
350
- output_format,
351
- ):
352
- """
353
- Wrapper to call LattifAI client.
354
- Note: Transcription will be automatically triggered when no caption is provided.
355
- """
356
- # Get lazily initialized client
357
- client = get_client()
358
- if not client:
359
- raise RuntimeError("LattifAI client not initialized")
360
-
361
- # Update caption config
362
- client.caption_config.normalize_text = normalize_text
363
-
364
- # Check if alignment model changed - if so, reinitialize aligner
365
- if client.aligner.config.model_name != alignment_model:
366
- print(
367
- f"Alignment model changed from {client.aligner.config.model_name} to {alignment_model}, reinitializing aligner..."
368
- ) # noqa: E501
369
- from lattifai.alignment import Lattice1Aligner
370
-
371
- client.aligner.config.model_name = alignment_model
372
- client.aligner = Lattice1Aligner(config=client.aligner.config)
373
-
374
- # Check if transcription model changed - if so, reinitialize transcriber
375
- if transcription_model != client.transcription_config.model_name:
376
- print(
377
- f"Transcription model changed from {client.transcription_config.model_name} to {transcription_model}, reinitializing transcriber..."
378
- ) # noqa: E501
379
- from lattifai.config import TranscriptionConfig
380
-
381
- client.transcription_config = TranscriptionConfig(model_name=transcription_model)
382
- client._transcriber = None
383
-
384
- if youtube_url:
385
- # If youtube, we use client.youtube
386
- # Note: client.youtube handles download + alignment
387
- # Will try to download YT captions first, if not available, will transcribe
388
-
389
- # Determine output directory
390
- # Default: ~/Downloads/YYYY-MM-DD
391
- if not youtube_output_dir or not youtube_output_dir.strip():
392
- from datetime import datetime
393
-
394
- today = datetime.now().strftime("%Y-%m-%d")
395
- youtube_output_dir = f"~/Downloads/{today}"
396
-
397
- temp_path = Path(youtube_output_dir).expanduser()
398
- temp_path.mkdir(parents=True, exist_ok=True)
399
-
400
- result = client.youtube(
401
- url=youtube_url,
402
- output_dir=temp_path,
403
- use_transcription=False, # Try to download captions first
404
- force_overwrite=True, # No user prompt in server mode
405
- split_sentence=split_sentence,
406
- )
407
- return result
408
- else:
409
- # Local file alignment
410
- output_caption_path = None
411
- if local_output_dir:
412
- output_dir = Path(local_output_dir).expanduser()
413
- output_dir.mkdir(parents=True, exist_ok=True)
414
- stem = Path(media_path).stem
415
- # Prevent overwriting input if names clash, use _LattifAI suffix
416
- output_filename = f"{stem}_LattifAI.{output_format}"
417
- output_caption_path = output_dir / output_filename
418
- print(f"Saving alignment result to: {output_caption_path}")
419
-
420
- # If no caption_path provided, client.alignment will automatically call _transcribe
421
- return client.alignment(
422
- input_media=str(media_path),
423
- input_caption=str(caption_path) if caption_path else None,
424
- output_caption_path=str(output_caption_path) if output_caption_path else None,
425
- split_sentence=split_sentence,
426
- streaming_chunk_secs=None, # Server API default: no streaming
427
- )