Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Like other web services, network and infrastructure providers may process techni

`create_hcmotion` stores the generated `.hcmotion` file (the submitted composition, compressed) in a private Vercel Blob store so it can be downloaded. Anyone with the link can download it while it is valid. Links expire **24 hours** after creation, and a daily job deletes expired files, so a file may remain stored for up to about 48 hours before deletion. The service creates no user accounts.

`propose_template` (Community templates) stores the submitted compositions, the gallery card and the source link, and publishes them in the public HyperCodeMotion Studio gallery until they are retired. It keeps a salted hash of the caller's network address for one day, only to limit proposals to 5 a day; the daily job deletes it. `get_recreation_brief` reads the public source page the user links to, and has the public source video downloaded once to keep frames of it in the private store; those frames and the comparisons between the original and the recreation are used only to check the recreation and are never published.
`propose_template` (Community templates) stores the submitted compositions, the gallery card and the source link, and publishes them in the public HyperCodeMotion Studio gallery until they are retired. It keeps a salted hash of the caller's network address for one day, only to limit proposals to 5 a day; the daily job deletes it. `get_recreation_brief` reads the public source page the user links to, and has the public source video downloaded once (for prompt-motion.com, the user's Claude downloads it and sends it here) to keep it and frames of it in the private store; those frames and the comparisons between the original and the recreation are used only to check the recreation and are never published.

The service runs on Vercel. Vercel may generate and retain infrastructure, security, deployment, and runtime logs under its own platform policies and the account configuration. We do **not** claim that Vercel stores no request metadata or that infrastructure logs have zero retention. Exact infrastructure retention can vary with plan, product, and configuration. Please consult [Vercel's Privacy Policy](https://vercel.com/legal/privacy-policy) and [Vercel's documentation](https://vercel.com/docs/observability).

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Both links expire **24 hours** after they are created. Save the project from Stu
| `create_hcmotion` | Validates a finished composition and packages it as a `.hcmotion` file. Invalid input returns `created: false` with the errors to fix and stores nothing. Valid input returns `open` and `download` links (24 h). | write (creates a temporary file), not destructive |
| `get_template` | Returns the full design of a Studio gallery template by its id and format (brief, scenes, HTML scene documents and layers), to use as a design reference. | read-only |
| `validate_json` | Checks a composition, design or improvement patch and returns errors and warnings. Preliminary: it does not prove a correct render. | read-only |
| `get_recreation_brief` | Takes the link of a public motion video (prompt-motion.com or oneshotted.io), plus the video file, prompt, title and author that Claude reads on that page (the source sites often refuse servers), and returns its source details, its published prompt when available, the brief to recreate it as a gallery template and reference frames of the original video (extracted once per video; the first call may answer that they are being prepared). | write (starts the frame extraction), not destructive |
| `get_recreation_brief` | Takes the link of a public motion video (prompt-motion.com or oneshotted.io), plus the video file, prompt, title and author that Claude reads on that page (the source sites often refuse servers), and returns its source details, its published prompt when available, the brief to recreate it as a gallery template and reference frames of the original video (extracted once per video; the first call may answer that they are being prepared). prompt-motion.com refuses every server, so for its videos the brief returns a short script that Claude runs in its own code tool to download the video and send it here in parts. | write (starts the frame extraction), not destructive |
| `propose_template` | Submits the recreation (three compositions and the gallery card). Checks the source, the compositions and the card; when accepted, renders the composition in the original's format with the Studio engine and compares it with the original frame by frame. Send fixes with the same code (up to 3 comparisons). Returns a tracking code. | write, not destructive |
| `get_proposal_status` | Returns the status of a proposal by its tracking code: comparing, review (with the comparison sheet: original and recreation side by side), rendering, published, failed (with the reason) or retired. | read-only |
| `publish_template` | Publishes a compared proposal in the Studio gallery marked «Community», with notes on what is consciously different from the original. | write, not destructive |
Expand Down
23 changes: 22 additions & 1 deletion api/community.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { finishCompare, finishRef, finishRender, putCompare, putMedia, putRef, readCompare, readIndex, readMedia, readTemplateWithStatus, retire } from "../src/community.js";
import { finishCompare, finishRef, finishRender, finishUpload, putCompare, putMedia, putRef, putUpload, readCompare, readIndex, readMedia, readTemplateWithStatus, readUpload, retire } from "../src/community.js";

const MAX = 4 * 1024 * 1024;
const json = (res, status, payload, cache = "no-store") => { res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.setHeader("Cache-Control", cache); res.end(JSON.stringify(payload)); };
Expand All @@ -17,6 +17,9 @@ async function body(req) {
* GET /c/<id>/<variant>.json a template's composition (cuadrado, vertical, horizontal)
* GET /c/<id>/<id>-<ratio>.<ext> its preview media
* GET /api/community?op=compare&id=&file= latest comparison, sheet.jpg or comparacion.mp4 (COMMUNITY_ADMIN_SECRET)
* GET /api/community?op=part&id=&part= a part of an original the caller sent (COMMUNITY_RENDER_SECRET)
* POST /api/community?op=upload&id=&key=&part= the caller's Claude sends one part of the original (key from the brief)
* POST /api/community?op=upload-done&id=&key=&parts= all parts sent: starts the reference job
* POST /api/community?op=ref&id=&file= reference job uploads one frame sheet (COMMUNITY_RENDER_SECRET)
* POST /api/community?op=ref-done {id, ok, reason, duration, width, height, sheets} (COMMUNITY_RENDER_SECRET)
* POST /api/community?op=compare&id=&file= compare job uploads sheet.jpg or comparacion.mp4 (COMMUNITY_RENDER_SECRET)
Expand All @@ -32,6 +35,15 @@ export default async function handler(req, res) {
const op = q("op"), id = q("id"), file = q("file");
if (req.method === "OPTIONS") { res.statusCode = 204; return res.end(); }
if (req.method === "GET" || req.method === "HEAD") {
if (op === "part") {
if (!bearer(req, process.env.COMMUNITY_RENDER_SECRET)) return json(res, 401, { error: "Unauthorized" });
const bytes = await readUpload(String(id), q("part"));
if (!bytes) { res.statusCode = 404; return res.end("Not found."); }
res.statusCode = 200;
res.setHeader("Content-Type", "application/octet-stream");
res.setHeader("Cache-Control", "no-store");
return res.end(Buffer.from(bytes));
}
if (op === "compare") {
if (!bearer(req, process.env.COMMUNITY_ADMIN_SECRET)) return json(res, 401, { error: "Unauthorized" });
const bytes = await readCompare(String(id), String(file));
Expand All @@ -58,6 +70,15 @@ export default async function handler(req, res) {
if (req.method !== "POST") return json(res, 405, { error: "Use GET or POST." });
let data;
try { data = await body(req); } catch { return json(res, 413, { error: "Larger than 4 MB." }); }
if (op === "upload") {
const bytes = Buffer.isBuffer(data) ? data : null;
const r = await putUpload(String(id), q("key"), q("part"), bytes);
return json(res, r.ok ? 200 : 400, r);
}
if (op === "upload-done") {
const r = await finishUpload(String(id), q("key"), q("parts"));
return json(res, r.ok ? 200 : 400, r);
}
const uploads = { media: putMedia, ref: putRef, compare: putCompare };
if (uploads[op]) {
if (!bearer(req, process.env.COMMUNITY_RENDER_SECRET)) return json(res, 401, { error: "Unauthorized" });
Expand Down
Loading
Loading