cozyclay 1.0.0 → 1.2.0
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.
- package/README.md +59 -24
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/bin/cozyclay.mjs +105 -11
- package/dist/CNAME +1 -0
- package/dist/app/index.html +57 -0
- package/dist/assets/app-Cgpk2hwX.js +4803 -0
- package/dist/assets/app-DgZvaAE1.css +1 -0
- package/dist/favicon.ico +0 -0
- package/dist/icons/apple-touch-icon.png +0 -0
- package/dist/icons/icon-192.png +0 -0
- package/dist/icons/icon-32.png +0 -0
- package/dist/icons/icon-512.png +0 -0
- package/dist/icons/icon-maskable-512.png +0 -0
- package/dist/index.html +406 -5
- package/dist/manifest.webmanifest +42 -0
- package/dist/media/cozyclay-demo-poster.jpg +0 -0
- package/dist/media/cozyclay-logo.png +0 -0
- package/dist/robots.txt +4 -0
- package/dist/sitemap.xml +9 -0
- package/dist/sw.js +127 -0
- package/package.json +15 -3
- package/src/App.jsx +1393 -648
- package/src/ardy/client.js +5 -4
- package/src/ardy/mp4-duration.js +102 -0
- package/src/ardy/timeline-coordinates.js +11 -0
- package/src/ardy/timeline.jsx +528 -67
- package/src/ardy/waypoints.js +14 -9
- package/src/camera-block.js +60 -0
- package/src/camera-follow.js +49 -10
- package/src/camera-rail-schedule.js +176 -0
- package/src/controls.jsx +16 -2
- package/src/cuts.js +184 -0
- package/src/generation/client.js +56 -0
- package/src/generation/session.js +47 -0
- package/src/generation/shot-spec.js +40 -0
- package/src/generation/use-generation.js +55 -0
- package/src/hierarchy-model.js +9 -17
- package/src/hierarchy-panel.jsx +170 -23
- package/src/locale-toggle.jsx +18 -0
- package/src/locale.js +34 -0
- package/src/main.jsx +5 -0
- package/src/object-catalog.jsx +32 -3
- package/src/planview.jsx +174 -66
- package/src/pose-extract/browser.js +42 -0
- package/src/pose-extract/fit.js +194 -0
- package/src/pose-extract/index.js +53 -0
- package/src/pose-extract/keyframes.js +74 -0
- package/src/pose-extract/landmarks.js +162 -0
- package/src/poses.js +114 -0
- package/src/posestudio.jsx +75 -13
- package/src/pwa.js +42 -0
- package/src/result-modal.jsx +124 -0
- package/src/scenes.js +223 -0
- package/src/shot-authoring.js +204 -77
- package/src/shot.js +8 -4
- package/src/styles.css +839 -38
- package/src/ui.jsx +54 -5
- package/tools/ardy/BRIDGE.md +70 -4
- package/tools/ardy/README.md +55 -0
- package/tools/ardy/bridge.mjs +89 -389
- package/tools/ardy/cclay_constrained_generate.py +449 -0
- package/tools/ardy/cclay_motion_edit.py +74 -9
- package/tools/ardy/cclay_sequence_generate.py +68 -7
- package/tools/ardy/cclay_worker.py +304 -0
- package/tools/ardy/merge-text-encoder.py +160 -0
- package/tools/ardy/run-local.mjs +233 -0
- package/tools/ardy/runners/index.mjs +23 -0
- package/tools/ardy/runners/local.mjs +452 -0
- package/tools/ardy/runners/proc.mjs +125 -0
- package/tools/ardy/runners/remote.mjs +246 -0
- package/tools/ardy/setup-local.mjs +189 -0
- package/tools/ardy/setup-text-encoder-on-box.sh +93 -0
- package/tools/ardy/setup-text-encoder.py +296 -0
- package/tools/generation/README.md +29 -0
- package/tools/generation/bridge.mjs +149 -0
- package/tools/generation/job-store.mjs +51 -0
- package/tools/generation/providers/kling.mjs +76 -0
- package/tools/generation/providers/runway.mjs +101 -0
- package/tools/generation/providers/seedance.mjs +87 -0
- package/tools/generation/providers/shared.mjs +49 -0
- package/tools/generation/providers/veo.mjs +84 -0
- package/tools/metrics/archive-traffic.mjs +138 -0
- package/dist/assets/index-BW_S1YRy.js +0 -4395
- package/dist/assets/index-nRmmJgap.css +0 -1
- package/tools/ardy/__pycache__/cclay_sequence_generate.cpython-313.pyc +0 -0
- package/tools/ardy/out/gen-1786443326924-c6019e-generated.npz +0 -0
- package/tools/ardy/out/gen-1786443609325-0053db-generated.npz +0 -0
- package/tools/ardy/out/gen-1786443835628-749ed1-generated.npz +0 -0
- package/tools/ardy/out/gen-1786462605247-03cb19-generated.npz +0 -0
- package/tools/qa-browser.mjs +0 -84
- package/tools/qa-crop3.mjs +0 -22
- package/tools/qa-playview.mjs +0 -49
- package/tools/qa-screenshot.mjs +0 -24
- package/tools/qa-visual.mjs +0 -51
package/src/ardy/client.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* sidecar as an expected state and reports it as data — never as a thrown
|
|
8
8
|
* error — so the UI can degrade to a hint instead of crashing.
|
|
9
9
|
*/
|
|
10
|
+
import { ko, isKo } from "../locale.js";
|
|
10
11
|
|
|
11
12
|
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
12
13
|
|
|
@@ -39,7 +40,7 @@ export async function checkBridge() {
|
|
|
39
40
|
try {
|
|
40
41
|
const res = await fetch("/ardy/health", { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
|
|
41
42
|
if (!res.ok) {
|
|
42
|
-
return { ok: false, reason: await reasonOf(res, `bridge unhealthy (HTTP ${res.status})`) };
|
|
43
|
+
return { ok: false, reason: await reasonOf(res, isKo ? `브리지 상태가 좋지 않아요(HTTP ${res.status})` : `bridge unhealthy (HTTP ${res.status})`) };
|
|
43
44
|
}
|
|
44
45
|
const payload = await res.json();
|
|
45
46
|
return {
|
|
@@ -49,7 +50,7 @@ export async function checkBridge() {
|
|
|
49
50
|
device: payload.device,
|
|
50
51
|
};
|
|
51
52
|
} catch (err) {
|
|
52
|
-
return { ok: false, reason: err?.message || "bridge unreachable" };
|
|
53
|
+
return { ok: false, reason: err?.message || ko("bridge unreachable", "브리지에 연결할 수 없어요") };
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -91,7 +92,7 @@ export async function generate(body, onEvent, { signal } = {}) {
|
|
|
91
92
|
if (!res.ok) {
|
|
92
93
|
throw new Error(await reasonOf(res, `generate failed (HTTP ${res.status})`));
|
|
93
94
|
}
|
|
94
|
-
if (!res.body) throw new Error("generate: response has no body stream");
|
|
95
|
+
if (!res.body) throw new Error(ko("generate: response has no body stream", "생성 응답에 본문 스트림이 없어요"));
|
|
95
96
|
|
|
96
97
|
const reader = res.body.getReader();
|
|
97
98
|
const decoder = new TextDecoder();
|
|
@@ -118,7 +119,7 @@ export async function generate(body, onEvent, { signal } = {}) {
|
|
|
118
119
|
const terminal = applyLine(buffer.trim());
|
|
119
120
|
if (terminal) return terminal;
|
|
120
121
|
}
|
|
121
|
-
throw new Error("generate: stream ended without a done or error event");
|
|
122
|
+
throw new Error(ko("generate: stream ended without a done or error event", "완료 또는 오류 이벤트 없이 생성 스트림이 끝났어요"));
|
|
122
123
|
|
|
123
124
|
/** Parse one ndjson line; returns the done event, throws on error/bad line. */
|
|
124
125
|
function applyLine(line) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const CONTAINER_BOXES = new Set(["moov", "trak", "mdia"]);
|
|
2
|
+
|
|
3
|
+
function boxType(view, offset) {
|
|
4
|
+
return String.fromCharCode(
|
|
5
|
+
view.getUint8(offset + 4),
|
|
6
|
+
view.getUint8(offset + 5),
|
|
7
|
+
view.getUint8(offset + 6),
|
|
8
|
+
view.getUint8(offset + 7),
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function walkBoxes(view, start, end, visit) {
|
|
13
|
+
let offset = start;
|
|
14
|
+
while (offset + 8 <= end) {
|
|
15
|
+
let size = view.getUint32(offset);
|
|
16
|
+
let headerSize = 8;
|
|
17
|
+
if (size === 1) {
|
|
18
|
+
if (offset + 16 > end) throw new Error("Invalid MP4 extended-size box");
|
|
19
|
+
const extendedSize = view.getBigUint64(offset + 8);
|
|
20
|
+
if (extendedSize > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("MP4 box is too large to inspect safely");
|
|
21
|
+
size = Number(extendedSize);
|
|
22
|
+
headerSize = 16;
|
|
23
|
+
} else if (size === 0) {
|
|
24
|
+
size = end - offset;
|
|
25
|
+
}
|
|
26
|
+
if (size < headerSize || offset + size > end) throw new Error("Invalid MP4 box size");
|
|
27
|
+
|
|
28
|
+
const box = { type: boxType(view, offset), start: offset, end: offset + size, payload: offset + headerSize };
|
|
29
|
+
visit(box);
|
|
30
|
+
if (CONTAINER_BOXES.has(box.type)) walkBoxes(view, box.payload, box.end, visit);
|
|
31
|
+
offset = box.end;
|
|
32
|
+
}
|
|
33
|
+
if (offset !== end) throw new Error("Invalid trailing MP4 box data");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function timingFields(view, box) {
|
|
37
|
+
const version = view.getUint8(box.payload);
|
|
38
|
+
const timescaleOffset = box.payload + (version === 1 ? 20 : 12);
|
|
39
|
+
const durationOffset = timescaleOffset + 4;
|
|
40
|
+
const durationBytes = version === 1 ? 8 : 4;
|
|
41
|
+
if ((version !== 0 && version !== 1) || durationOffset + durationBytes > box.end) {
|
|
42
|
+
throw new Error(`Invalid ${box.type} box`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
version,
|
|
46
|
+
timescale: view.getUint32(timescaleOffset),
|
|
47
|
+
duration: version === 1 ? view.getBigUint64(durationOffset) : BigInt(view.getUint32(durationOffset)),
|
|
48
|
+
durationOffset,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writeDuration(view, fields, duration) {
|
|
53
|
+
if (fields.version === 1) {
|
|
54
|
+
view.setBigUint64(fields.durationOffset, duration);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (duration > 0xffffffffn) throw new Error("Corrected mdhd duration does not fit a version 0 box");
|
|
58
|
+
view.setUint32(fields.durationOffset, Number(duration));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Repair MediaRecorder MP4 files whose mdhd duration was written in mvhd units.
|
|
63
|
+
*
|
|
64
|
+
* mvhd is the movie header and uses the movie timescale (commonly 1000), while
|
|
65
|
+
* mdhd is a media-track header and must use that track's media timescale
|
|
66
|
+
* (commonly 30000). They describe the same elapsed time with different clocks;
|
|
67
|
+
* copying the mvhd duration number into mdhd makes a 30000 Hz track read 30x
|
|
68
|
+
* too short.
|
|
69
|
+
*/
|
|
70
|
+
export function repairMp4MediaDurations(input) {
|
|
71
|
+
const source = input instanceof ArrayBuffer
|
|
72
|
+
? new Uint8Array(input)
|
|
73
|
+
: ArrayBuffer.isView(input)
|
|
74
|
+
? new Uint8Array(input.buffer, input.byteOffset, input.byteLength)
|
|
75
|
+
: null;
|
|
76
|
+
if (!source) throw new TypeError("repairMp4MediaDurations expects an ArrayBuffer or typed array");
|
|
77
|
+
|
|
78
|
+
const output = Uint8Array.from(source);
|
|
79
|
+
const view = new DataView(output.buffer);
|
|
80
|
+
let movie = null;
|
|
81
|
+
const mediaHeaders = [];
|
|
82
|
+
walkBoxes(view, 0, output.byteLength, (box) => {
|
|
83
|
+
if (box.type === "mvhd") movie = timingFields(view, box);
|
|
84
|
+
if (box.type === "mdhd") mediaHeaders.push(timingFields(view, box));
|
|
85
|
+
});
|
|
86
|
+
if (!movie || movie.timescale === 0) throw new Error("MP4 is missing a valid mvhd box");
|
|
87
|
+
if (mediaHeaders.length === 0) throw new Error("MP4 is missing an mdhd box");
|
|
88
|
+
|
|
89
|
+
for (const media of mediaHeaders) {
|
|
90
|
+
if (media.timescale === 0) throw new Error("MP4 contains an mdhd box with timescale 0");
|
|
91
|
+
const numerator = movie.duration * BigInt(media.timescale);
|
|
92
|
+
const correctedDuration = (numerator + BigInt(movie.timescale) / 2n) / BigInt(movie.timescale);
|
|
93
|
+
writeDuration(view, media, correctedDuration);
|
|
94
|
+
}
|
|
95
|
+
return output;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Preserve the recorded Blob's MIME type while repairing its MP4 headers. */
|
|
99
|
+
export async function repairRecordedMp4(blob) {
|
|
100
|
+
if (!(blob instanceof Blob)) throw new TypeError("repairRecordedMp4 expects a Blob");
|
|
101
|
+
return new Blob([repairMp4MediaDurations(await blob.arrayBuffer())], { type: blob.type });
|
|
102
|
+
}
|
|
@@ -15,3 +15,14 @@ export function promptMoveStartFrame(startFrame, startClientX, clientX, laneWidt
|
|
|
15
15
|
const framesPerPixel = Math.max(0, displayFrameCount - 1) / Math.max(1, laneWidth);
|
|
16
16
|
return startFrame + (clientX - startClientX) * framesPerPixel;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
/** Optional Shot blocks use their own explicit inclusive range. Gaps remain
|
|
20
|
+
* visible free-camera time instead of being inferred from the next card. */
|
|
21
|
+
export function shotBlockGeometry(shots, index, frameCount, displayFrameCount = frameCount) {
|
|
22
|
+
const shot = shots[index];
|
|
23
|
+
if (!shot || frameCount < 1 || displayFrameCount < 1) return null;
|
|
24
|
+
const denominator = Math.max(1, displayFrameCount - 1);
|
|
25
|
+
const startFrame = Math.max(0, Math.min(frameCount - 1, shot.startFrame));
|
|
26
|
+
const endFrame = Math.max(startFrame, Math.min(frameCount - 1, shot.endFrame ?? startFrame));
|
|
27
|
+
return { startFrame, endFrame, startPct: startFrame / denominator, endPct: endFrame / denominator };
|
|
28
|
+
}
|