lattifai 0.4.5__py3-none-any.whl → 1.0.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.
- lattifai/__init__.py +61 -47
- lattifai/alignment/__init__.py +6 -0
- lattifai/alignment/lattice1_aligner.py +119 -0
- lattifai/alignment/lattice1_worker.py +185 -0
- lattifai/{tokenizer → alignment}/phonemizer.py +4 -4
- lattifai/alignment/segmenter.py +166 -0
- lattifai/{tokenizer → alignment}/tokenizer.py +244 -169
- lattifai/audio2.py +211 -0
- lattifai/caption/__init__.py +20 -0
- lattifai/caption/caption.py +1275 -0
- lattifai/{io → caption}/gemini_reader.py +30 -30
- lattifai/{io → caption}/gemini_writer.py +17 -17
- lattifai/{io → caption}/supervision.py +4 -3
- lattifai/caption/text_parser.py +145 -0
- lattifai/cli/__init__.py +17 -0
- lattifai/cli/alignment.py +153 -0
- lattifai/cli/caption.py +204 -0
- lattifai/cli/server.py +19 -0
- lattifai/cli/transcribe.py +197 -0
- lattifai/cli/youtube.py +128 -0
- lattifai/client.py +460 -251
- lattifai/config/__init__.py +20 -0
- lattifai/config/alignment.py +73 -0
- lattifai/config/caption.py +178 -0
- lattifai/config/client.py +46 -0
- lattifai/config/diarization.py +67 -0
- lattifai/config/media.py +335 -0
- lattifai/config/transcription.py +84 -0
- lattifai/diarization/__init__.py +5 -0
- lattifai/diarization/lattifai.py +89 -0
- lattifai/errors.py +98 -91
- lattifai/logging.py +116 -0
- lattifai/mixin.py +552 -0
- lattifai/server/app.py +420 -0
- lattifai/transcription/__init__.py +76 -0
- lattifai/transcription/base.py +108 -0
- lattifai/transcription/gemini.py +219 -0
- lattifai/transcription/lattifai.py +103 -0
- lattifai/{workflows → transcription}/prompts/__init__.py +4 -4
- lattifai/types.py +30 -0
- lattifai/utils.py +16 -44
- lattifai/workflow/__init__.py +22 -0
- lattifai/workflow/agents.py +6 -0
- lattifai/{workflows → workflow}/base.py +22 -22
- lattifai/{workflows → workflow}/file_manager.py +239 -215
- lattifai/workflow/youtube.py +564 -0
- lattifai-1.0.0.dist-info/METADATA +736 -0
- lattifai-1.0.0.dist-info/RECORD +52 -0
- {lattifai-0.4.5.dist-info → lattifai-1.0.0.dist-info}/WHEEL +1 -1
- lattifai-1.0.0.dist-info/entry_points.txt +13 -0
- {lattifai-0.4.5.dist-info → lattifai-1.0.0.dist-info}/licenses/LICENSE +1 -1
- lattifai/base_client.py +0 -126
- lattifai/bin/__init__.py +0 -3
- lattifai/bin/agent.py +0 -325
- lattifai/bin/align.py +0 -296
- lattifai/bin/cli_base.py +0 -25
- lattifai/bin/subtitle.py +0 -210
- lattifai/io/__init__.py +0 -42
- lattifai/io/reader.py +0 -85
- lattifai/io/text_parser.py +0 -75
- lattifai/io/utils.py +0 -15
- lattifai/io/writer.py +0 -90
- lattifai/tokenizer/__init__.py +0 -3
- lattifai/workers/__init__.py +0 -3
- lattifai/workers/lattice1_alpha.py +0 -284
- lattifai/workflows/__init__.py +0 -34
- lattifai/workflows/agents.py +0 -10
- lattifai/workflows/gemini.py +0 -167
- lattifai/workflows/prompts/README.md +0 -22
- lattifai/workflows/prompts/gemini/README.md +0 -24
- lattifai/workflows/prompts/gemini/transcription_gem.txt +0 -81
- lattifai/workflows/youtube.py +0 -931
- lattifai-0.4.5.dist-info/METADATA +0 -808
- lattifai-0.4.5.dist-info/RECORD +0 -39
- lattifai-0.4.5.dist-info/entry_points.txt +0 -3
- {lattifai-0.4.5.dist-info → lattifai-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
lattifai/__init__.py,sha256=K46XVINrXgjGehO8uXByTIbUnBCdB7QwsvVNWzKbdeU,2364
|
|
2
|
+
lattifai/audio2.py,sha256=WPAhcaEoIMRQBf2QZe-0yyAbgyyiqUVAthJ-z54R9Wc,7761
|
|
3
|
+
lattifai/client.py,sha256=Wkz7Q1XvCQ9KxD0uZ_M1ix457ZbgIG1gAxxt8nMBUj4,22147
|
|
4
|
+
lattifai/errors.py,sha256=dFQ_7c8rwuHrq2pDPjpzA755tAV3t8daXXFbHmWblbs,11015
|
|
5
|
+
lattifai/logging.py,sha256=MbUEeOUFlF92pA9v532DiPPWKl03S7UHCJ6Z652cf0w,2860
|
|
6
|
+
lattifai/mixin.py,sha256=7D6zeI9EIhva5FRomnj2RaJkY6ghR86otbqudcijGK4,23380
|
|
7
|
+
lattifai/types.py,sha256=SjYBfwrCBOXlICvH04niFQJ7OzTx7oTaa_npfRkB67U,659
|
|
8
|
+
lattifai/utils.py,sha256=TqOPrd_Et7KxrbfI_JbBNIGZ5-oGJY8ZUyJMPDTih1I,3848
|
|
9
|
+
lattifai/alignment/__init__.py,sha256=ehpkKfjNIYUx7_M-RWD_8Efcrzd9bE-NSm0QgMMVLW0,178
|
|
10
|
+
lattifai/alignment/lattice1_aligner.py,sha256=soBRZ98jRIju-wN5eqYUmQfF56KiEUxVGw0UvtRcx4A,4464
|
|
11
|
+
lattifai/alignment/lattice1_worker.py,sha256=U3izvaBCnrRmr2jBnaEcHLJLGHbZlHNb1qroFxTH8oQ,7440
|
|
12
|
+
lattifai/alignment/phonemizer.py,sha256=fbhN2DOl39lW4nQWKzyUUTMUabg7v61lB1kj8SKK-Sw,1761
|
|
13
|
+
lattifai/alignment/segmenter.py,sha256=-FKtIwv9Z4fU9Fs08jhL9VyREVSYcfcwuTqb8jxCiuo,6228
|
|
14
|
+
lattifai/alignment/tokenizer.py,sha256=WilqU9Ecdkl_cW86IkB1mh_PFlHN-35Jsreiyse2r-8,22355
|
|
15
|
+
lattifai/caption/__init__.py,sha256=6MM_2j6CaqwZ81LfSy4di2EP0ykvheRjMZKAYDx2rQs,477
|
|
16
|
+
lattifai/caption/caption.py,sha256=NNkBJbSdfXe4CwlCvMplrd4UOxlZyxq5Cs5g-dReB1E,46974
|
|
17
|
+
lattifai/caption/gemini_reader.py,sha256=GqY2w78xGYCMDP5kD5WGS8jK0gntel2SK-EPpPKTrwU,15138
|
|
18
|
+
lattifai/caption/gemini_writer.py,sha256=sYPxYEmVQcEan5WVGgSrcraxs3QJRQRh8CJkl2yUQ1s,6515
|
|
19
|
+
lattifai/caption/supervision.py,sha256=DRrM8lfKU_x9aVBcLG6xnT0xIJrnc8jzHpzcSwQOg8c,905
|
|
20
|
+
lattifai/caption/text_parser.py,sha256=XDb8KTt031uJ1hg6dpbINglGOTX-6pBcghbg3DULM1I,4633
|
|
21
|
+
lattifai/cli/__init__.py,sha256=dIUmrpN-OwR4h6BqMhXp87_5ZwgO41ShPru_iZGnpQs,463
|
|
22
|
+
lattifai/cli/alignment.py,sha256=uKMTE95_JMikfbyCcwLbQxms-EQmZXEj7oYugiupk9I,5890
|
|
23
|
+
lattifai/cli/caption.py,sha256=ucgYxJ43ab71nGpZBAiVn8QA0DAVht2QMZFE5IdgxP0,6853
|
|
24
|
+
lattifai/cli/server.py,sha256=k3kuQsZ2JNHZD_eMDxGSn5OFHiUrC2ybHN_xQQlLEgU,493
|
|
25
|
+
lattifai/cli/transcribe.py,sha256=6uJfvtB1o_u1uQwxt4fje_koyfN93mGaFLlskmjqx2c,7406
|
|
26
|
+
lattifai/cli/youtube.py,sha256=9_erdIkhX8pCiy7BRzNstEiO9saM-VKZ1WVqvbXbmrc,5267
|
|
27
|
+
lattifai/config/__init__.py,sha256=Z8OudvS6fgfLNLu_2fvoXartQiYCECOnNfzDt-PfCN4,543
|
|
28
|
+
lattifai/config/alignment.py,sha256=z0b9tg67ftDI90j9Td3qmXFg3WZjSbgszjzeTdwrjZA,3291
|
|
29
|
+
lattifai/config/caption.py,sha256=nmfdsJ-18l4UmapdVgxF1ARJbA4aOr7jek1bmse2F_E,6787
|
|
30
|
+
lattifai/config/client.py,sha256=I1JqLQlsQNU5ouovTumr-PP_8GWC9DI_e9B5UwsDZws,1492
|
|
31
|
+
lattifai/config/diarization.py,sha256=cIkwCfsYqfMns3i6tKWcwBBBkdnhhmB_Eo0TuOPCw9o,2484
|
|
32
|
+
lattifai/config/media.py,sha256=5JOPjifXDM2WWQERySDZen4-7YfgQNcYM2NkkKp0LjQ,13610
|
|
33
|
+
lattifai/config/transcription.py,sha256=bzghOGgcNWzTnDYd_cqCOB7GT8OnzHDiyam7LSixqxM,2901
|
|
34
|
+
lattifai/diarization/__init__.py,sha256=MgBDQ1ehL2qDnZprEp8KqON7CmbG-qaP37gzBsV0jzk,119
|
|
35
|
+
lattifai/diarization/lattifai.py,sha256=SE2BpIZ3_deKyhXdBqe77bsDLXIUV9AQV34gfINv7_s,2657
|
|
36
|
+
lattifai/server/app.py,sha256=ndMBC_FuAFZGs9bvrgHUMtgDDDeYGhCkW0Lx49SEh7k,15193
|
|
37
|
+
lattifai/transcription/__init__.py,sha256=mEoMTbs5jAgtXQn1jTjlFY_GUr-S0WmPn8uZ6WZCkU0,2643
|
|
38
|
+
lattifai/transcription/base.py,sha256=59b4nQHFMyTRyyzBJTM8ZpEuUy1KjwA2o6rNfrNluKY,3911
|
|
39
|
+
lattifai/transcription/gemini.py,sha256=pdc4Q1rk1QLpheC7zIauUOXWViUOM1qGnwC5cRoxH-4,7992
|
|
40
|
+
lattifai/transcription/lattifai.py,sha256=h0nhXST0qljhyndf80IEddM7Y_N1jiS28YoaE536eME,3483
|
|
41
|
+
lattifai/transcription/prompts/__init__.py,sha256=G9b42COaCYv3sPPNkHsGDLOMBuVGKt4mXGYal_BYtYQ,1351
|
|
42
|
+
lattifai/workflow/__init__.py,sha256=GOT9jptXwpIMiNRqJ_LToEt_5Dt0k7XXbLkFzhrl31o,548
|
|
43
|
+
lattifai/workflow/agents.py,sha256=yEOnxnhcTvr1iOhCorNvp8B76P6nQsLRXJCu_rCYFfM,38
|
|
44
|
+
lattifai/workflow/base.py,sha256=8QoVIBZwJfr5mppJbtUFafHv5QR9lL-XrULjTWD0oBg,6257
|
|
45
|
+
lattifai/workflow/file_manager.py,sha256=d106KHLY8A9amLy5h1vR32e4od8mmJGqMD-iDyiRPLI,32917
|
|
46
|
+
lattifai/workflow/youtube.py,sha256=n8L1c6tl8FuYzAzKZ-B76zf5yZsvVggZEJ9mPdbEWGQ,22989
|
|
47
|
+
lattifai-1.0.0.dist-info/licenses/LICENSE,sha256=_IkHdwOWLAWcE1M_tIpDoRWdNSJwFdtIqI-XSkK3yPU,1066
|
|
48
|
+
lattifai-1.0.0.dist-info/METADATA,sha256=orTvmokTIN-W7Lcm5LBs3nof6T8DWKcY9mrU1MwOApE,23109
|
|
49
|
+
lattifai-1.0.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
50
|
+
lattifai-1.0.0.dist-info/entry_points.txt,sha256=Ay-Rxb214lYSuJIXO_wuNlUu1LbvOXtMJ-G-PcvGZFw,424
|
|
51
|
+
lattifai-1.0.0.dist-info/top_level.txt,sha256=tHSoXF26r-IGfbIP_JoYATqbmf14h5NrnNJGH4j5reI,9
|
|
52
|
+
lattifai-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
lai-align = lattifai.cli.alignment:main
|
|
3
|
+
lai-server = lattifai.cli.server:main
|
|
4
|
+
lai-transcribe = lattifai.cli.transcribe:main
|
|
5
|
+
lai-youtube = lattifai.cli.youtube:main
|
|
6
|
+
laicap-convert = lattifai.cli.caption:main_convert
|
|
7
|
+
laicap-normalize = lattifai.cli.caption:main_normalize
|
|
8
|
+
laicap-shift = lattifai.cli.caption:main_shift
|
|
9
|
+
|
|
10
|
+
[lai_run.cli]
|
|
11
|
+
alignment = lattifai.cli
|
|
12
|
+
caption = lattifai.cli
|
|
13
|
+
transcribe = lattifai.cli
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
lattifai/base_client.py
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"""Base client classes for LattifAI SDK."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from abc import ABC
|
|
5
|
-
from typing import Any, Awaitable, Callable, Dict, Optional, Union # noqa: F401
|
|
6
|
-
|
|
7
|
-
import httpx
|
|
8
|
-
|
|
9
|
-
# Import from errors module for consistency
|
|
10
|
-
from .errors import APIError, ConfigurationError, LattifAIError
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class BaseAPIClient(ABC):
|
|
14
|
-
"""Abstract base class for API clients."""
|
|
15
|
-
|
|
16
|
-
def __init__(
|
|
17
|
-
self,
|
|
18
|
-
*,
|
|
19
|
-
api_key: Optional[str] = None,
|
|
20
|
-
base_url: Optional[str] = None,
|
|
21
|
-
timeout: Union[float, httpx.Timeout] = 60.0,
|
|
22
|
-
max_retries: int = 2,
|
|
23
|
-
default_headers: Optional[Dict[str, str]] = None,
|
|
24
|
-
) -> None:
|
|
25
|
-
if api_key is None:
|
|
26
|
-
api_key = os.environ.get('LATTIFAI_API_KEY')
|
|
27
|
-
if api_key is None:
|
|
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'
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
self._api_key = api_key
|
|
34
|
-
self._base_url = base_url
|
|
35
|
-
self._timeout = timeout
|
|
36
|
-
self._max_retries = max_retries
|
|
37
|
-
|
|
38
|
-
headers = {
|
|
39
|
-
'User-Agent': 'LattifAI/Python',
|
|
40
|
-
'Authorization': f'Bearer {self._api_key}',
|
|
41
|
-
}
|
|
42
|
-
if default_headers:
|
|
43
|
-
headers.update(default_headers)
|
|
44
|
-
self._default_headers = headers
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SyncAPIClient(BaseAPIClient):
|
|
48
|
-
"""Synchronous API client."""
|
|
49
|
-
|
|
50
|
-
def __init__(self, **kwargs) -> None:
|
|
51
|
-
super().__init__(**kwargs)
|
|
52
|
-
self._client = httpx.Client(
|
|
53
|
-
base_url=self._base_url,
|
|
54
|
-
timeout=self._timeout,
|
|
55
|
-
headers=self._default_headers,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
def __enter__(self):
|
|
59
|
-
return self
|
|
60
|
-
|
|
61
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
62
|
-
self.close()
|
|
63
|
-
|
|
64
|
-
def close(self) -> None:
|
|
65
|
-
"""Close the HTTP client."""
|
|
66
|
-
self._client.close()
|
|
67
|
-
|
|
68
|
-
def _request(
|
|
69
|
-
self,
|
|
70
|
-
method: str,
|
|
71
|
-
url: str,
|
|
72
|
-
*,
|
|
73
|
-
json: Optional[Dict[str, Any]] = None,
|
|
74
|
-
**kwargs,
|
|
75
|
-
) -> httpx.Response:
|
|
76
|
-
"""Make an HTTP request."""
|
|
77
|
-
return self._client.request(method=method, url=url, json=json, **kwargs)
|
|
78
|
-
|
|
79
|
-
def post(self, api_endpoint: str, *, json: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
|
|
80
|
-
"""Make a POST request to the specified API endpoint."""
|
|
81
|
-
return self._request('POST', api_endpoint, json=json, **kwargs)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class AsyncAPIClient(BaseAPIClient):
|
|
85
|
-
"""Asynchronous API client."""
|
|
86
|
-
|
|
87
|
-
def __init__(self, **kwargs) -> None:
|
|
88
|
-
super().__init__(**kwargs)
|
|
89
|
-
self._client = httpx.AsyncClient(
|
|
90
|
-
base_url=self._base_url,
|
|
91
|
-
timeout=self._timeout,
|
|
92
|
-
headers=self._default_headers,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
async def __aenter__(self):
|
|
96
|
-
return self
|
|
97
|
-
|
|
98
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
99
|
-
await self.close()
|
|
100
|
-
|
|
101
|
-
async def close(self) -> None:
|
|
102
|
-
"""Close the HTTP client."""
|
|
103
|
-
await self._client.aclose()
|
|
104
|
-
|
|
105
|
-
async def _request(
|
|
106
|
-
self,
|
|
107
|
-
method: str,
|
|
108
|
-
url: str,
|
|
109
|
-
*,
|
|
110
|
-
json: Optional[Dict[str, Any]] = None,
|
|
111
|
-
files: Optional[Dict[str, Any]] = None,
|
|
112
|
-
**kwargs,
|
|
113
|
-
) -> httpx.Response:
|
|
114
|
-
"""Make an HTTP request."""
|
|
115
|
-
return await self._client.request(method=method, url=url, json=json, files=files, **kwargs)
|
|
116
|
-
|
|
117
|
-
async def post(
|
|
118
|
-
self,
|
|
119
|
-
api_endpoint: str,
|
|
120
|
-
*,
|
|
121
|
-
json: Optional[Dict[str, Any]] = None,
|
|
122
|
-
files: Optional[Dict[str, Any]] = None,
|
|
123
|
-
**kwargs,
|
|
124
|
-
) -> httpx.Response:
|
|
125
|
-
"""Make a POST request to the specified API endpoint."""
|
|
126
|
-
return await self._request('POST', api_endpoint, json=json, files=files, **kwargs)
|
lattifai/bin/__init__.py
DELETED
lattifai/bin/agent.py
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Agent command for YouTube workflow
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
from typing import List, Optional
|
|
9
|
-
|
|
10
|
-
import click
|
|
11
|
-
import colorful
|
|
12
|
-
from lhotse.utils import Pathlike
|
|
13
|
-
|
|
14
|
-
from lattifai.bin.cli_base import cli
|
|
15
|
-
from lattifai.io import OUTPUT_SUBTITLE_FORMATS
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@cli.command()
|
|
19
|
-
@click.option('--youtube', '--yt', is_flag=True, help='Process YouTube URL through agentic workflow.')
|
|
20
|
-
@click.option(
|
|
21
|
-
'-K',
|
|
22
|
-
'-L',
|
|
23
|
-
'--api-key',
|
|
24
|
-
'--api_key',
|
|
25
|
-
type=str,
|
|
26
|
-
help='LattifAI API key for alignment (overrides LATTIFAI_API_KEY env var).',
|
|
27
|
-
)
|
|
28
|
-
@click.option(
|
|
29
|
-
'-G',
|
|
30
|
-
'--gemini-api-key',
|
|
31
|
-
'--gemini_api_key',
|
|
32
|
-
type=str,
|
|
33
|
-
help='Gemini API key for transcription (overrides GEMINI_API_KEY env var).',
|
|
34
|
-
)
|
|
35
|
-
@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.',
|
|
41
|
-
)
|
|
42
|
-
@click.option(
|
|
43
|
-
'-M',
|
|
44
|
-
'--model-name-or-path',
|
|
45
|
-
'--model_name_or_path',
|
|
46
|
-
type=str,
|
|
47
|
-
default='Lattifai/Lattice-1-Alpha',
|
|
48
|
-
help='Model name or path for alignment.',
|
|
49
|
-
)
|
|
50
|
-
@click.option(
|
|
51
|
-
'--media-format',
|
|
52
|
-
'--media_format',
|
|
53
|
-
type=click.Choice(
|
|
54
|
-
['mp3', 'wav', 'm4a', 'aac', 'opus', 'mp4', 'webm', 'mkv', 'avi', 'mov', 'flv', 'wmv', 'mpeg', 'mpg', '3gp'],
|
|
55
|
-
case_sensitive=False,
|
|
56
|
-
),
|
|
57
|
-
default='mp4',
|
|
58
|
-
help='Media format for YouTube download (audio or video).',
|
|
59
|
-
)
|
|
60
|
-
@click.option(
|
|
61
|
-
'--output-format',
|
|
62
|
-
'--output_format',
|
|
63
|
-
type=click.Choice(OUTPUT_SUBTITLE_FORMATS, case_sensitive=False),
|
|
64
|
-
default='srt',
|
|
65
|
-
help='Subtitle output format.',
|
|
66
|
-
)
|
|
67
|
-
@click.option(
|
|
68
|
-
'--output-dir',
|
|
69
|
-
'--output_dir',
|
|
70
|
-
type=click.Path(exists=False, file_okay=False, dir_okay=True),
|
|
71
|
-
help='Output directory for generated files (default: current directory).',
|
|
72
|
-
)
|
|
73
|
-
@click.option(
|
|
74
|
-
'--max-retries',
|
|
75
|
-
'--max_retries',
|
|
76
|
-
type=int,
|
|
77
|
-
default=0,
|
|
78
|
-
help='Maximum number of retries for failed steps.',
|
|
79
|
-
)
|
|
80
|
-
@click.option(
|
|
81
|
-
'-S',
|
|
82
|
-
'--split-sentence',
|
|
83
|
-
'--split_sentence',
|
|
84
|
-
is_flag=True,
|
|
85
|
-
default=False,
|
|
86
|
-
help='Re-segment subtitles by semantics.',
|
|
87
|
-
)
|
|
88
|
-
@click.option(
|
|
89
|
-
'--word-level',
|
|
90
|
-
'--word_level',
|
|
91
|
-
is_flag=True,
|
|
92
|
-
default=False,
|
|
93
|
-
help='Include word-level alignment timestamps in output (for JSON, TextGrid, and subtitle formats).',
|
|
94
|
-
)
|
|
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)
|
|
98
|
-
def agent(
|
|
99
|
-
youtube: bool,
|
|
100
|
-
url: str,
|
|
101
|
-
api_key: Optional[str] = None,
|
|
102
|
-
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',
|
|
107
|
-
output_dir: Optional[str] = None,
|
|
108
|
-
max_retries: int = 0,
|
|
109
|
-
split_sentence: bool = False,
|
|
110
|
-
word_level: bool = False,
|
|
111
|
-
verbose: bool = False,
|
|
112
|
-
force: bool = False,
|
|
113
|
-
):
|
|
114
|
-
"""
|
|
115
|
-
LattifAI Agentic Workflow Agent
|
|
116
|
-
|
|
117
|
-
Process multimedia content through intelligent agent-based pipelines.
|
|
118
|
-
|
|
119
|
-
Example:
|
|
120
|
-
lattifai agent --youtube https://www.youtube.com/watch?v=example
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
if not youtube:
|
|
124
|
-
click.echo(colorful.red('❌ Please specify a workflow type. Use --youtube for YouTube processing.'))
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
# Setup logging
|
|
128
|
-
import logging
|
|
129
|
-
|
|
130
|
-
log_level = logging.DEBUG if verbose else logging.INFO
|
|
131
|
-
logging.basicConfig(level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
132
|
-
|
|
133
|
-
# Set default output directory
|
|
134
|
-
if not output_dir:
|
|
135
|
-
output_dir = os.getcwd()
|
|
136
|
-
|
|
137
|
-
# 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')
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
# Run the YouTube workflow
|
|
143
|
-
asyncio.run(
|
|
144
|
-
_run_youtube_workflow(
|
|
145
|
-
url=url,
|
|
146
|
-
lattifai_api_key=lattifai_api_key,
|
|
147
|
-
gemini_api_key=gemini_key,
|
|
148
|
-
device=device,
|
|
149
|
-
model_name_or_path=model_name_or_path,
|
|
150
|
-
media_format=media_format,
|
|
151
|
-
output_format=output_format,
|
|
152
|
-
output_dir=output_dir,
|
|
153
|
-
max_retries=max_retries,
|
|
154
|
-
split_sentence=split_sentence,
|
|
155
|
-
word_level=word_level,
|
|
156
|
-
force_overwrite=force,
|
|
157
|
-
)
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
except KeyboardInterrupt:
|
|
161
|
-
click.echo(colorful.yellow('\n⚠️ Process interrupted by user'))
|
|
162
|
-
sys.exit(1)
|
|
163
|
-
except Exception as e:
|
|
164
|
-
from lattifai.errors import LattifAIError
|
|
165
|
-
|
|
166
|
-
# Extract error message without support info (to avoid duplication)
|
|
167
|
-
if isinstance(e, LattifAIError):
|
|
168
|
-
# Use the get_message() method which includes proper formatting
|
|
169
|
-
click.echo(colorful.red('❌ Workflow failed:'))
|
|
170
|
-
click.echo(e.get_message())
|
|
171
|
-
# Show support info once at the end
|
|
172
|
-
click.echo(e.get_support_info())
|
|
173
|
-
else:
|
|
174
|
-
click.echo(colorful.red(f'❌ Workflow failed: {str(e)}'))
|
|
175
|
-
|
|
176
|
-
if verbose:
|
|
177
|
-
import traceback
|
|
178
|
-
|
|
179
|
-
traceback.print_exc()
|
|
180
|
-
sys.exit(1)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
async def _run_youtube_workflow(
|
|
184
|
-
url: str,
|
|
185
|
-
lattifai_api_key: Optional[str],
|
|
186
|
-
gemini_api_key: str,
|
|
187
|
-
device: str,
|
|
188
|
-
model_name_or_path: str,
|
|
189
|
-
media_format: str,
|
|
190
|
-
output_format: str,
|
|
191
|
-
output_dir: str,
|
|
192
|
-
max_retries: int,
|
|
193
|
-
split_sentence: bool = False,
|
|
194
|
-
word_level: bool = False,
|
|
195
|
-
force_overwrite: bool = False,
|
|
196
|
-
):
|
|
197
|
-
"""Run the YouTube processing workflow"""
|
|
198
|
-
|
|
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}')
|
|
205
|
-
click.echo()
|
|
206
|
-
|
|
207
|
-
# Import workflow components
|
|
208
|
-
from lattifai.client import AsyncLattifAI
|
|
209
|
-
from lattifai.workflows import YouTubeSubtitleAgent
|
|
210
|
-
from lattifai.workflows.gemini import GeminiTranscriber
|
|
211
|
-
from lattifai.workflows.youtube import YouTubeDownloader
|
|
212
|
-
|
|
213
|
-
# Initialize components with their configuration (only persistent config, not runtime params)
|
|
214
|
-
downloader = YouTubeDownloader()
|
|
215
|
-
transcriber = GeminiTranscriber(api_key=gemini_api_key)
|
|
216
|
-
aligner = AsyncLattifAI(model_name_or_path=model_name_or_path, device=device, api_key=lattifai_api_key)
|
|
217
|
-
|
|
218
|
-
# Initialize agent with components
|
|
219
|
-
agent = YouTubeSubtitleAgent(
|
|
220
|
-
downloader=downloader,
|
|
221
|
-
transcriber=transcriber,
|
|
222
|
-
aligner=aligner,
|
|
223
|
-
max_retries=max_retries,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
# Process the URL
|
|
227
|
-
result = await agent.process_youtube_url(
|
|
228
|
-
url=url,
|
|
229
|
-
output_dir=output_dir,
|
|
230
|
-
media_format=media_format,
|
|
231
|
-
force_overwrite=force_overwrite,
|
|
232
|
-
output_format=output_format,
|
|
233
|
-
split_sentence=split_sentence,
|
|
234
|
-
word_level=word_level,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
# Display results
|
|
238
|
-
click.echo(colorful.bold_white_on_green('🎉 Workflow completed successfully!'))
|
|
239
|
-
click.echo()
|
|
240
|
-
click.echo(colorful.bold_white_on_green('📊 Results:'))
|
|
241
|
-
|
|
242
|
-
# Show metadata
|
|
243
|
-
metadata = result.get('metadata', {})
|
|
244
|
-
if metadata:
|
|
245
|
-
click.echo(f'🎬 Title: {metadata.get("title", "Unknown")}')
|
|
246
|
-
click.echo(f'👤 Uploader: {metadata.get("uploader", "Unknown").strip()}')
|
|
247
|
-
click.echo(f'⏱️ Duration: {metadata.get("duration", 0)} seconds')
|
|
248
|
-
click.echo()
|
|
249
|
-
|
|
250
|
-
# Show exported files
|
|
251
|
-
exported_files = result.get('exported_files', {})
|
|
252
|
-
if exported_files:
|
|
253
|
-
click.echo(colorful.bold_white_on_green('📄 Generated subtitle files:'))
|
|
254
|
-
for format_name, file_path in exported_files.items():
|
|
255
|
-
click.echo(f' {format_name.upper()}: {file_path}')
|
|
256
|
-
click.echo()
|
|
257
|
-
|
|
258
|
-
# Show subtitle count
|
|
259
|
-
subtitle_count = result.get('subtitle_count', 0)
|
|
260
|
-
click.echo(f'📝 Generated {subtitle_count} subtitle segments')
|
|
261
|
-
|
|
262
|
-
click.echo(colorful.bold_white_on_green('✨ All done! Your aligned subtitles are ready.'))
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
# Add dependencies check
|
|
266
|
-
def check_dependencies():
|
|
267
|
-
"""Check if required dependencies are installed"""
|
|
268
|
-
missing_deps = []
|
|
269
|
-
|
|
270
|
-
try:
|
|
271
|
-
from google import genai
|
|
272
|
-
except ImportError:
|
|
273
|
-
missing_deps.append('google-genai')
|
|
274
|
-
|
|
275
|
-
try:
|
|
276
|
-
import yt_dlp
|
|
277
|
-
except ImportError:
|
|
278
|
-
missing_deps.append('yt-dlp')
|
|
279
|
-
|
|
280
|
-
try:
|
|
281
|
-
from dotenv import load_dotenv
|
|
282
|
-
except ImportError:
|
|
283
|
-
missing_deps.append('python-dotenv')
|
|
284
|
-
|
|
285
|
-
if missing_deps:
|
|
286
|
-
click.echo(colorful.red('❌ Missing required dependencies:'))
|
|
287
|
-
for dep in missing_deps:
|
|
288
|
-
click.echo(f' - {dep}')
|
|
289
|
-
click.echo()
|
|
290
|
-
click.echo('Install them with:')
|
|
291
|
-
click.echo(f' pip install {" ".join(missing_deps)}')
|
|
292
|
-
return False
|
|
293
|
-
|
|
294
|
-
return True
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
# Check dependencies when module is imported
|
|
298
|
-
if not check_dependencies():
|
|
299
|
-
pass # Don't exit on import, let the command handle it
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if __name__ == '__main__':
|
|
303
|
-
import os
|
|
304
|
-
|
|
305
|
-
from dotenv import find_dotenv, load_dotenv
|
|
306
|
-
|
|
307
|
-
load_dotenv(find_dotenv(usecwd=True))
|
|
308
|
-
|
|
309
|
-
asyncio.run(
|
|
310
|
-
_run_youtube_workflow(
|
|
311
|
-
# 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',
|
|
320
|
-
max_retries=0,
|
|
321
|
-
split_sentence=True,
|
|
322
|
-
word_level=False,
|
|
323
|
-
force_overwrite=False,
|
|
324
|
-
)
|
|
325
|
-
)
|