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/bin/align.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import os
3
- from pathlib import Path
4
3
 
5
4
  import click
6
5
  import colorful
@@ -13,74 +12,74 @@ from lattifai.io import INPUT_SUBTITLE_FORMATS, OUTPUT_SUBTITLE_FORMATS
13
12
 
14
13
  @cli.command()
15
14
  @click.option(
16
- '-F',
17
- '--input_format',
18
- '--input-format',
15
+ "-F",
16
+ "--input_format",
17
+ "--input-format",
19
18
  type=click.Choice(INPUT_SUBTITLE_FORMATS, case_sensitive=False),
20
- default='auto',
21
- help='Input subtitle format.',
19
+ default="auto",
20
+ help="Input subtitle format.",
22
21
  )
23
22
  @click.option(
24
- '-S',
25
- '--split-sentence',
26
- '--split_sentence',
23
+ "-S",
24
+ "--split-sentence",
25
+ "--split_sentence",
27
26
  is_flag=True,
28
27
  default=False,
29
- help='Re-segment subtitles by semantics.',
28
+ help="Re-segment subtitles by semantics.",
30
29
  )
31
30
  @click.option(
32
- '-W',
33
- '--word-level',
34
- '--word_level',
31
+ "-W",
32
+ "--word-level",
33
+ "--word_level",
35
34
  is_flag=True,
36
35
  default=False,
37
- help='Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).',
36
+ help="Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).",
38
37
  )
39
38
  @click.option(
40
- '-D',
41
- '--device',
42
- type=click.Choice(['cpu', 'cuda', 'mps'], case_sensitive=False),
43
- default='cpu',
44
- help='Device to use for inference.',
39
+ "-D",
40
+ "--device",
41
+ type=click.Choice(["cpu", "cuda", "mps"], case_sensitive=False),
42
+ default="cpu",
43
+ help="Device to use for inference.",
45
44
  )
46
45
  @click.option(
47
- '-M',
48
- '--model-name-or-path',
49
- '--model_name_or_path',
46
+ "-M",
47
+ "--model-name-or-path",
48
+ "--model_name_or_path",
50
49
  type=str,
51
- default='Lattifai/Lattice-1-Alpha',
52
- help='Model name or path for alignment.',
50
+ default="Lattifai/Lattice-1-Alpha",
51
+ help="Model name or path for alignment.",
53
52
  )
54
53
  @click.option(
55
- '-K',
56
- '-L',
57
- '--api-key',
58
- '--api_key',
54
+ "-K",
55
+ "-L",
56
+ "--api-key",
57
+ "--api_key",
59
58
  type=str,
60
59
  default=None,
61
- help='API key for LattifAI.',
60
+ help="API key for LattifAI.",
62
61
  )
63
62
  @click.argument(
64
- 'input_media_path',
63
+ "input_media_path",
65
64
  type=click.Path(exists=True, dir_okay=False),
66
65
  )
67
66
  @click.argument(
68
- 'input_subtitle_path',
67
+ "input_subtitle_path",
69
68
  type=click.Path(exists=True, dir_okay=False),
70
69
  )
71
70
  @click.argument(
72
- 'output_subtitle_path',
71
+ "output_subtitle_path",
73
72
  type=click.Path(allow_dash=True),
74
73
  )
75
74
  def align(
76
75
  input_media_path: Pathlike,
77
76
  input_subtitle_path: Pathlike,
78
77
  output_subtitle_path: Pathlike,
79
- input_format: str = 'auto',
78
+ input_format: str = "auto",
80
79
  split_sentence: bool = False,
81
80
  word_level: bool = False,
82
- device: str = 'cpu',
83
- model_name_or_path: str = 'Lattifai/Lattice-1-Alpha',
81
+ device: str = "cpu",
82
+ model_name_or_path: str = "Lattifai/Lattice-1-Alpha",
84
83
  api_key: str = None,
85
84
  ):
