genblaze-cli 0.2.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.
- genblaze_cli/__init__.py +1 -0
- genblaze_cli/commands/__init__.py +0 -0
- genblaze_cli/commands/extract.py +40 -0
- genblaze_cli/commands/index.py +34 -0
- genblaze_cli/commands/replay.py +211 -0
- genblaze_cli/commands/verify.py +41 -0
- genblaze_cli/main.py +20 -0
- genblaze_cli/py.typed +0 -0
- genblaze_cli-0.2.0.dist-info/METADATA +88 -0
- genblaze_cli-0.2.0.dist-info/RECORD +12 -0
- genblaze_cli-0.2.0.dist-info/WHEEL +4 -0
- genblaze_cli-0.2.0.dist-info/entry_points.txt +2 -0
genblaze_cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""genblaze CLI."""
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Extract command — print embedded manifest from a media file."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from genblaze_core.exceptions import EmbeddingError
|
|
7
|
+
from genblaze_core.media import get_handler, guess_mime
|
|
8
|
+
from genblaze_core.media.sidecar import SidecarHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _extract_manifest(file: Path):
|
|
12
|
+
"""Try format-specific handler first, then sidecar fallback."""
|
|
13
|
+
mime = guess_mime(file)
|
|
14
|
+
handler = get_handler(mime)
|
|
15
|
+
if handler is not None:
|
|
16
|
+
try:
|
|
17
|
+
return handler.extract(file)
|
|
18
|
+
except EmbeddingError:
|
|
19
|
+
pass # Expected: no manifest in this format, try sidecar
|
|
20
|
+
# Try sidecar fallback
|
|
21
|
+
sidecar = SidecarHandler()
|
|
22
|
+
return sidecar.extract(file)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.argument("file", type=click.Path(exists=True, path_type=Path))
|
|
27
|
+
@click.option("--format", "fmt", type=click.Choice(["json", "summary"]), default="json")
|
|
28
|
+
def extract(file: Path, fmt: str) -> None:
|
|
29
|
+
"""Extract and display the genblaze manifest from a media file."""
|
|
30
|
+
try:
|
|
31
|
+
manifest = _extract_manifest(file)
|
|
32
|
+
if fmt == "json":
|
|
33
|
+
click.echo(manifest.to_canonical_json())
|
|
34
|
+
else:
|
|
35
|
+
click.echo(f"Run ID: {manifest.run.run_id}")
|
|
36
|
+
click.echo(f"Steps: {len(manifest.run.steps)}")
|
|
37
|
+
click.echo(f"Hash: {manifest.canonical_hash}")
|
|
38
|
+
click.echo(f"Verified: {manifest.verify()}")
|
|
39
|
+
except Exception as exc:
|
|
40
|
+
raise click.ClickException(f"{type(exc).__name__}: {exc}") from exc
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Index command — write manifest data to a Parquet sink."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.argument("manifest_file", type=click.Path(exists=True, path_type=Path))
|
|
11
|
+
@click.option(
|
|
12
|
+
"--output",
|
|
13
|
+
"-o",
|
|
14
|
+
type=click.Path(path_type=Path),
|
|
15
|
+
default="./genblaze_index",
|
|
16
|
+
help="Base directory for Parquet output.",
|
|
17
|
+
)
|
|
18
|
+
def index(manifest_file: Path, output: Path) -> None:
|
|
19
|
+
"""Write manifest data to a Parquet sink for querying."""
|
|
20
|
+
from genblaze_core.models.manifest import Manifest
|
|
21
|
+
from genblaze_core.sinks.parquet import ParquetSink
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
data = json.loads(manifest_file.read_text(encoding="utf-8"))
|
|
25
|
+
manifest = Manifest.model_validate(data)
|
|
26
|
+
except Exception as exc:
|
|
27
|
+
raise click.ClickException(f"Failed to load manifest: {exc}") from exc
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
sink = ParquetSink(output)
|
|
31
|
+
sink.write_run(manifest.run, manifest)
|
|
32
|
+
click.echo(f"Indexed run {manifest.run.run_id} to {output}")
|
|
33
|
+
except Exception as exc:
|
|
34
|
+
raise click.ClickException(f"Failed to write Parquet: {exc}") from exc
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Replay command — re-execute a pipeline from a manifest file."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from genblaze_core.models.enums import PromptVisibility
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_provider(provider_name: str, allowed: tuple[str, ...] | None):
|
|
11
|
+
"""Load a provider by name using entry point discovery.
|
|
12
|
+
|
|
13
|
+
If allowed is set, reject providers not in the allowlist.
|
|
14
|
+
"""
|
|
15
|
+
if allowed and provider_name not in allowed:
|
|
16
|
+
raise click.ClickException(
|
|
17
|
+
f"Provider '{provider_name}' not in allowlist: {', '.join(allowed)}"
|
|
18
|
+
)
|
|
19
|
+
from genblaze_core.providers.registry import discover_providers
|
|
20
|
+
|
|
21
|
+
registry = discover_providers()
|
|
22
|
+
cls = registry.get(provider_name)
|
|
23
|
+
if cls is None:
|
|
24
|
+
available = ", ".join(sorted(registry.keys())) or "(none installed)"
|
|
25
|
+
raise click.ClickException(
|
|
26
|
+
f"Unknown provider '{provider_name}'. Installed providers: {available}"
|
|
27
|
+
)
|
|
28
|
+
click.echo(f" Loading provider: {provider_name}", err=True)
|
|
29
|
+
return cls()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _detect_chain_mode(run) -> bool:
|
|
33
|
+
"""Detect if the original pipeline used chain mode by checking step inputs.
|
|
34
|
+
|
|
35
|
+
If any step has _input_from metadata, this is a fan-in pipeline (not chain).
|
|
36
|
+
Otherwise, if every non-first step has inputs, it was likely chain mode.
|
|
37
|
+
"""
|
|
38
|
+
has_input_from = any(step.metadata.get("_input_from") is not None for step in run.steps)
|
|
39
|
+
if has_input_from:
|
|
40
|
+
return False
|
|
41
|
+
non_first = run.steps[1:]
|
|
42
|
+
return bool(non_first) and all(len(step.inputs) > 0 for step in non_first)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _print_summary(run, *, show_prompts: bool) -> None:
|
|
46
|
+
"""Print manifest run summary.
|
|
47
|
+
|
|
48
|
+
Prompts are only shown when ``show_prompts`` is True AND the step's
|
|
49
|
+
prompt_visibility is PUBLIC. Non-public prompts are redacted to avoid
|
|
50
|
+
leaking private content to terminals/CI logs when replaying manifests
|
|
51
|
+
pulled from shared storage.
|
|
52
|
+
"""
|
|
53
|
+
click.echo(f"Run: {run.run_id}")
|
|
54
|
+
click.echo(f"Name: {run.name or '(unnamed)'}")
|
|
55
|
+
click.echo(f"Steps: {len(run.steps)}")
|
|
56
|
+
click.echo()
|
|
57
|
+
|
|
58
|
+
for i, step in enumerate(run.steps, 1):
|
|
59
|
+
click.echo(f" Step {i}: {step.provider}/{step.model}")
|
|
60
|
+
click.echo(f" Type: {step.step_type}")
|
|
61
|
+
if show_prompts and step.prompt_visibility == PromptVisibility.PUBLIC:
|
|
62
|
+
click.echo(f" Prompt: {step.prompt or '(none)'}")
|
|
63
|
+
else:
|
|
64
|
+
click.echo(
|
|
65
|
+
f" Prompt: [redacted — visibility={step.prompt_visibility};"
|
|
66
|
+
" pass --show-prompts to reveal public prompts]"
|
|
67
|
+
)
|
|
68
|
+
click.echo(f" Modality: {step.modality}")
|
|
69
|
+
if step.params:
|
|
70
|
+
click.echo(f" Params: {step.params}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Fields that cannot be faithfully replayed (logged as warnings)
|
|
74
|
+
_UNREPLAYABLE_FIELDS = [
|
|
75
|
+
("prompt_visibility", lambda s: s.prompt_visibility != "public"),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@click.command()
|
|
80
|
+
@click.argument("manifest_file", type=click.Path(exists=True, path_type=Path))
|
|
81
|
+
@click.option("--dry-run/--no-dry-run", default=True, help="Preview without executing.")
|
|
82
|
+
@click.option(
|
|
83
|
+
"--allow-provider",
|
|
84
|
+
multiple=True,
|
|
85
|
+
help="Only allow these providers (can be specified multiple times).",
|
|
86
|
+
)
|
|
87
|
+
@click.option(
|
|
88
|
+
"--show-prompts",
|
|
89
|
+
is_flag=True,
|
|
90
|
+
help="Show public-visibility prompts in the summary. Non-public prompts are always redacted.",
|
|
91
|
+
)
|
|
92
|
+
@click.option("--force", is_flag=True, help="Skip manifest hash verification.")
|
|
93
|
+
def replay(
|
|
94
|
+
manifest_file: Path,
|
|
95
|
+
dry_run: bool,
|
|
96
|
+
allow_provider: tuple[str, ...],
|
|
97
|
+
show_prompts: bool,
|
|
98
|
+
force: bool,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Re-execute a pipeline from a manifest JSON file."""
|
|
101
|
+
from genblaze_core.models.manifest import parse_manifest
|
|
102
|
+
from genblaze_core.pipeline import Pipeline
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
data = json.loads(manifest_file.read_text(encoding="utf-8"))
|
|
106
|
+
manifest = parse_manifest(data)
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
raise click.ClickException(f"Failed to load manifest: {exc}") from exc
|
|
109
|
+
|
|
110
|
+
# Verify manifest hash integrity
|
|
111
|
+
if not force and not manifest.verify():
|
|
112
|
+
click.echo(
|
|
113
|
+
"WARNING: Manifest hash does not match content. The file may have been modified.",
|
|
114
|
+
err=True,
|
|
115
|
+
)
|
|
116
|
+
if not click.confirm("Continue anyway?"):
|
|
117
|
+
raise click.Abort()
|
|
118
|
+
|
|
119
|
+
run = manifest.run
|
|
120
|
+
_print_summary(run, show_prompts=show_prompts)
|
|
121
|
+
|
|
122
|
+
if dry_run:
|
|
123
|
+
click.echo()
|
|
124
|
+
click.echo("Dry run — no steps executed. Use --no-dry-run to execute.")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
# Build and execute pipeline from manifest steps
|
|
128
|
+
click.echo()
|
|
129
|
+
click.echo("Replaying pipeline...")
|
|
130
|
+
|
|
131
|
+
allowed: tuple[str, ...] | None = allow_provider if allow_provider else None
|
|
132
|
+
|
|
133
|
+
# Safety: when no allowlist is provided, confirm each unique provider.
|
|
134
|
+
# A hostile manifest could reference any installed provider and execute
|
|
135
|
+
# paid API calls on the user's credentials without consent.
|
|
136
|
+
if allowed is None:
|
|
137
|
+
unique_providers = sorted({step.provider for step in run.steps})
|
|
138
|
+
click.echo(
|
|
139
|
+
"No --allow-provider allowlist set. Confirm each provider before execution:",
|
|
140
|
+
err=True,
|
|
141
|
+
)
|
|
142
|
+
confirmed: list[str] = []
|
|
143
|
+
for name in unique_providers:
|
|
144
|
+
if click.confirm(f" Execute with provider '{name}'?", default=False):
|
|
145
|
+
confirmed.append(name)
|
|
146
|
+
else:
|
|
147
|
+
raise click.Abort()
|
|
148
|
+
allowed = tuple(confirmed)
|
|
149
|
+
|
|
150
|
+
# Detect chain mode from step inputs in the manifest
|
|
151
|
+
chain = _detect_chain_mode(run)
|
|
152
|
+
if chain:
|
|
153
|
+
click.echo(" Detected chain mode from manifest step inputs.", err=True)
|
|
154
|
+
|
|
155
|
+
# Cache providers by name to avoid re-instantiating
|
|
156
|
+
providers: dict[str, object] = {}
|
|
157
|
+
pipe = Pipeline(
|
|
158
|
+
run.name,
|
|
159
|
+
tenant_id=run.tenant_id,
|
|
160
|
+
project_id=run.project_id,
|
|
161
|
+
chain=chain,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Reserved kwargs that Pipeline.step() accepts explicitly
|
|
165
|
+
_reserved = {"model", "prompt", "modality", "step_type", "seed", "negative_prompt"}
|
|
166
|
+
|
|
167
|
+
for step_idx, step in enumerate(run.steps):
|
|
168
|
+
if step.provider not in providers:
|
|
169
|
+
providers[step.provider] = _load_provider(step.provider, allowed)
|
|
170
|
+
|
|
171
|
+
# Warn about fields that can't be faithfully replayed
|
|
172
|
+
for field_name, has_value in _UNREPLAYABLE_FIELDS:
|
|
173
|
+
if has_value(step):
|
|
174
|
+
click.echo(
|
|
175
|
+
f" WARNING: Step {step_idx + 1} has {field_name}={getattr(step, field_name)}"
|
|
176
|
+
f" which may not replay identically.",
|
|
177
|
+
err=True,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Strip reserved keys from params to avoid TypeError on collision
|
|
181
|
+
extra_params = {k: v for k, v in step.params.items() if k not in _reserved}
|
|
182
|
+
|
|
183
|
+
# Restore seed and negative_prompt if present
|
|
184
|
+
if step.seed is not None:
|
|
185
|
+
extra_params["seed"] = step.seed
|
|
186
|
+
if step.negative_prompt is not None:
|
|
187
|
+
extra_params["negative_prompt"] = step.negative_prompt
|
|
188
|
+
|
|
189
|
+
# Restore fallback_models and input_from from step metadata
|
|
190
|
+
fallback_models = step.metadata.get("_fallback_models")
|
|
191
|
+
input_from = step.metadata.get("_input_from")
|
|
192
|
+
|
|
193
|
+
pipe.step(
|
|
194
|
+
providers[step.provider],
|
|
195
|
+
model=step.model,
|
|
196
|
+
prompt=step.prompt,
|
|
197
|
+
modality=step.modality,
|
|
198
|
+
step_type=step.step_type,
|
|
199
|
+
fallback_models=fallback_models,
|
|
200
|
+
input_from=input_from,
|
|
201
|
+
**extra_params,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
result = pipe.run()
|
|
206
|
+
click.echo()
|
|
207
|
+
click.echo(f"Replay complete. New run ID: {result.run.run_id}")
|
|
208
|
+
click.echo(f"Hash: {result.manifest.canonical_hash}")
|
|
209
|
+
click.echo(f"Status: {result.run.status}")
|
|
210
|
+
except Exception as exc:
|
|
211
|
+
raise click.ClickException(f"Replay failed: {exc}") from exc
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Verify command — check manifest hash integrity."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from genblaze_core.exceptions import EmbeddingError
|
|
7
|
+
from genblaze_core.media import get_handler, guess_mime
|
|
8
|
+
from genblaze_core.media.sidecar import SidecarHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _verify_manifest(file: Path) -> bool:
|
|
12
|
+
"""Extract and verify manifest, trying format-specific handler then sidecar."""
|
|
13
|
+
mime = guess_mime(file)
|
|
14
|
+
handler = get_handler(mime)
|
|
15
|
+
if handler is not None:
|
|
16
|
+
try:
|
|
17
|
+
return handler.verify(file)
|
|
18
|
+
except EmbeddingError:
|
|
19
|
+
pass # Expected: no manifest in this format, try sidecar
|
|
20
|
+
# Try sidecar fallback
|
|
21
|
+
sidecar = SidecarHandler()
|
|
22
|
+
return sidecar.verify(file)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.argument("file", type=click.Path(exists=True, path_type=Path))
|
|
27
|
+
def verify(file: Path) -> None:
|
|
28
|
+
"""Verify the integrity of a genblaze manifest in a media file."""
|
|
29
|
+
try:
|
|
30
|
+
valid = _verify_manifest(file)
|
|
31
|
+
if valid:
|
|
32
|
+
click.echo("OK — manifest hash verified.")
|
|
33
|
+
else:
|
|
34
|
+
click.echo("FAIL — manifest hash mismatch.", err=True)
|
|
35
|
+
raise click.exceptions.Exit(1)
|
|
36
|
+
except click.exceptions.Exit:
|
|
37
|
+
raise
|
|
38
|
+
except Exception as exc:
|
|
39
|
+
# Prefix with exception type so "PermissionError: ..." and
|
|
40
|
+
# "EmbeddingError: ..." are distinguishable at a glance.
|
|
41
|
+
raise click.ClickException(f"{type(exc).__name__}: {exc}") from exc
|
genblaze_cli/main.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""CLI entry point."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from genblaze_cli.commands.extract import extract
|
|
6
|
+
from genblaze_cli.commands.index import index
|
|
7
|
+
from genblaze_cli.commands.replay import replay
|
|
8
|
+
from genblaze_cli.commands.verify import verify
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
@click.version_option(package_name="genblaze-cli")
|
|
13
|
+
def cli() -> None:
|
|
14
|
+
"""genblaze — media generation manifest toolkit."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
cli.add_command(extract)
|
|
18
|
+
cli.add_command(index)
|
|
19
|
+
cli.add_command(replay)
|
|
20
|
+
cli.add_command(verify)
|
genblaze_cli/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genblaze-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI for genblaze manifest operations
|
|
5
|
+
Project-URL: Homepage, https://github.com/backblaze-labs/genblaze
|
|
6
|
+
Project-URL: Repository, https://github.com/backblaze-labs/genblaze
|
|
7
|
+
Project-URL: Changelog, https://github.com/backblaze-labs/genblaze/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/backblaze-labs/genblaze/issues
|
|
9
|
+
Author-email: Jeronimo De Leon <jdeleon@backblaze.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: click>=8.0
|
|
16
|
+
Requires-Dist: genblaze-core<0.3,>=0.2.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pillow>=10.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
<!-- last_verified: 2026-04-22 -->
|
|
23
|
+
# genblaze-cli
|
|
24
|
+
|
|
25
|
+
**Command-line toolkit for inspecting, verifying, and indexing genblaze AI-generated-media provenance manifests.**
|
|
26
|
+
|
|
27
|
+
`genblaze-cli` is the companion CLI for [genblaze](https://github.com/backblaze-labs/genblaze) — the Python SDK for generative AI pipelines across video, image, and audio. It lets anyone (not just Python developers) audit AI-generated media: extract the embedded provenance manifest from an MP4 / PNG / MP3, verify its SHA-256 hash, replay a run, or index manifests into Parquet for downstream analytics.
|
|
28
|
+
|
|
29
|
+
## Why genblaze-cli
|
|
30
|
+
|
|
31
|
+
- **Audit AI-generated media in one command** — `genblaze verify video.mp4` confirms a file's manifest hash hasn't been tampered with.
|
|
32
|
+
- **Works on any genblaze output** — PNG, JPEG, WebP, MP4, MP3, WAV with embedded manifests.
|
|
33
|
+
- **Analytics-ready** — `genblaze index` emits partitioned Parquet tables (runs, steps, assets) for BI tools and data warehouses.
|
|
34
|
+
- **Zero provider dependencies** — reads manifests; doesn't call any AI API.
|
|
35
|
+
- **Shell-friendly** — non-zero exit codes on verification failure, pipeable JSON output.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install genblaze-cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Installs the `genblaze` console script.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
genblaze --help
|
|
49
|
+
|
|
50
|
+
genblaze extract video.mp4 # Extract embedded manifest → stdout (JSON)
|
|
51
|
+
genblaze extract video.mp4 -o m.json # …or to a file
|
|
52
|
+
|
|
53
|
+
genblaze verify video.mp4 # Verify the embedded manifest's SHA-256
|
|
54
|
+
genblaze verify manifest.json # Or verify a standalone manifest file
|
|
55
|
+
|
|
56
|
+
genblaze replay manifest.json # Show what a replay would do (dry run)
|
|
57
|
+
|
|
58
|
+
genblaze index manifest.json -o data/ # Index into partitioned Parquet tables
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Exit codes are non-zero on verification failure — safe to drop into CI pipelines, release checks, or content-moderation workflows.
|
|
62
|
+
|
|
63
|
+
## Typical flow
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 1. Someone ships you an AI-generated video
|
|
67
|
+
genblaze extract delivered-ad.mp4 -o manifest.json
|
|
68
|
+
|
|
69
|
+
# 2. Confirm it hasn't been tampered with
|
|
70
|
+
genblaze verify delivered-ad.mp4
|
|
71
|
+
|
|
72
|
+
# 3. Index into your analytics warehouse
|
|
73
|
+
genblaze index manifest.json -o s3://analytics/genblaze/
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Documentation
|
|
77
|
+
|
|
78
|
+
- **Main repo**: https://github.com/backblaze-labs/genblaze
|
|
79
|
+
- **CLI reference**: https://github.com/backblaze-labs/genblaze/tree/main/cli
|
|
80
|
+
|
|
81
|
+
## Related packages
|
|
82
|
+
|
|
83
|
+
- [`genblaze-core`](https://pypi.org/project/genblaze-core/) — the pipeline SDK that produces these manifests
|
|
84
|
+
- [`genblaze-s3`](https://pypi.org/project/genblaze-s3/) — durable storage on [Backblaze B2](https://www.backblaze.com/cloud-storage?utm_source=github&utm_medium=referral&utm_campaign=ai_artifacts&utm_content=genblaze), AWS S3, Cloudflare R2, MinIO
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
genblaze_cli/__init__.py,sha256=bugOiMl926GX70kdp9IhfgjC4Wa7nJ1Q0ej3OXPp0oQ,20
|
|
2
|
+
genblaze_cli/main.py,sha256=7rqLrPhC5oNAftoyOzGIQM-xyuSZ7Ca34gmPUYIk724,473
|
|
3
|
+
genblaze_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
genblaze_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
genblaze_cli/commands/extract.py,sha256=Wp_LJHMtwXNysB4aWA8_eE5F2K0m6mXjRkvhPxvsaB0,1499
|
|
6
|
+
genblaze_cli/commands/index.py,sha256=wyfSha0oXWkv89TNVzj9rXboCZRU0dS3IQv2zfyE8Tk,1104
|
|
7
|
+
genblaze_cli/commands/replay.py,sha256=E2ujYBfyN7Z2CFBoJlqYZ6v7rCvu5lM5bx7Ycg9eXdg,7779
|
|
8
|
+
genblaze_cli/commands/verify.py,sha256=qLi68LFevBL5Yj5LoeWr60F0TfR2bFpgl1gbc5Mvk5I,1445
|
|
9
|
+
genblaze_cli-0.2.0.dist-info/METADATA,sha256=XAsvE5WqGWLEI-mhB9DcMtjqFomLEWMjbss6gso0J7k,3580
|
|
10
|
+
genblaze_cli-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
11
|
+
genblaze_cli-0.2.0.dist-info/entry_points.txt,sha256=BhMtCFvHqDssP-lleKOdxIBzN3oVqWjDGr45uP9tzzQ,51
|
|
12
|
+
genblaze_cli-0.2.0.dist-info/RECORD,,
|