lattifai 0.4.5__py3-none-any.whl → 0.4.6__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.
lattifai/__init__.py CHANGED
@@ -1,4 +1,3 @@
1
- import os
2
1
  import sys
3
2
  import warnings
4
3
 
@@ -26,9 +25,9 @@ except ImportError:
26
25
  from importlib_metadata import version
27
26
 
28
27
  try:
29
- __version__ = version('lattifai')
28
+ __version__ = version("lattifai")
30
29
  except Exception:
31
- __version__ = '0.1.0' # fallback version
30
+ __version__ = "0.1.0" # fallback version
32
31
 
33
32
 
34
33
  # Check and auto-install k2 if not present
@@ -39,15 +38,15 @@ def _check_and_install_k2():
39
38
  except ImportError:
40
39
  import subprocess
41
40
 
42
- print('k2 is not installed. Attempting to install k2...')
41
+ print("k2 is not installed. Attempting to install k2...")
43
42
  try:
44
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'install-k2'])
45
- subprocess.check_call([sys.executable, '-m', 'install_k2'])
43
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "install-k2"])
44
+ subprocess.check_call([sys.executable, "-m", "install_k2"])
46
45
  import k2 # Try importing again after installation
47
46
 
48
- print('k2 installed successfully.')
47
+ print("k2 installed successfully.")
49
48
  except Exception as e:
50
- warnings.warn(f'Failed to install k2 automatically. Please install it manually. Error: {e}')
49
+ warnings.warn(f"Failed to install k2 automatically. Please install it manually. Error: {e}")
51
50
  return True
52
51
 
53
52
 
@@ -57,11 +56,11 @@ _check_and_install_k2()
57
56
 
58
57
  # Lazy import for LattifAI to avoid dependency issues during basic import
59
58
  def __getattr__(name):
60
- if name == 'LattifAI':
59
+ if name == "LattifAI":
61
60
  from .client import LattifAI
62
61
 
63
62
  return LattifAI
64
- if name == 'AsyncLattifAI':
63
+ if name == "AsyncLattifAI":
65
64
  from .client import AsyncLattifAI
66
65
 
67
66
  return AsyncLattifAI
@@ -69,21 +68,21 @@ def __getattr__(name):
69
68
 
70
69
 
71
70
  __all__ = [
72
- 'LattifAI', # noqa: F822
73
- 'AsyncLattifAI', # noqa: F822
74
- 'LattifAIError',
75
- 'AudioProcessingError',
76
- 'AudioLoadError',
77
- 'AudioFormatError',
78
- 'SubtitleProcessingError',
79
- 'SubtitleParseError',
80
- 'AlignmentError',
81
- 'LatticeEncodingError',
82
- 'LatticeDecodingError',
83
- 'ModelLoadError',
84
- 'DependencyError',
85
- 'APIError',
86
- 'ConfigurationError',
87
- 'SubtitleIO',
88
- '__version__',
71
+ "LattifAI", # noqa: F822
72
+ "AsyncLattifAI", # noqa: F822
73
+ "LattifAIError",
74
+ "AudioProcessingError",
75
+ "AudioLoadError",
76
+ "AudioFormatError",
77
+ "SubtitleProcessingError",
78
+ "SubtitleParseError",
79
+ "AlignmentError",
80
+ "LatticeEncodingError",
81
+ "LatticeDecodingError",
82
+ "ModelLoadError",
83
+ "DependencyError",
84
+ "APIError",
85
+ "ConfigurationError",
86
+ "SubtitleIO",
87
+ "__version__",
89
88
  ]
lattifai/base_client.py CHANGED
@@ -23,11 +23,11 @@ class BaseAPIClient(ABC):
23
23
  default_headers: Optional[Dict[str, str]] = None,
24
24
  ) -> None:
25
25
  if api_key is None:
26
- api_key = os.environ.get('LATTIFAI_API_KEY')
26
+ api_key = os.environ.get("LATTIFAI_API_KEY")
27
27
  if api_key is None:
28
28
  raise ConfigurationError(
29
- 'The api_key client option must be set either by passing api_key to the client '
30
- 'or by setting the LATTIFAI_API_KEY environment variable'
29
+ "The api_key client option must be set either by passing api_key to the client "
30
+ "or by setting the LATTIFAI_API_KEY environment variable"
31
31
  )
32
32
 
33
33
  self._api_key = api_key
@@ -36,8 +36,8 @@ class BaseAPIClient(ABC):
36
36
  self._max_retries = max_retries
37
37
 
38
38
  headers = {
39
- 'User-Agent': 'LattifAI/Python',
40
- 'Authorization': f'Bearer {self._api_key}',
39
+ "User-Agent": "LattifAI/Python",
40
+ "Authorization": f"Bearer {self._api_key}",
41
41
  }
42
42
  if default_headers:
43
43
  headers.update(default_headers)
@@ -78,7 +78,7 @@ class SyncAPIClient(BaseAPIClient):
78
78
 
79
79
  def post(self, api_endpoint: str, *, json: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
80
80
  """Make a POST request to the specified API endpoint."""
81
- return self._request('POST', api_endpoint, json=json, **kwargs)
81
+ return self._request("POST", api_endpoint, json=json, **kwargs)
82
82
 
83
83
 
84
84
  class AsyncAPIClient(BaseAPIClient):
@@ -123,4 +123,4 @@ class AsyncAPIClient(BaseAPIClient):
123
123
  **kwargs,
124
124
  ) -> httpx.Response:
125
125
  """Make a POST request to the specified API endpoint."""
126
- return await self._request('POST', api_endpoint, json=json, files=files, **kwargs)
126
+ return await self._request("POST", api_endpoint, json=json, files=files, **kwargs)
lattifai/bin/agent.py CHANGED
@@ -5,105 +5,104 @@ Agent command for YouTube workflow
5
5
  import asyncio
6
6
  import os
7
7
  import sys
8
- from typing import List, Optional
8
+ from typing import Optional
9
9
 
10
10
  import click
11
11
  import colorful
12
- from lhotse.utils import Pathlike
13
12
 
14
13
  from lattifai.bin.cli_base import cli
15
14
  from lattifai.io import OUTPUT_SUBTITLE_FORMATS
16
15
 
17
16
 
18
17
  @cli.command()
19
- @click.option('--youtube', '--yt', is_flag=True, help='Process YouTube URL through agentic workflow.')
18
+ @click.option("--youtube", "--yt", is_flag=True, help="Process YouTube URL through agentic workflow.")
20
19
  @click.option(
21
- '-K',
22
- '-L',
23
- '--api-key',
24
- '--api_key',
20
+ "-K",
21
+ "-L",
22
+ "--api-key",
23
+ "--api_key",
25
24
  type=str,
26
- help='LattifAI API key for alignment (overrides LATTIFAI_API_KEY env var).',
25
+ help="LattifAI API key for alignment (overrides LATTIFAI_API_KEY env var).",
27
26
  )
28
27
  @click.option(
29
- '-G',
30
- '--gemini-api-key',
31
- '--gemini_api_key',
28
+ "-G",
29
+ "--gemini-api-key",
30
+ "--gemini_api_key",
32
31
  type=str,
33
- help='Gemini API key for transcription (overrides GEMINI_API_KEY env var).',
32
+ help="Gemini API key for transcription (overrides GEMINI_API_KEY env var).",
34
33
  )
35
34
  @click.option(
36
- '-D',
37
- '--device',
38
- type=click.Choice(['cpu', 'cuda', 'mps'], case_sensitive=False),
39
- default='cpu',
40
- help='Device to use for inference.',
35
+ "-D",
36
+ "--device",
37
+ type=click.Choice(["cpu", "cuda", "mps"], case_sensitive=False),
38
+ default="cpu",
39
+ help="Device to use for inference.",
41
40
  )
42
41
  @click.option(
43
- '-M',
44
- '--model-name-or-path',
45
- '--model_name_or_path',
42
+ "-M",
43
+ "--model-name-or-path",
44
+ "--model_name_or_path",
46
45
  type=str,
47
- default='Lattifai/Lattice-1-Alpha',
48
- help='Model name or path for alignment.',
46
+ default="Lattifai/Lattice-1-Alpha",
47
+ help="Model name or path for alignment.",
49
48
  )