86
85
  """
@@ -96,129 +95,129 @@ def align(
96
95
  return_details=word_level,
97
96
  output_subtitle_path=output_subtitle_path,
98
97
  )
99
- click.echo(colorful.green(f'✅ Alignment completed successfully: {output_subtitle_path}'))
98
+ click.echo(colorful.green(f"✅ Alignment completed successfully: {output_subtitle_path}"))
100
99
  except Exception as e:
101
100
  from lattifai.errors import LattifAIError
102
101
 
103
102
  # Display error message
104
103
  if isinstance(e, LattifAIError):
105
- click.echo(colorful.red('❌ Alignment failed:'))
104
+ click.echo(colorful.red("❌ Alignment failed:"))
106
105
  click.echo(e.get_message())
107
106
  # Show support info
108
107
  click.echo(e.get_support_info())
109
108
  else:
110
- click.echo(colorful.red(f'❌ Alignment failed: {str(e)}'))
109
+ click.echo(colorful.red(f"❌ Alignment failed: {str(e)}"))
111
110
 
112
- raise click.ClickException('Alignment failed')
111
+ raise click.ClickException("Alignment failed")
113
112
 
114
113
 
115
114
  @cli.command()
116
115
  @click.option(
117
- '-M',
118
- '--media-format',
119
- '--media_format',
116
+ "-M",
117
+ "--media-format",
118
+ "--media_format",
120
119
  type=click.Choice(
121
120
  [
122
121
  # Audio formats
123
- 'mp3',
124
- 'wav',
125
- 'm4a',
126
- 'aac',
127
- 'flac',
128
- 'ogg',
129
- 'opus',
130
- 'aiff',
122
+ "mp3",
123
+ "wav",
124
+ "m4a",
125
+ "aac",
126
+ "flac",
127
+ "ogg",
128
+ "opus",
129
+ "aiff",
131
130
  # Video formats
132
- 'mp4',
133
- 'webm',
134
- 'mkv',
135
- 'avi',
136
- 'mov',
131
+ "mp4",
132
+ "webm",
133
+ "mkv",
134
+ "avi",
135
+ "mov",
137
136
  ],
138
137
  case_sensitive=False,
139
138
  ),
140
- default='mp3',
141
- help='Media format for YouTube download (audio or video).',
139
+ default="mp3",
140
+ help="Media format for YouTube download (audio or video).",
142
141
  )
143
142
  @click.option(
144
- '-S',
145
- '--split-sentence',
146
- '--split_sentence',
143
+ "-S",
144
+ "--split-sentence",
145
+ "--split_sentence",
147
146
  is_flag=True,
148
147
  default=False,
149
- help='Re-segment subtitles by semantics.',
148
+ help="Re-segment subtitles by semantics.",
150
149
  )
151
150
  @click.option(
152
- '-W',
153
- '--word-level',
154
- '--word_level',
151
+ "-W",
152
+ "--word-level",
153
+ "--word_level",
155
154
  is_flag=True,
156
155
  default=False,
157
- help='Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).',
156
+ help="Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).",
158
157
  )
159
158
  @click.option(
160
- '-O',
161
- '--output-dir',
162
- '--output_dir',
159
+ "-O",
160
+ "--output-dir",
161
+ "--output_dir",
163
162
  type=click.Path(file_okay=False, dir_okay=True, writable=True),
164
- default='.',
165
- help='Output directory (default: current directory).',
163
+ default=".",
164
+ help="Output directory (default: current directory).",
166
165
  )
167
166
  @click.option(
168
- '-D',
169
- '--device',
170
- type=click.Choice(['cpu', 'cuda', 'mps'], case_sensitive=False),
171
- default='cpu',
172
- help='Device to use for inference.',
167
+ "-D",
168
+ "--device",
169
+ type=click.Choice(["cpu", "cuda", "mps"], case_sensitive=False),
170
+ default="cpu",
171
+ help="Device to use for inference.",
173
172
  )
174
173
  @click.option(
175
- '-M',
176
- '--model-name-or-path',
177
- '--model_name_or_path',
174
+ "-M",
175
+ "--model-name-or-path",
176
+ "--model_name_or_path",
178
177
  type=str,
179
- default='Lattifai/Lattice-1-Alpha',
180
- help='Model name or path for alignment.',
178
+ default="Lattifai/Lattice-1-Alpha",
179
+ help="Model name or path for alignment.",
181
180
  )
182
181
  @click.option(
183
- '-K',
184
- '-L',
185
- '--api-key',
186
- '--api_key',
182
+ "-K",
183
+ "-L",
184
+ "--api-key",
185
+ "--api_key",
187
186
  type=str,
188
187
  default=None,
189
- help='API key for LattifAI.',
188
+ help="API key for LattifAI.",
190
189
  )
191
190
  @click.option(
192
- '-G',
193
- '--gemini-api-key',
194
- '--gemini_api_key',
191
+ "-G",
192
+ "--gemini-api-key",
193
+ "--gemini_api_key",
195
194
  type=str,
196
195
  default=None,
197
- help='Gemini API key for transcription fallback when subtitles are unavailable.',
196
+ help="Gemini API key for transcription fallback when subtitles are unavailable.",
198
197
  )
199
198
  @click.option(
200
- '-F',
201
- '--output-format',
202
- '--output_format',
199
+ "-F",
200
+ "--output-format",
201
+ "--output_format",
203
202
  type=click.Choice(OUTPUT_SUBTITLE_FORMATS, case_sensitive=False),
204
- default='vtt',
205
- help='Subtitle output format.',
203
+ default="vtt",
204
+ help="Subtitle output format.",
206
205
  )
207
206
  @click.argument(
208
- 'yt_url',
207
+ "yt_url",
209
208
  type=str,
210
209
  )
211
210
  def youtube(
212
211
  yt_url: str,
213
- media_format: str = 'mp3',
212
+ media_format: str = "mp3",
214
213
  split_sentence: bool = False,
215
214
  word_level: bool = False,
216
- output_dir: str = '.',
217
- device: str = 'cpu',
218
- model_name_or_path: str = 'Lattifai/Lattice-1-Alpha',
215
+ output_dir: str = ".",
216
+ device: str = "cpu",
217
+ model_name_or_path: str = "Lattifai/Lattice-1-Alpha",
219
218
  api_key: str = None,
220
219
  gemini_api_key: str = None,
221
- output_format: str = 'vtt',
220
+ output_format: str = "vtt",
222
221
  ):
223
222
  """
