vctx 0.1.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.
- vctx/__init__.py +5 -0
- vctx/app/__init__.py +0 -0
- vctx/app/chunk.py +38 -0
- vctx/app/credentials.py +55 -0
- vctx/app/doctor.py +42 -0
- vctx/app/metadata.py +30 -0
- vctx/app/prepare.py +804 -0
- vctx/app/render.py +69 -0
- vctx/app/result.py +112 -0
- vctx/chunking.py +75 -0
- vctx/cli.py +221 -0
- vctx/config.py +552 -0
- vctx/errors.py +25 -0
- vctx/io.py +60 -0
- vctx/models/__init__.py +10 -0
- vctx/models/acquisition.py +108 -0
- vctx/models/artifacts.py +37 -0
- vctx/models/knowledge_flow.py +40 -0
- vctx/models/manifest.py +99 -0
- vctx/models/media.py +127 -0
- vctx/models/metadata.py +18 -0
- vctx/models/visual.py +181 -0
- vctx/net.py +239 -0
- vctx/render/__init__.py +0 -0
- vctx/render/bundle.py +107 -0
- vctx/render/markdown.py +287 -0
- vctx/sources/__init__.py +0 -0
- vctx/sources/detect.py +43 -0
- vctx/sources/local_file_source.py +80 -0
- vctx/sources/ytdlp_source.py +480 -0
- vctx/subtitles.py +54 -0
- vctx/transcript.py +136 -0
- vctx/transforms/__init__.py +0 -0
- vctx/transforms/ai_routes.py +205 -0
- vctx/transforms/asr.py +291 -0
- vctx/transforms/knowledge_flow.py +248 -0
- vctx/transforms/model_resolution.py +347 -0
- vctx/transforms/planning.py +178 -0
- vctx/transforms/text_ai.py +172 -0
- vctx/transforms/visual_cases.py +245 -0
- vctx/transforms/visual_evidence.py +287 -0
- vctx/transforms/visual_execute.py +150 -0
- vctx/transforms/visual_frames.py +95 -0
- vctx/transforms/visual_ocr.py +81 -0
- vctx/transforms/visual_planning.py +344 -0
- vctx/transforms/visual_routes.py +150 -0
- vctx/transforms/visual_vlm.py +171 -0
- vctx/util.py +19 -0
- vctx-0.1.0.dist-info/METADATA +25 -0
- vctx-0.1.0.dist-info/RECORD +53 -0
- vctx-0.1.0.dist-info/WHEEL +4 -0
- vctx-0.1.0.dist-info/entry_points.txt +2 -0
- vctx-0.1.0.dist-info/licenses/LICENSE +21 -0
vctx/__init__.py
ADDED
vctx/app/__init__.py
ADDED
|
File without changes
|
vctx/app/chunk.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from vctx.chunking import ChunkOptions, chunk_transcript
|
|
8
|
+
from vctx.errors import InvalidTranscriptError, VctxError
|
|
9
|
+
from vctx.io import model_to_json
|
|
10
|
+
from vctx.transcript import Transcript
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChunkWriteError(VctxError):
|
|
14
|
+
exit_code = 1
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def write_chunk_file(
|
|
18
|
+
transcript_path: Path,
|
|
19
|
+
out_path: Path,
|
|
20
|
+
*,
|
|
21
|
+
max_chars: int = 6000,
|
|
22
|
+
max_seconds: int | None = None,
|
|
23
|
+
) -> Path:
|
|
24
|
+
try:
|
|
25
|
+
transcript = Transcript.model_validate_json(transcript_path.read_text(encoding="utf-8"))
|
|
26
|
+
except (OSError, ValidationError) as exc:
|
|
27
|
+
raise InvalidTranscriptError(f"invalid transcript file: {transcript_path}") from exc
|
|
28
|
+
|
|
29
|
+
chunks = chunk_transcript(
|
|
30
|
+
transcript,
|
|
31
|
+
ChunkOptions(max_chars=max_chars, max_seconds=max_seconds),
|
|
32
|
+
)
|
|
33
|
+
try:
|
|
34
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
out_path.write_text(model_to_json(chunks), encoding="utf-8")
|
|
36
|
+
except OSError as exc:
|
|
37
|
+
raise ChunkWriteError(f"failed to write chunks: {out_path}: {exc}") from exc
|
|
38
|
+
return out_path
|
vctx/app/credentials.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import Iterable, Mapping
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CredentialError(RuntimeError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def env_with_credential_presence(
|
|
13
|
+
names: Iterable[str], *, env_files: list[Path], base_env: Mapping[str, str]
|
|
14
|
+
) -> dict[str, str]:
|
|
15
|
+
env = dict(base_env)
|
|
16
|
+
for name in names:
|
|
17
|
+
if name in env:
|
|
18
|
+
continue
|
|
19
|
+
if any(_read_dotenv_value(env_file, name) for env_file in env_files):
|
|
20
|
+
env[name] = "[present]"
|
|
21
|
+
return env
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def resolve_env_credential(name: str | None, *, env_files: list[Path]) -> str:
|
|
25
|
+
if not name:
|
|
26
|
+
raise CredentialError("ASR instance is missing api_key_env")
|
|
27
|
+
value = os.environ.get(name)
|
|
28
|
+
if value:
|
|
29
|
+
return value
|
|
30
|
+
for env_file in env_files:
|
|
31
|
+
dotenv_value = _read_dotenv_value(env_file, name)
|
|
32
|
+
if dotenv_value:
|
|
33
|
+
return dotenv_value
|
|
34
|
+
searched = ", ".join(str(path) for path in env_files) or "no env files"
|
|
35
|
+
raise CredentialError(f"missing credential {name}; searched shell environment and {searched}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _read_dotenv_value(path: Path, name: str) -> str | None:
|
|
39
|
+
if not path.exists():
|
|
40
|
+
return None
|
|
41
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
42
|
+
line = raw_line.strip()
|
|
43
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
44
|
+
continue
|
|
45
|
+
key, value = line.split("=", 1)
|
|
46
|
+
if key.strip() != name:
|
|
47
|
+
continue
|
|
48
|
+
return _strip_dotenv_quotes(value.strip())
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _strip_dotenv_quotes(value: str) -> str:
|
|
53
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
|
|
54
|
+
return value[1:-1]
|
|
55
|
+
return value
|
vctx/app/doctor.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from vctx.io import build_cache
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def doctor_report() -> str:
|
|
12
|
+
lines = [
|
|
13
|
+
f"python: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
14
|
+
f"vctx: {_package_version('vctx')}",
|
|
15
|
+
f"yt-dlp: {_package_version('yt-dlp')}",
|
|
16
|
+
f"cache: {_cache_status()}",
|
|
17
|
+
f"ffmpeg: {_command_status('ffmpeg')}",
|
|
18
|
+
]
|
|
19
|
+
return "\n".join(lines) + "\n"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _package_version(distribution: str) -> str:
|
|
23
|
+
try:
|
|
24
|
+
return importlib.metadata.version(distribution)
|
|
25
|
+
except importlib.metadata.PackageNotFoundError:
|
|
26
|
+
return "missing"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _cache_status() -> str:
|
|
30
|
+
try:
|
|
31
|
+
cache = build_cache(None)
|
|
32
|
+
probe = Path(cache.root) / ".doctor-write-test"
|
|
33
|
+
probe.write_text("ok", encoding="utf-8")
|
|
34
|
+
probe.unlink(missing_ok=True)
|
|
35
|
+
except OSError as exc:
|
|
36
|
+
return f"error: {exc}"
|
|
37
|
+
return f"writable ({cache.root})"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _command_status(command: str) -> str:
|
|
41
|
+
path = shutil.which(command)
|
|
42
|
+
return path if path else "missing"
|
vctx/app/metadata.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from vctx.models.metadata import VideoMetadata
|
|
4
|
+
from vctx.sources.detect import detect_source_adapter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def inspect_metadata(value: str) -> VideoMetadata:
|
|
8
|
+
adapter = detect_source_adapter(value)
|
|
9
|
+
return adapter.extract_metadata(value)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def render_metadata_text(metadata: VideoMetadata) -> str:
|
|
13
|
+
lines: list[str] = [
|
|
14
|
+
f"id: {metadata.id}",
|
|
15
|
+
f"source_type: {metadata.source_type}",
|
|
16
|
+
f"source: {metadata.source.value}",
|
|
17
|
+
]
|
|
18
|
+
optional_fields = {
|
|
19
|
+
"title": metadata.title,
|
|
20
|
+
"uploader": metadata.uploader,
|
|
21
|
+
"duration_seconds": metadata.duration_seconds,
|
|
22
|
+
"webpage_url": metadata.webpage_url,
|
|
23
|
+
"language": metadata.language,
|
|
24
|
+
"extractor": metadata.extractor,
|
|
25
|
+
"raw_provider": metadata.raw_provider,
|
|
26
|
+
}
|
|
27
|
+
for key, value in optional_fields.items():
|
|
28
|
+
if value is not None:
|
|
29
|
+
lines.append(f"{key}: {value}")
|
|
30
|
+
return "\n".join(lines) + "\n"
|