Spaces:
Running
Running
| 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> | |
| ); | |
| } | |