50
49
  @click.option(
51
- '--media-format',
52
- '--media_format',
50
+ "--media-format",
51
+ "--media_format",
53
52
  type=click.Choice(
54
- ['mp3', 'wav', 'm4a', 'aac', 'opus', 'mp4', 'webm', 'mkv', 'avi', 'mov', 'flv', 'wmv', 'mpeg', 'mpg', '3gp'],
53
+ ["mp3", "wav", "m4a", "aac", "opus", "mp4", "webm", "mkv", "avi", "mov", "flv", "wmv", "mpeg", "mpg", "3gp"],
55
54
  case_sensitive=False,
56
55
  ),
57
- default='mp4',
58
- help='Media format for YouTube download (audio or video).',
56
+ default="mp4",
57
+ help="Media format for YouTube download (audio or video).",
59
58
  )
60
59
  @click.option(
61
- '--output-format',
62
- '--output_format',
60
+ "--output-format",
61
+ "--output_format",
63
62
  type=click.Choice(OUTPUT_SUBTITLE_FORMATS, case_sensitive=False),
64
- default='srt',
65
- help='Subtitle output format.',
63
+ default="srt",
64
+ help="Subtitle output format.",
66
65
  )
67
66
  @click.option(
68
- '--output-dir',
69
- '--output_dir',
67
+ "--output-dir",
68
+ "--output_dir",
70
69
  type=click.Path(exists=False, file_okay=False, dir_okay=True),
71
- help='Output directory for generated files (default: current directory).',
70
+ help="Output directory for generated files (default: current directory).",
72
71
  )
73
72
  @click.option(
74
- '--max-retries',
75
- '--max_retries',
73
+ "--max-retries",
74
+ "--max_retries",
76
75
  type=int,
77
76
  default=0,
78
- help='Maximum number of retries for failed steps.',
77
+ help="Maximum number of retries for failed steps.",
79
78
  )
80
79
  @click.option(
81
- '-S',
82
- '--split-sentence',
83
- '--split_sentence',
80
+ "-S",
81
+ "--split-sentence",
82
+ "--split_sentence",
84
83
  is_flag=True,
85
84
  default=False,
86
- help='Re-segment subtitles by semantics.',
85
+ help="Re-segment subtitles by semantics.",
87
86
  )
88
87
  @click.option(
89
- '--word-level',
90
- '--word_level',
88
+ "--word-level",
89
+ "--word_level",
91
90
  is_flag=True,
92
91
  default=False,
93
- help='Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).',
92
+ help="Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).",
94
93
  )