224
223
  Download media and subtitles from YouTube for further alignment.
@@ -227,7 +226,7 @@ def youtube(
227
226
  from lattifai.workflows.youtube import YouTubeDownloader, YouTubeSubtitleAgent
228
227
 
229
228
  # Get Gemini API key
230
- gemini_key = gemini_api_key or os.getenv('GEMINI_API_KEY')
229
+ gemini_key = gemini_api_key or os.getenv("GEMINI_API_KEY")
231
230
 
232
231
  async def _process():
233
232
  # Initialize components with their configuration (only config, not runtime params)
@@ -258,27 +257,27 @@ def youtube(
258
257
  result = asyncio.run(_process())
259
258
 
260
259
  # Display results
261
- click.echo(colorful.green('✅ Processing completed!'))
260
+ click.echo(colorful.green("✅ Processing completed!"))
262
261
  click.echo()
263
262
 
264
263
  # Show metadata
265
- metadata = result.get('metadata', {})
264
+ metadata = result.get("metadata", {})
266
265
  if metadata:
267
266
  click.echo(f'🎬 Title: {metadata.get("title", "Unknown")}')
268
267
  click.echo(f'⏱️ Duration: {metadata.get("duration", 0)} seconds')
269
268
  click.echo()
270
269
 
271
270
  # Show exported files
272
- exported_files = result.get('exported_files', {})
271
+ exported_files = result.get("exported_files", {})
273
272
  if exported_files:
274
- click.echo(colorful.green('📄 Generated subtitle files:'))
273
+ click.echo(colorful.green("📄 Generated subtitle files:"))
275
274
  for format_name, file_path in exported_files.items():
276
- click.echo(f' {format_name}: {file_path}')
275
+ click.echo(f" {format_name}: {file_path}")
277
276
  click.echo()
278
277
 
279
278
  # Show subtitle count
280
- subtitle_count = result.get('subtitle_count', 0)
281
- click.echo(f'📝 Generated {subtitle_count} subtitle segments')
279
+ subtitle_count = result.get("subtitle_count", 0)
280
+ click.echo(f"📝 Generated {subtitle_count} subtitle segments")
282
281
 
283
282
  except Exception as e:
284
283
  from lattifai.errors import LattifAIError
@@ -286,11 +285,11 @@ def youtube(
286
285
  # Extract error message without support info (to avoid duplication)
287
286
  if isinstance(e, LattifAIError):
288
287
  # Use the get_message() method which includes proper formatting
289
- click.echo(colorful.red('❌ Failed to process YouTube URL:'))
288
+ click.echo(colorful.red("❌ Failed to process YouTube URL:"))
290
289
  click.echo(e.get_message())
291
290
  # Show support info once at the end
292
291
  click.echo(e.get_support_info())
293
292
  else:
294
- click.echo(colorful.red(f'❌ Failed to process YouTube URL: {str(e)}'))
293
+ click.echo(colorful.red(f"❌ Failed to process YouTube URL: {str(e)}"))
295
294
 
296
- raise click.ClickException('Processing failed')
295
+ raise click.ClickException("Processing failed")
lattifai/bin/cli_base.py CHANGED
@@ -15,11 +15,11 @@ def cli():
15
15
  load_dotenv(find_dotenv(usecwd=True))
16
16
 
17
17
  logging.basicConfig(
18
- format='%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s',
18
+ format="%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s",
19
19
  level=logging.INFO,
20
20
  )
21
21
 
22
22
  import os
23
23
 
24
- os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
25
- os.environ['TOKENIZERS_PARALLELISM'] = 'FALSE'
24
+ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
25
+ os.environ["TOKENIZERS_PARALLELISM"] = "FALSE"
lattifai/bin/subtitle.py CHANGED
@@ -16,11 +16,11 @@ def subtitle():
16
16
 
17
17
  @subtitle.command()
18
18
  @click.argument(
19
- 'input_subtitle_path',
19
+ "input_subtitle_path",
20
20
  type=click.Path(exists=True, dir_okay=False),
21
21
  )
22
22
  @click.argument(
23
- 'output_subtitle_path',
23
+ "output_subtitle_path",
24
24
  type=click.Path(allow_dash=True),
25
25
  )
26
26
  def convert(
@@ -30,7 +30,7 @@ def convert(
30
30
  """
31
31
  Convert subtitle file to another format.
32
32
  """
33
- if str(output_subtitle_path).lower().endswith('.TextGrid'.lower()):
33
+ if str(output_subtitle_path).lower().endswith(".TextGrid".lower()):
34
34
  from lattifai.io import SubtitleIO
35
35
 
36
36
  alignments = SubtitleIO.read(input_subtitle_path)
@@ -44,30 +44,30 @@ def convert(
44
44
 
45
45
 
46
46
  @subtitle.command()
47
- @click.argument('url', type=str, required=True)
47
+ @click.argument("url", type=str, required=True)
48
48
  @click.option(
49
- '--output-dir',
50
- '--output_dir',
51
- '-o',
49
+ "--output-dir",
50
+ "--output_dir",
51
+ "-o",
52
52
  type=click.Path(file_okay=False, dir_okay=True),
53
- default='.',
54
- help='Output directory for downloaded subtitle files (default: current directory).',
53
+ default=".",
54
+ help="Output directory for downloaded subtitle files (default: current directory).",
55
55
  )
56
56
  @click.option(
57
- '--output-format',
58
- '--output_format',
59
- '-f',
60
- type=click.Choice(SUBTITLE_FORMATS + ['best'], case_sensitive=False),
61
- default='best',
62
- help='Preferred subtitle format to download (default: best available).',
57
+ "--output-format",
58
+ "--output_format",
59
+ "-f",
60
+ type=click.Choice(SUBTITLE_FORMATS + ["best"], case_sensitive=False),
61
+ default="best",
62
+ help="Preferred subtitle format to download (default: best available).",
63
63
  )
64
- @click.option('--force-overwrite', '-F', is_flag=True, help='Overwrite existing files without prompting.')
64
+ @click.option("--force-overwrite", "-F", is_flag=True, help="Overwrite existing files without prompting.")
65
65
  @click.option(
66
- '--lang',
67
- '-l',
68
- '-L',
69
- '--subtitle-lang',
70
- '--subtitle_lang',
66
+ "--lang",
67
+ "-l",
68
+ "-L",
69
+ "--subtitle-lang",
70
+ "--subtitle_lang",
71
71
  type=str,
72
72
  help='Specific subtitle language/track to download (e.g., "en").',
73
73
  )
@@ -88,8 +88,8 @@ def download(
88
88
 
89
89
  # Validate URL format
90
90
  if not _is_valid_youtube_url(url):
91
- click.echo(f'Error: Invalid YouTube URL format: {url}', err=True)
92
- click.echo('Please provide a valid YouTube URL (e.g., https://www.youtube.com/watch?v=VIDEO_ID)', err=True)
91
+ click.echo(f"Error: Invalid YouTube URL format: {url}", err=True)
92
+ click.echo("Please provide a valid YouTube URL (e.g., https://www.youtube.com/watch?v=VIDEO_ID)", err=True)
93
93
  raise click.Abort()
94
94
 
95
95
  # Convert relative path to absolute
@@ -98,13 +98,13 @@ def download(
98
98
  # Create output directory if it doesn't exist
99
99
  output_path.mkdir(parents=True, exist_ok=True)
100
100
 
101
- click.echo(f'Downloading subtitles from: {url}')
102
- click.echo(f' Output directory: {output_path}')
103
- click.echo(f' Preferred format: {output_format}')
101
+ click.echo(f"Downloading subtitles from: {url}")
102
+ click.echo(f" Output directory: {output_path}")
103
+ click.echo(f" Preferred format: {output_format}")
104
104
  if lang:
105
- click.echo(f' Subtitle language: {lang}')
105
+ click.echo(f" Subtitle language: {lang}")
106
106
  else:
107
- click.echo(' Subtitle language: All available')
107
+ click.echo(" Subtitle language: All available")
108
108
 
109
109
  # Initialize downloader and download
110
110
  downloader = YouTubeDownloader()
@@ -119,28 +119,28 @@ def download(
119
119
  )
120
120
 
121
121
  if result:
122
- click.echo('✅ Subtitles downloaded successfully!')
122
+ click.echo("✅ Subtitles downloaded successfully!")
123
123
  return result
124
124
  else:
125
- click.echo('⚠️ No subtitles available for this video')
125
+ click.echo("⚠️ No subtitles available for this video")
126
126
  return None
127
127
 
128
128
  except Exception as e:
129
- click.echo(f'❌ Error downloading subtitles: {str(e)}', err=True)
129
+ click.echo(f"❌ Error downloading subtitles: {str(e)}", err=True)
130
130
  raise click.Abort()
131
131
 
132
132
  # Run the async function
133
133
  result = asyncio.run(download_subtitles())
134
134
 
135
135
  if result:
136
- if result == 'gemini':
137
- click.echo('✨ Gemini transcription selected (use the agent command to transcribe)')
136
+ if result == "gemini":
137
+ click.echo("✨ Gemini transcription selected (use the agent command to transcribe)")
138
138
  else:
139
- click.echo(f'📄 Subtitle file saved to: {result}')
139
+ click.echo(f"📄 Subtitle file saved to: {result}")
140
140
 
141
141
 
142
142
  @subtitle.command()
143
- @click.argument('url', type=str, required=True)
143
+ @click.argument("url", type=str, required=True)
144
144
  def list_subs(url: str):
145
145
  """
146
146
  List available subtitle tracks for a YouTube video.
@@ -152,11 +152,11 @@ def list_subs(url: str):
152
152
 
153
153
  # Validate URL format
154
154
  if not _is_valid_youtube_url(url):
155
- click.echo(f'Error: Invalid YouTube URL format: {url}', err=True)
156
- click.echo('Please provide a valid YouTube URL (e.g., https://www.youtube.com/watch?v=VIDEO_ID)', err=True)
155
+ click.echo(f"Error: Invalid YouTube URL format: {url}", err=True)
156
+ click.echo("Please provide a valid YouTube URL (e.g., https://www.youtube.com/watch?v=VIDEO_ID)", err=True)
157
157
  raise click.Abort()
158
158
 
159
- click.echo(f'Listing available subtitles for: {url}')
159
+ click.echo(f"Listing available subtitles for: {url}")
160
160
 
161
161
  # Initialize downloader
162
162
  downloader = YouTubeDownloader()
@@ -166,20 +166,20 @@ def list_subs(url: str):
166
166
  result = await downloader.list_available_subtitles(url)
167
167
 
168
168
  if result:
169
- click.echo('📋 Available subtitle tracks:')
169
+ click.echo("📋 Available subtitle tracks:")
170
170
  for subtitle_info in result:
171
171
  click.echo(f' 🎬 Language: {subtitle_info["language"]} - {subtitle_info["name"]}')
172
172
  click.echo(f' 📄 Formats: {", ".join(subtitle_info["formats"])}')
173
173
  click.echo()
174
174
 
175
- click.echo('💡 To download a specific track, use:')
175
+ click.echo("💡 To download a specific track, use:")
176
176
  click.echo(f' lattifai subtitle download "{url}" --lang <language_code>')
177
177
  click.echo(' Example: lattifai subtitle download "{}" --lang en-JkeT_87f4cc'.format(url))
178
178
  else:
179
- click.echo('⚠️ No subtitles available for this video')
179
+ click.echo("⚠️ No subtitles available for this video")
180
180
 
181
181
  except Exception as e:
182
- click.echo(f'❌ Error listing subtitles: {str(e)}', err=True)
182
+ click.echo(f"❌ Error listing subtitles: {str(e)}", err=True)
183
183
  raise click.Abort()
184
184
 
185
185
  # Run the async function
@@ -199,9 +199,9 @@ def _is_valid_youtube_url(url: str) -> bool:
199
199
  import re
200
200
 
201
201
  patterns = [
202
- r'(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/shorts/)([a-zA-Z0-9_-]{11})',
203
- r'youtube\.com/embed/([a-zA-Z0-9_-]{11})',
204
- r'youtube\.com/v/([a-zA-Z0-9_-]{11})',
202
+ r"(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/shorts/)([a-zA-Z0-9_-]{11})",
203
+ r"youtube\.com/embed/([a-zA-Z0-9_-]{11})",
204
+ r"youtube\.com/v/([a-zA-Z0-9_-]{11})",
205
205
  ]
206
206
 
207
207
  for pattern in patterns: