ltmarx / web /src /components /ApiDocs.tsx
harelcain's picture
Upload 29 files
86323af verified
import { useState } from 'react';
interface CodeBlockProps {
lang: string;
children: string;
}
function CodeBlock({ lang, children }: CodeBlockProps) {
const [copied, setCopied] = useState(false);
const copy = () => {
navigator.clipboard.writeText(children.trim());
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};
return (
<div className="group relative">
<div className="flex items-center justify-between rounded-t-lg bg-zinc-800/80 px-3 py-1.5">
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-500">{lang}</span>
<button
onClick={copy}
className="text-[10px] text-zinc-500 transition-colors hover:text-zinc-300"
>
{copied ? 'Copied!' : 'Copy'}
</button>
</div>
<pre className="overflow-x-auto rounded-b-lg bg-zinc-900/80 p-3 text-xs leading-relaxed text-zinc-300 ring-1 ring-zinc-800/50">
<code>{children.trim()}</code>
</pre>
</div>
);
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="space-y-3">
<h3 className="text-sm font-semibold text-zinc-200">{title}</h3>
{children}
</div>
);
}
function Endpoint({ method, path, desc }: { method: string; path: string; desc: string }) {
const color = method === 'POST' ? 'text-amber-400 bg-amber-400/10' : 'text-emerald-400 bg-emerald-400/10';
return (
<div className="flex items-start gap-2">
<span className={`mt-0.5 shrink-0 rounded px-1.5 py-0.5 text-[10px] font-bold ${color}`}>{method}</span>
<div>
<code className="text-xs font-medium text-zinc-200">{path}</code>
<p className="mt-0.5 text-xs text-zinc-500">{desc}</p>
</div>
</div>
);
}
function ParamTable({ params }: { params: { name: string; type: string; desc: string; required?: boolean }[] }) {
return (
<div className="overflow-hidden rounded-lg ring-1 ring-zinc-800/50">
<table className="w-full text-xs">
<thead>
<tr className="border-b border-zinc-800/50 bg-zinc-900/50">
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Parameter</th>
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Type</th>
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Description</th>
</tr>
</thead>
<tbody>
{params.map((p) => (
<tr key={p.name} className="border-b border-zinc-800/30 last:border-0">
<td className="px-3 py-1.5">
<code className="text-zinc-200">{p.name}</code>
{p.required && <span className="ml-1 text-[9px] text-red-400">*</span>}
</td>
<td className="px-3 py-1.5 text-zinc-500">{p.type}</td>
<td className="px-3 py-1.5 text-zinc-400">{p.desc}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default function ApiDocs() {
return (
<div className="space-y-8">
{/* Core Library */}
<Section title="Core Library (TypeScript)">
<p className="text-xs text-zinc-400">
The core engine is isomorphic — same code runs in the browser and on Node.js.
It operates on raw Y-plane (luminance) buffers with no platform dependencies.
</p>
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-xs font-medium text-zinc-300">Embed a watermark</h4>
<CodeBlock lang="typescript">{`
import { embedWatermark } from '@core/embedder';
import { getPreset } from '@core/presets';
const config = getPreset('moderate');
const payload = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
const result = embedWatermark(yPlane, width, height, payload, secretKey, config);
// result.yPlane → Uint8Array (watermarked luminance)
// result.psnr → number (quality in dB, higher = less visible)
`}</CodeBlock>
</div>
<div className="space-y-2">
<h4 className="text-xs font-medium text-zinc-300">Detect a watermark</h4>
<CodeBlock lang="typescript">{`
import { detectWatermarkMultiFrame } from '@core/detector';
import { getPreset } from '@core/presets';
const config = getPreset('moderate');
const result = detectWatermarkMultiFrame(yPlanes, width, height, secretKey, config);
// result.detected → boolean
// result.payload → Uint8Array | null (the 4-byte payload)
// result.confidence → number (0–1)
// result.tilesDecoded → number
`}</CodeBlock>
</div>
<div className="space-y-2">
<h4 className="text-xs font-medium text-zinc-300">Auto-detect (tries all presets)</h4>
<CodeBlock lang="typescript">{`
import { autoDetectMultiFrame } from '@core/detector';
const result = autoDetectMultiFrame(yPlanes, width, height, secretKey);
// result.presetUsed → 'light' | 'moderate' | 'strong' | 'fortress' | null
`}</CodeBlock>
</div>
</div>
</Section>
{/* HTTP API */}
<Section title="HTTP API">
<p className="text-xs text-zinc-400">
Base URL: <code className="text-zinc-300">https://lightricks-ltmarx.hf.space</code> (HF Space) or <code className="text-zinc-300">http://localhost:7860</code> (local).
For private Spaces, add <code className="text-zinc-300">Authorization: Bearer hf_YOUR_TOKEN</code> header.
</p>
<div className="space-y-4">
<div className="space-y-2">
<Endpoint method="POST" path="/api/embed" desc="Embed a watermark into a video" />
<ParamTable params={[
{ name: 'videoBase64', type: 'string', desc: 'Base64-encoded video file', required: true },
{ name: 'key', type: 'string', desc: 'Secret key for embedding', required: true },
{ name: 'preset', type: 'string', desc: 'light | moderate | strong | fortress', required: true },
{ name: 'payload', type: 'string', desc: 'Hex string, up to 8 chars (32 bits)', required: true },
]} />
<CodeBlock lang="bash">{`
curl -X POST https://lightricks-ltmarx.hf.space/api/embed \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer hf_YOUR_TOKEN" \\
-d '{
"videoBase64": "'$(base64 -i input.mp4)'",
"key": "my-secret",
"preset": "moderate",
"payload": "DEADBEEF"
}'
`}</CodeBlock>
</div>
<div className="space-y-2">
<Endpoint method="POST" path="/api/detect" desc="Detect and extract a watermark from a video" />
<ParamTable params={[
{ name: 'videoBase64', type: 'string', desc: 'Base64-encoded video file', required: true },
{ name: 'key', type: 'string', desc: 'Secret key used during embedding', required: true },
{ name: 'preset', type: 'string', desc: 'Preset to try (omit to try all)', required: false },
{ name: 'frames', type: 'number', desc: 'Max frames to analyze (default: 10)', required: false },
]} />
<CodeBlock lang="json">{`
{
"detected": true,
"payload": "DEADBEEF",
"confidence": 0.97,
"preset": "moderate",
"tilesDecoded": 12,
"tilesTotal": 16
}
`}</CodeBlock>
</div>
<Endpoint method="GET" path="/api/health" desc="Health check — returns { status: 'ok' }" />
</div>
</Section>
{/* CLI */}
<Section title="CLI">
<p className="text-xs text-zinc-400">
Requires Node.js and FFmpeg installed locally.
</p>
<div className="space-y-2">
<CodeBlock lang="bash">{`
# Embed a watermark
npx tsx server/cli.ts embed \\
-i input.mp4 -o output.mp4 \\
--key SECRET --preset moderate --payload DEADBEEF
# Detect a watermark (auto-tries all presets)
npx tsx server/cli.ts detect -i video.mp4 --key SECRET
# List available presets
npx tsx server/cli.ts presets
`}</CodeBlock>
</div>
</Section>
{/* Presets reference */}
<Section title="Presets">
<div className="overflow-hidden rounded-lg ring-1 ring-zinc-800/50">
<table className="w-full text-xs">
<thead>
<tr className="border-b border-zinc-800/50 bg-zinc-900/50">
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Preset</th>
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Delta</th>
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">BCH</th>
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Masking</th>
<th className="px-3 py-1.5 text-left font-medium text-zinc-400">Use case</th>
</tr>
</thead>
<tbody>
{[
{ name: 'Light', delta: 50, bch: '(63,36,5)', mask: 'Yes', use: 'Near-invisible, mild compression' },
{ name: 'Moderate', delta: 62, bch: '(63,36,5)', mask: 'Yes', use: 'Near-invisible with perceptual masking' },
{ name: 'Strong', delta: 110, bch: '(63,36,5)', mask: 'Yes', use: 'More frequencies, handles rescaling' },
{ name: 'Fortress', delta: 150, bch: '(63,36,5)', mask: 'Yes', use: 'Maximum robustness' },
].map((p) => (
<tr key={p.name} className="border-b border-zinc-800/30 last:border-0">
<td className="px-3 py-1.5 font-medium text-zinc-200">{p.name}</td>
<td className="px-3 py-1.5 tabular-nums text-zinc-400">{p.delta}</td>
<td className="px-3 py-1.5 font-mono text-zinc-400">{p.bch}</td>
<td className="px-3 py-1.5 text-zinc-400">{p.mask}</td>
<td className="px-3 py-1.5 text-zinc-400">{p.use}</td>
</tr>
))}
</tbody>
</table>
</div>
</Section>
</div>
);
}