95
- @click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging.')
96
- @click.option('--force', '-f', is_flag=True, help='Force overwrite existing files without confirmation.')
97
- @click.argument('url', type=str, required=True)
94
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging.")
95
+ @click.option("--force", "-f", is_flag=True, help="Force overwrite existing files without confirmation.")
96
+ @click.argument("url", type=str, required=True)
98
97
  def agent(
99
98
  youtube: bool,
100
99
  url: str,
101
100
  api_key: Optional[str] = None,
102
101
  gemini_api_key: Optional[str] = None,
103
- device: str = 'cpu',
104
- model_name_or_path: str = 'Lattifai/Lattice-1-Alpha',
105
- media_format: str = 'mp4',
106
- output_format: str = 'srt',
102
+ device: str = "cpu",
103
+ model_name_or_path: str = "Lattifai/Lattice-1-Alpha",
104
+ media_format: str = "mp4",
105
+ output_format: str = "srt",
107
106
  output_dir: Optional[str] = None,
108
107
  max_retries: int = 0,
109
108
  split_sentence: bool = False,
@@ -121,22 +120,22 @@ def agent(
121
120
  """
122
121
 
123
122
  if not youtube:
124
- click.echo(colorful.red('❌ Please specify a workflow type. Use --youtube for YouTube processing.'))
123
+ click.echo(colorful.red("❌ Please specify a workflow type. Use --youtube for YouTube processing."))
125
124
  return
126
125
 
127
126
  # Setup logging
128
127
  import logging
129
128
 
130
129
  log_level = logging.DEBUG if verbose else logging.INFO
131
- logging.basicConfig(level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
130
+ logging.basicConfig(level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
132
131
 
133
132
  # Set default output directory
134
133
  if not output_dir:
135
134
  output_dir = os.getcwd()
136
135
 
137
136
  # Get API keys
138
- lattifai_api_key = api_key or os.getenv('LATTIFAI_API_KEY')
139
- gemini_key = gemini_api_key or os.getenv('GEMINI_API_KEY')
137
+ lattifai_api_key = api_key or os.getenv("LATTIFAI_API_KEY")
138
+ gemini_key = gemini_api_key or os.getenv("GEMINI_API_KEY")
140
139
 
141
140
  try:
142
141
  # Run the YouTube workflow
@@ -158,7 +157,7 @@ def agent(
158
157
  )
159
158
 
160
159
  except KeyboardInterrupt:
161
- click.echo(colorful.yellow('\n⚠️ Process interrupted by user'))
160
+ click.echo(colorful.yellow("\n⚠️ Process interrupted by user"))
162
161
  sys.exit(1)
163
162
  except Exception as e:
164
163
  from lattifai.errors import LattifAIError
@@ -166,12 +165,12 @@ def agent(
166
165
  # Extract error message without support info (to avoid duplication)
167
166
  if isinstance(e, LattifAIError):
168
167
  # Use the get_message() method which includes proper formatting
169
- click.echo(colorful.red('❌ Workflow failed:'))
168
+ click.echo(colorful.red("❌ Workflow failed:"))
170
169
  click.echo(e.get_message())
171
170
  # Show support info once at the end
172
171
  click.echo(e.get_support_info())
173
172
  else:
174
- click.echo(colorful.red(f'❌ Workflow failed: {str(e)}'))
173
+ click.echo(colorful.red(f"❌ Workflow failed: {str(e)}"))
175
174
 
176
175
  if verbose:
177
176
  import traceback
@@ -196,12 +195,12 @@ async def _run_youtube_workflow(
196
195
  ):
197
196
  """Run the YouTube processing workflow"""
198
197
 
199
- click.echo(colorful.cyan('🚀 LattifAI Agentic Workflow - YouTube Processing'))
200
- click.echo(f'📺 YouTube URL: {url}')
201
- click.echo(f'🎬 Media format: {media_format}')
202
- click.echo(f'📝 Output format: {output_format}')
203
- click.echo(f'📁 Output directory: {output_dir}')
204
- click.echo(f'🔄 Max retries: {max_retries}')
198
+ click.echo(colorful.cyan("🚀 LattifAI Agentic Workflow - YouTube Processing"))
199
+ click.echo(f"📺 YouTube URL: {url}")
200
+ click.echo(f"🎬 Media format: {media_format}")
201
+ click.echo(f"📝 Output format: {output_format}")
202
+ click.echo(f"📁 Output directory: {output_dir}")
203
+ click.echo(f"🔄 Max retries: {max_retries}")
205
204
  click.echo()
206
205
 
207
206
  # Import workflow components
@@ -235,12 +234,12 @@ async def _run_youtube_workflow(
235
234
  )
236
235
 
237
236
  # Display results
238
- click.echo(colorful.bold_white_on_green('🎉 Workflow completed successfully!'))
237
+ click.echo(colorful.bold_white_on_green("🎉 Workflow completed successfully!"))
239
238
  click.echo()
240
- click.echo(colorful.bold_white_on_green('📊 Results:'))
239
+ click.echo(colorful.bold_white_on_green("📊 Results:"))
241
240
 
242
241
  # Show metadata
243
- metadata = result.get('metadata', {})
242
+ metadata = result.get("metadata", {})
244
243
  if metadata:
245
244
  click.echo(f'🎬 Title: {metadata.get("title", "Unknown")}')
246
245
  click.echo(f'👤 Uploader: {metadata.get("uploader", "Unknown").strip()}')
@@ -248,18 +247,18 @@ async def _run_youtube_workflow(
248
247
  click.echo()
249
248
 
250
249
  # Show exported files
251
- exported_files = result.get('exported_files', {})
250
+ exported_files = result.get("exported_files", {})
252
251
  if exported_files:
253
- click.echo(colorful.bold_white_on_green('📄 Generated subtitle files:'))
252
+ click.echo(colorful.bold_white_on_green("📄 Generated subtitle files:"))
254
253
  for format_name, file_path in exported_files.items():
255
- click.echo(f' {format_name.upper()}: {file_path}')
254
+ click.echo(f" {format_name.upper()}: {file_path}")
256
255
  click.echo()
257
256
 
258
257
  # Show subtitle count
259
- subtitle_count = result.get('subtitle_count', 0)
260
- click.echo(f'📝 Generated {subtitle_count} subtitle segments')
258
+ subtitle_count = result.get("subtitle_count", 0)
259
+ click.echo(f"📝 Generated {subtitle_count} subtitle segments")
261
260
 
262
- click.echo(colorful.bold_white_on_green('✨ All done! Your aligned subtitles are ready.'))
261
+ click.echo(colorful.bold_white_on_green("✨ All done! Your aligned subtitles are ready."))
263
262
 
264
263
 
265
264
  # Add dependencies check
@@ -268,26 +267,26 @@ def check_dependencies():
268
267
  missing_deps = []
269
268
 
270
269
  try:
271
- from google import genai
270
+ from google import genai # noqa: F401
272
271
  except ImportError:
273
- missing_deps.append('google-genai')
272
+ missing_deps.append("google-genai")
274
273
 
275
274
  try:
276
- import yt_dlp
275
+ import yt_dlp # noqa: F401
277
276
  except ImportError:
278
- missing_deps.append('yt-dlp')
277
+ missing_deps.append("yt-dlp")
279
278
 
280
279
  try:
281
- from dotenv import load_dotenv
280
+ from dotenv import load_dotenv # noqa: F401
282
281
  except ImportError:
283
- missing_deps.append('python-dotenv')
282
+ missing_deps.append("python-dotenv")
284
283
 
285
284
  if missing_deps:
286
- click.echo(colorful.red('❌ Missing required dependencies:'))
285
+ click.echo(colorful.red("❌ Missing required dependencies:"))
287
286
  for dep in missing_deps:
288
- click.echo(f' - {dep}')
287
+ click.echo(f" - {dep}")
289
288
  click.echo()
290
- click.echo('Install them with:')
289
+ click.echo("Install them with:")
291
290
  click.echo(f' pip install {" ".join(missing_deps)}')
292
291
  return False
293
292
 
@@ -299,7 +298,7 @@ if not check_dependencies():
299
298
  pass # Don't exit on import, let the command handle it
300
299
 
301
300
 
302
- if __name__ == '__main__':
301
+ if __name__ == "__main__":
303
302
  import os
304
303
 
305
304
  from dotenv import find_dotenv, load_dotenv
@@ -309,14 +308,14 @@ if __name__ == '__main__':
309
308
  asyncio.run(
310
309
  _run_youtube_workflow(
311
310
  # url='https://www.youtube.com/watch?v=7nv1snJRCEI',
312
- url='https://www.youtube.com/watch?v=DQacCB9tDaw',
313
- lattifai_api_key=os.getenv('LATTIFAI_API_KEY'),
314
- gemini_api_key=os.getenv('GEMINI_API_KEY', ''),
315
- device='mps',
316
- model_name_or_path='Lattifai/Lattice-1-Alpha',
317
- media_format='mp3',
318
- output_format='TextGrid',
319
- output_dir='~/Downloads/lattifai_youtube',
311
+ url="https://www.youtube.com/watch?v=DQacCB9tDaw",
312
+ lattifai_api_key=os.getenv("LATTIFAI_API_KEY"),
313
+ gemini_api_key=os.getenv("GEMINI_API_KEY", ""),
314
+ device="mps",
315
+ model_name_or_path="Lattifai/Lattice-1-Alpha",
316
+ media_format="mp3",
317
+ output_format="TextGrid",
318
+ output_dir="~/Downloads/lattifai_youtube",
320
319
  max_retries=0,
321
320
  split_sentence=True,
322
321
  word_level=False,