lattifai 1.2.1__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.
- lattifai/alignment/__init__.py +10 -1
- lattifai/alignment/lattice1_aligner.py +66 -58
- lattifai/alignment/punctuation.py +38 -0
- lattifai/alignment/sentence_splitter.py +152 -21
- lattifai/alignment/text_align.py +440 -0
- lattifai/alignment/tokenizer.py +82 -40
- lattifai/caption/__init__.py +82 -6
- lattifai/caption/caption.py +335 -1141
- lattifai/caption/formats/__init__.py +199 -0
- lattifai/caption/formats/base.py +211 -0
- lattifai/caption/{gemini_reader.py → formats/gemini.py} +320 -60
- lattifai/caption/formats/json.py +194 -0
- lattifai/caption/formats/lrc.py +309 -0
- lattifai/caption/formats/nle/__init__.py +9 -0
- lattifai/caption/formats/nle/audition.py +561 -0
- lattifai/caption/formats/nle/avid.py +423 -0
- lattifai/caption/formats/nle/fcpxml.py +549 -0
- lattifai/caption/formats/nle/premiere.py +589 -0
- lattifai/caption/formats/pysubs2.py +642 -0
- lattifai/caption/formats/sbv.py +147 -0
- lattifai/caption/formats/tabular.py +338 -0
- lattifai/caption/formats/textgrid.py +193 -0
- lattifai/caption/formats/ttml.py +652 -0
- lattifai/caption/formats/vtt.py +469 -0
- lattifai/caption/parsers/__init__.py +9 -0
- lattifai/caption/{text_parser.py → parsers/text_parser.py} +4 -2
- lattifai/caption/standardize.py +636 -0
- lattifai/caption/utils.py +474 -0
- lattifai/cli/__init__.py +2 -1
- lattifai/cli/caption.py +108 -1
- lattifai/cli/transcribe.py +1 -1
- lattifai/cli/youtube.py +4 -1
- lattifai/client.py +33 -113
- lattifai/config/__init__.py +11 -1
- lattifai/config/alignment.py +7 -0
- lattifai/config/caption.py +267 -23
- lattifai/config/media.py +20 -0
- lattifai/diarization/__init__.py +41 -1
- lattifai/mixin.py +27 -15
- lattifai/transcription/base.py +6 -1
- lattifai/transcription/lattifai.py +19 -54
- lattifai/utils.py +7 -13
- lattifai/workflow/__init__.py +28 -4
- lattifai/workflow/file_manager.py +2 -5
- lattifai/youtube/__init__.py +43 -0
- lattifai/youtube/client.py +1170 -0
- lattifai/youtube/types.py +23 -0
- lattifai-1.2.2.dist-info/METADATA +615 -0
- lattifai-1.2.2.dist-info/RECORD +76 -0
- {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/entry_points.txt +1 -2
- lattifai/caption/gemini_writer.py +0 -173
- lattifai/cli/app_installer.py +0 -142
- lattifai/cli/server.py +0 -44
- lattifai/server/app.py +0 -427
- lattifai/workflow/youtube.py +0 -577
- lattifai-1.2.1.dist-info/METADATA +0 -1134
- lattifai-1.2.1.dist-info/RECORD +0 -58
- {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/WHEEL +0 -0
- {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/licenses/LICENSE +0 -0
- {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
lattifai/__init__.py,sha256=RXa1IK8Qt6jsAnLlxecOCZmREqv2naXx6T1Fy0g6pqU,1953
|
|
2
|
+
lattifai/audio2.py,sha256=P3N8_BwiscbetzDbkbj-n8BcMu2vWD6-MvtQvGwWWf0,17448
|
|
3
|
+
lattifai/client.py,sha256=pTtpOZRpc3weXkjKZ_-FZLsbbs1CrzVqM4fVqRjiYTc,17179
|
|
4
|
+
lattifai/errors.py,sha256=LyWRGVhQ6Ak2CYn9FBYAPRgQ7_VHpxzNsXI31HXD--s,11291
|
|
5
|
+
lattifai/logging.py,sha256=MbUEeOUFlF92pA9v532DiPPWKl03S7UHCJ6Z652cf0w,2860
|
|
6
|
+
lattifai/mixin.py,sha256=0I-rwvZumaYt8KFTfiVPT2wpXs-JfTEnLOPTdI5r-bM,26115
|
|
7
|
+
lattifai/types.py,sha256=SjYBfwrCBOXlICvH04niFQJ7OzTx7oTaa_npfRkB67U,659
|
|
8
|
+
lattifai/utils.py,sha256=5LeunAN0OQ1jWoKMIThpXSEOxFYD2dCRTdsglosodUU,7963
|
|
9
|
+
lattifai/alignment/__init__.py,sha256=ggOF4MlbnBD7U9yrcyRb1caBR3se_KGA87cfYlyX8RY,450
|
|
10
|
+
lattifai/alignment/lattice1_aligner.py,sha256=WG3mJM4fGyYkY7FdqhPE10yXwBzhdj2TkS-6LF8F_9k,6463
|
|
11
|
+
lattifai/alignment/lattice1_worker.py,sha256=hQbZTgncPq3n-b_l-gUPDPfm460EwuZTKveErgWLWNk,10891
|
|
12
|
+
lattifai/alignment/phonemizer.py,sha256=fbhN2DOl39lW4nQWKzyUUTMUabg7v61lB1kj8SKK-Sw,1761
|
|
13
|
+
lattifai/alignment/punctuation.py,sha256=qLcvuXhBzoEa6bznWZiAB5TAxR6eLr_ZV-PnnCY90UA,1218
|
|
14
|
+
lattifai/alignment/segmenter.py,sha256=0s0eABe0rLAo7eNfl0l5e_knxmZba_BjabPdqsRD45E,6284
|
|
15
|
+
lattifai/alignment/sentence_splitter.py,sha256=2ORvfAgW9yQaqHjts2zlSFjTiNDZF5Fhd5KZX19QWe0,14781
|
|
16
|
+
lattifai/alignment/text_align.py,sha256=PN7RNL5d6jim96zeCUdfdFEdGw--I8zc0kcgWIFJIXU,14910
|
|
17
|
+
lattifai/alignment/tokenizer.py,sha256=AQzXbJ_AW8cg4CAd5TVl1Qd3zH56uy9whX9LVFQ4AaA,17835
|
|
18
|
+
lattifai/caption/__init__.py,sha256=tyIsUvCbImw_qrhp0Nxxrk4vt9szJIPlRcTBviOQkuI,2641
|
|
19
|
+
lattifai/caption/caption.py,sha256=2PHLRDG0Ks4JMl6jNDeXlrI1kpYinektbZ15GwwTcFI,23479
|
|
20
|
+
lattifai/caption/standardize.py,sha256=1pAB8BmziTqYkgj7abCXUcNmNwSV1EAR0PrmbpAEipU,21491
|
|
21
|
+
lattifai/caption/supervision.py,sha256=DRrM8lfKU_x9aVBcLG6xnT0xIJrnc8jzHpzcSwQOg8c,905
|
|
22
|
+
lattifai/caption/utils.py,sha256=YOdJCXhy-6DdrZUkdrJHuPE9sbEHsE9Z7-Vdo4Z5lLY,14406
|
|
23
|
+
lattifai/caption/formats/__init__.py,sha256=UGl7Y0ybMf_F4hiNMMwoKOrpWNxs5m2tiD5zkbwjokY,5240
|
|
24
|
+
lattifai/caption/formats/base.py,sha256=gGeKLKEAB2Hs05R09QMkq5KlXMIQ7bbkUhLct40IcU8,6314
|
|
25
|
+
lattifai/caption/formats/gemini.py,sha256=zIxK7Vxo2YB1eXFiWnsNrz9WSx69lMN0rL-Sd3r57iI,29389
|
|
26
|
+
lattifai/caption/formats/json.py,sha256=s3tFWMUzkWx_IL46phPJnFbJW476Yh_GsxcwD7Q_Mfw,6416
|
|
27
|
+
lattifai/caption/formats/lrc.py,sha256=CWS9wD3Tp6xuvF5gP1nTlVBsBXYnu59_4m4zNRem-c0,11084
|
|
28
|
+
lattifai/caption/formats/pysubs2.py,sha256=eOTQKRbsFStW9gTHaxuAtD7ha1OnrFdqcNLsjdxpHRY,22591
|
|
29
|
+
lattifai/caption/formats/sbv.py,sha256=QUgm5lfRSc2IGSX955yQ7rPiSlaYrOHvniUigr2sF7Y,4520
|
|
30
|
+
lattifai/caption/formats/tabular.py,sha256=HHoiif2yIkMjO9f1bRNAk5Pc0CfkA1mtCFHk5sdLocM,11701
|
|
31
|
+
lattifai/caption/formats/textgrid.py,sha256=m2jMTwLhQa8gbm0Fs1XyEUdiHJaSfCxB9jrYsdk8j7Q,6659
|
|
32
|
+
lattifai/caption/formats/ttml.py,sha256=pJ_wd9pX-MwOhDFMeAHnCpbDiLtIhs888rkW26T7w9Y,23236
|
|
33
|
+
lattifai/caption/formats/vtt.py,sha256=f5OWqsr-2-ddW3CnMtiiqYKQz-hLYRn2B9WM_eT4-AM,17102
|
|
34
|
+
lattifai/caption/formats/nle/__init__.py,sha256=DPBnWPtxEKCC0J_8DCeTyXULPgkrqFT2jbKvkazAx0s,257
|
|
35
|
+
lattifai/caption/formats/nle/audition.py,sha256=65ipbUPdwgvNcUA--dQuisWCbmlt6nHPRbSdl4UUF2Q,18076
|
|
36
|
+
lattifai/caption/formats/nle/avid.py,sha256=UQwFlN4-Myly-kXZxuJTu-7IunEN2_PtAcK9YGQVpMA,14403
|
|
37
|
+
lattifai/caption/formats/nle/fcpxml.py,sha256=76NL6PeIR3KAG1BZscAZdoFJr5wcNdoS4j3VZsOxFV8,18317
|
|
38
|
+
lattifai/caption/formats/nle/premiere.py,sha256=Y2nXSWxI0J0YhV3iHJ9jDrFs0S_5sX32_fEi9SJyVt0,21319
|
|
39
|
+
lattifai/caption/parsers/__init__.py,sha256=z1JMr47FVl7CGbBDg92PKj9RabKktJIUv9iTmmKfEes,227
|
|
40
|
+
lattifai/caption/parsers/text_parser.py,sha256=rQv-aedTWowBe7crvYEOrHqrgKdpNBPcM8HeU-jElHY,4793
|
|
41
|
+
lattifai/cli/__init__.py,sha256=PdqoCTqRSFSWrqL3FjBTa5VzJy_e6Rq0OzyT7YkyHpc,541
|
|
42
|
+
lattifai/cli/alignment.py,sha256=06em-Uaf6NhSz1ce4dwT2r8n56NrtibR7ZsSkmc18Kc,5954
|
|
43
|
+
lattifai/cli/caption.py,sha256=b2mSVFVgL76b4FB5UoJ7AW5iGzPfKiWiLhbM96z_QoA,10371
|
|
44
|
+
lattifai/cli/diarization.py,sha256=GTd2vnTm6cJN6Q3mFP-ShY9bZBl1_zKzWFu-4HHcMzk,4075
|
|
45
|
+
lattifai/cli/transcribe.py,sha256=YhEalG3TQRK7esAN5SOZUQPwIk3TAI9ZknO8cW8C21Q,8038
|
|
46
|
+
lattifai/cli/youtube.py,sha256=CqAxSC_sErslnrnx2RSwAHc7INKET0wLG9Mf_144O-A,6238
|
|
47
|
+
lattifai/config/__init__.py,sha256=JOOn2WbvWXBN6a_3fSNt24W7xnJY7wn8RyNLa0XIY3s,724
|
|
48
|
+
lattifai/config/alignment.py,sha256=ObWf896GGLfP4jsxJaSk6nUyzeF4MvW-ULoPYa8kd9w,4987
|
|
49
|
+
lattifai/config/caption.py,sha256=D4sKNUestwFessU1nZrUqCTsIzYPgpTg12SZlm0HzbQ,15200
|
|
50
|
+
lattifai/config/client.py,sha256=46b816MiYja3Uan_3wjnhtqDr0M6T-FqEygJ3e50IZc,1664
|
|
51
|
+
lattifai/config/diarization.py,sha256=cIkwCfsYqfMns3i6tKWcwBBBkdnhhmB_Eo0TuOPCw9o,2484
|
|
52
|
+
lattifai/config/media.py,sha256=nxvgC7zeLsthCARPPUbnK2eMJY8R1d-1XgiAsy8kroA,15568
|
|
53
|
+
lattifai/config/transcription.py,sha256=_gPJD6cob_jWNdf841nBHhAqJGCxS6PfSyvx2W_vPcM,3082
|
|
54
|
+
lattifai/diarization/__init__.py,sha256=-ZZ_a5hIQgnlHIOehCTtmVmWOWC2H6eOhSs4AcVtRtk,1782
|
|
55
|
+
lattifai/diarization/lattifai.py,sha256=tCnFL6ywITqeKR8YoCsYvyJxNoIwoC6GsnI9zkXNB-Q,3128
|
|
56
|
+
lattifai/transcription/__init__.py,sha256=vMHciyCEPKhhfM3KjMCeDqnyxU1oghF8g5o5SvpnT_4,2669
|
|
57
|
+
lattifai/transcription/base.py,sha256=A2qnocdRCCbvy8mKP0f3K3mx3ZvYyxVXir3aJ2iU19s,4592
|
|
58
|
+
lattifai/transcription/gemini.py,sha256=LJSQt9nGqQdEG6ZFXoHWltumyMEM7-Ezy8ss0iPJb7k,12414
|
|
59
|
+
lattifai/transcription/lattifai.py,sha256=Sik4IyvzdqIMCvgkaxCzqvo-j7u0MfX045z8AJunjhg,3556
|
|
60
|
+
lattifai/transcription/prompts/README.md,sha256=X49KWSQVdjWxxWUp4R2w3ZqKrAOi6_kDNHh1hMaQ4PE,694
|
|
61
|
+
lattifai/transcription/prompts/__init__.py,sha256=G9b42COaCYv3sPPNkHsGDLOMBuVGKt4mXGYal_BYtYQ,1351
|
|
62
|
+
lattifai/transcription/prompts/gemini/README.md,sha256=rt7f7yDGtaobKBo95LG3u56mqa3ABOXQd0UVgJYtYuo,781
|
|
63
|
+
lattifai/transcription/prompts/gemini/transcription_gem.txt,sha256=cljzZ--BDgnnKzqVCakr-fTp2Xk38UOsUquvruNX-LU,4600
|
|
64
|
+
lattifai/workflow/__init__.py,sha256=INpQgc9gZ2Fp-aTHcpR3TEHGtEtPzjOB8T7-jLzVM0E,1547
|
|
65
|
+
lattifai/workflow/agents.py,sha256=yEOnxnhcTvr1iOhCorNvp8B76P6nQsLRXJCu_rCYFfM,38
|
|
66
|
+
lattifai/workflow/base.py,sha256=8QoVIBZwJfr5mppJbtUFafHv5QR9lL-XrULjTWD0oBg,6257
|
|
67
|
+
lattifai/workflow/file_manager.py,sha256=yc29Vb7JNUMJ9rwM_YjkAHfDInl8HMVAl9A7z7XiIOU,32974
|
|
68
|
+
lattifai/youtube/__init__.py,sha256=_uO3KCx-t6I-JaYFpcYLYpvkbmEOOni3xBqGEbExg68,1587
|
|
69
|
+
lattifai/youtube/client.py,sha256=aEOnd8jp4w1ZZkTfxppl7yz2TVdxMTkb8lGCqQxLqxE,47128
|
|
70
|
+
lattifai/youtube/types.py,sha256=80RgBmvM4tRbxqyNv9GU6hr9vPp_yhKrK0RJ_vG2h4E,472
|
|
71
|
+
lattifai-1.2.2.dist-info/licenses/LICENSE,sha256=xGMLmdFJy6Jkz3Hd0znyQLmcxC93FSZB5isKnEDMoQQ,1066
|
|
72
|
+
lattifai-1.2.2.dist-info/METADATA,sha256=NncEA5sSiDyj2DfZCt251tLSranIOn2Gd4KD2D0Q118,19757
|
|
73
|
+
lattifai-1.2.2.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
74
|
+
lattifai-1.2.2.dist-info/entry_points.txt,sha256=MfoqXNjXrhD7VMApHgaHmAECTcGVUMUiR0uqnTg7Ads,502
|
|
75
|
+
lattifai-1.2.2.dist-info/top_level.txt,sha256=tHSoXF26r-IGfbIP_JoYATqbmf14h5NrnNJGH4j5reI,9
|
|
76
|
+
lattifai-1.2.2.dist-info/RECORD,,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
2
|
lai-align = lattifai.cli.alignment:main
|
|
3
|
-
lai-app-install = lattifai.cli.app_installer:main
|
|
4
3
|
lai-diarize = lattifai.cli.diarization:main
|
|
5
|
-
lai-server = lattifai.cli.server:main
|
|
6
4
|
lai-transcribe = lattifai.cli.transcribe:main
|
|
7
5
|
lai-youtube = lattifai.cli.youtube:main
|
|
8
6
|
laicap-convert = lattifai.cli.caption:main_convert
|
|
7
|
+
laicap-diff = lattifai.cli.caption:main_diff
|
|
9
8
|
laicap-normalize = lattifai.cli.caption:main_normalize
|
|
10
9
|
laicap-shift = lattifai.cli.caption:main_shift
|
|
11
10
|
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""Writer for YouTube transcript files with corrected timestamps from alignment."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Dict, List, Optional
|
|
6
|
-
|
|
7
|
-
from lhotse.utils import Pathlike
|
|
8
|
-
|
|
9
|
-
from .gemini_reader import GeminiReader, GeminiSegment
|
|
10
|
-
from .supervision import Supervision
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class GeminiWriter:
|
|
14
|
-
"""Writer for updating YouTube transcript timestamps based on alignment results."""
|
|
15
|
-
|
|
16
|
-
@staticmethod
|
|
17
|
-
def format_timestamp(seconds: float) -> str:
|
|
18
|
-
"""Convert seconds to [HH:MM:SS] format."""
|
|
19
|
-
hours = int(seconds // 3600)
|
|
20
|
-
minutes = int((seconds % 3600) // 60)
|
|
21
|
-
secs = int(seconds % 60)
|
|
22
|
-
return f"[{hours:02d}:{minutes:02d}:{secs:02d}]"
|
|
23
|
-
|
|
24
|
-
@classmethod
|
|
25
|
-
def update_timestamps(
|
|
26
|
-
cls,
|
|
27
|
-
original_transcript: Pathlike,
|
|
28
|
-
aligned_supervisions: List[Supervision],
|
|
29
|
-
output_path: Pathlike,
|
|
30
|
-
timestamp_mapping: Optional[Dict[int, float]] = None,
|
|
31
|
-
) -> Pathlike:
|
|
32
|
-
"""Update transcript file with corrected timestamps from alignment.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
original_transcript: Path to the original transcript file
|
|
36
|
-
aligned_supervisions: List of aligned Supervision objects with corrected timestamps
|
|
37
|
-
output_path: Path to write the updated transcript
|
|
38
|
-
timestamp_mapping: Optional manual mapping from line_number to new timestamp
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
Path to the output file
|
|
42
|
-
"""
|
|
43
|
-
original_path = Path(original_transcript)
|
|
44
|
-
output_path = Path(output_path)
|
|
45
|
-
|
|
46
|
-
# Read original file
|
|
47
|
-
with open(original_path, "r", encoding="utf-8") as f:
|
|
48
|
-
lines = f.readlines()
|
|
49
|
-
|
|
50
|
-
# Parse original segments to get line numbers
|
|
51
|
-
original_segments = GeminiReader.read(original_transcript, include_events=True, include_sections=True)
|
|
52
|
-
|
|
53
|
-
# Create mapping from line number to new timestamp
|
|
54
|
-
if timestamp_mapping is None:
|
|
55
|
-
timestamp_mapping = cls._create_timestamp_mapping(original_segments, aligned_supervisions)
|
|
56
|
-
|
|
57
|
-
# Update timestamps in lines
|
|
58
|
-
updated_lines = []
|
|
59
|
-
for line_num, line in enumerate(lines, start=1):
|
|
60
|
-
if line_num in timestamp_mapping:
|
|
61
|
-
new_timestamp = timestamp_mapping[line_num]
|
|
62
|
-
updated_line = cls._replace_timestamp(line, new_timestamp)
|
|
63
|
-
updated_lines.append(updated_line)
|
|
64
|
-
else:
|
|
65
|
-
updated_lines.append(line)
|
|
66
|
-
|
|
67
|
-
# Write updated content
|
|
68
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
|
70
|
-
f.writelines(updated_lines)
|
|
71
|
-
|
|
72
|
-
return output_path
|
|
73
|
-
|
|
74
|
-
@classmethod
|
|
75
|
-
def _create_timestamp_mapping(
|
|
76
|
-
cls, original_segments: List[GeminiSegment], aligned_supervisions: List[Supervision]
|
|
77
|
-
) -> Dict[int, float]:
|
|
78
|
-
"""Create mapping from line numbers to new timestamps based on alignment.
|
|
79
|
-
|
|
80
|
-
This performs text matching between original segments and aligned supervisions
|
|
81
|
-
to determine which timestamps should be updated.
|
|
82
|
-
"""
|
|
83
|
-
mapping = {}
|
|
84
|
-
|
|
85
|
-
# Create a simple text-based matching
|
|
86
|
-
dialogue_segments = [s for s in original_segments if s.segment_type == "dialogue"]
|
|
87
|
-
|
|
88
|
-
# Try to match based on text content
|
|
89
|
-
for aligned_sup in aligned_supervisions:
|
|
90
|
-
aligned_text = aligned_sup.text.strip()
|
|
91
|
-
|
|
92
|
-
# Find best matching original segment
|
|
93
|
-
best_match = None
|
|
94
|
-
best_score = 0
|
|
95
|
-
|
|
96
|
-
for orig_seg in dialogue_segments:
|
|
97
|
-
orig_text = orig_seg.text.strip()
|
|
98
|
-
|
|
99
|
-
# Simple text similarity (could be improved with fuzzy matching)
|
|
100
|
-
if aligned_text == orig_text:
|
|
101
|
-
best_match = orig_seg
|
|
102
|
-
best_score = 1.0
|
|
103
|
-
break
|
|
104
|
-
elif aligned_text in orig_text or orig_text in aligned_text:
|
|
105
|
-
score = min(len(aligned_text), len(orig_text)) / max(len(aligned_text), len(orig_text))
|
|
106
|
-
if score > best_score:
|
|
107
|
-
best_score = score
|
|
108
|
-
best_match = orig_seg
|
|
109
|
-
|
|
110
|
-
# If we found a good match, update the mapping
|
|
111
|
-
if best_match and best_score > 0.8:
|
|
112
|
-
mapping[best_match.line_number] = aligned_sup.start
|
|
113
|
-
|
|
114
|
-
return mapping
|
|
115
|
-
|
|
116
|
-
@classmethod
|
|
117
|
-
def _replace_timestamp(cls, line: str, new_timestamp: float) -> str:
|
|
118
|
-
"""Replace timestamp in a line with new timestamp."""
|
|
119
|
-
new_ts_str = cls.format_timestamp(new_timestamp)
|
|
120
|
-
|
|
121
|
-
# Replace timestamp patterns
|
|
122
|
-
# Pattern 1: [HH:MM:SS] at the end or in brackets
|
|
123
|
-
line = re.sub(r"\[\d{2}:\d{2}:\d{2}\]", new_ts_str, line)
|
|
124
|
-
|
|
125
|
-
return line
|
|
126
|
-
|
|
127
|
-
@classmethod
|
|
128
|
-
def write_aligned_transcript(
|
|
129
|
-
cls,
|
|
130
|
-
aligned_supervisions: List[Supervision],
|
|
131
|
-
output_path: Pathlike,
|
|
132
|
-
include_word_timestamps: bool = False,
|
|
133
|
-
) -> Pathlike:
|
|
134
|
-
"""Write a new transcript file from aligned supervisions.
|
|
135
|
-
|
|
136
|
-
This creates a simplified transcript format with accurate timestamps.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
aligned_supervisions: List of aligned Supervision objects
|
|
140
|
-
output_path: Path to write the transcript
|
|
141
|
-
include_word_timestamps: Whether to include word-level timestamps if available
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Path to the output file
|
|
145
|
-
"""
|
|
146
|
-
output_path = Path(output_path)
|
|
147
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
148
|
-
|
|
149
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
|
150
|
-
f.write("# Aligned Transcript\n\n")
|
|
151
|
-
|
|
152
|
-
for i, sup in enumerate(aligned_supervisions):
|
|
153
|
-
# Write segment with timestamp
|
|
154
|
-
start_ts = cls.format_timestamp(sup.start)
|
|
155
|
-
f.write(f"{start_ts} {sup.text}\n")
|
|
156
|
-
|
|
157
|
-
# Optionally write word-level timestamps
|
|
158
|
-
if include_word_timestamps and hasattr(sup, "alignment") and sup.alignment:
|
|
159
|
-
if "word" in sup.alignment:
|
|
160
|
-
f.write(" Words: ")
|
|
161
|
-
word_parts = []
|
|
162
|
-
for word_info in sup.alignment["word"]:
|
|
163
|
-
word_ts = cls.format_timestamp(word_info["start"])
|
|
164
|
-
word_parts.append(f'{word_info["symbol"]}{word_ts}')
|
|
165
|
-
f.write(" ".join(word_parts))
|
|
166
|
-
f.write("\n")
|
|
167
|
-
|
|
168
|
-
f.write("\n")
|
|
169
|
-
|
|
170
|
-
return output_path
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
__all__ = ["GeminiWriter"]
|
lattifai/cli/app_installer.py
DELETED
|
@@ -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()
|