@darkclaw921/umgt 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +161 -2
- package/architecture.md +105 -0
- package/bin/lm-game-hub.js +7 -0
- package/dist/server/activity.d.ts +17 -0
- package/dist/server/activity.js +55 -0
- package/dist/server/activity.js.map +1 -0
- package/dist/server/agents/activity.d.ts +36 -0
- package/dist/server/agents/activity.js +117 -0
- package/dist/server/agents/activity.js.map +1 -0
- package/dist/server/agents/claude.d.ts +5 -0
- package/dist/server/agents/claude.js +107 -0
- package/dist/server/agents/claude.js.map +1 -0
- package/dist/server/agents/codex.d.ts +5 -0
- package/dist/server/agents/codex.js +128 -0
- package/dist/server/agents/codex.js.map +1 -0
- package/dist/server/agents/core-tools.d.ts +23 -0
- package/dist/server/agents/core-tools.js +298 -0
- package/dist/server/agents/core-tools.js.map +1 -0
- package/dist/server/agents/core.d.ts +9 -0
- package/dist/server/agents/core.js +106 -0
- package/dist/server/agents/core.js.map +1 -0
- package/dist/server/agents/index.d.ts +5 -0
- package/dist/server/agents/index.js +41 -0
- package/dist/server/agents/index.js.map +1 -0
- package/dist/server/agents/kimi.d.ts +5 -0
- package/dist/server/agents/kimi.js +123 -0
- package/dist/server/agents/kimi.js.map +1 -0
- package/dist/server/agents/rpc.d.ts +19 -0
- package/dist/server/agents/rpc.js +102 -0
- package/dist/server/agents/rpc.js.map +1 -0
- package/dist/server/agents/types.d.ts +51 -0
- package/dist/server/agents/types.js +7 -0
- package/dist/server/agents/types.js.map +1 -0
- package/dist/server/app.d.ts +21 -0
- package/dist/server/app.js +38 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/asset-catalog.d.ts +5 -0
- package/dist/server/asset-catalog.js +525 -0
- package/dist/server/asset-catalog.js.map +1 -0
- package/dist/server/asset-media.d.ts +16 -0
- package/dist/server/asset-media.js +170 -0
- package/dist/server/asset-media.js.map +1 -0
- package/dist/server/asset-selection.d.ts +71 -0
- package/dist/server/asset-selection.js +273 -0
- package/dist/server/asset-selection.js.map +1 -0
- package/dist/server/assets.d.ts +69 -0
- package/dist/server/assets.js +669 -0
- package/dist/server/assets.js.map +1 -0
- package/dist/server/browser-gateway.d.ts +82 -0
- package/dist/server/browser-gateway.js +285 -0
- package/dist/server/browser-gateway.js.map +1 -0
- package/dist/server/chat-computer.d.ts +13 -0
- package/dist/server/chat-computer.js +29 -0
- package/dist/server/chat-computer.js.map +1 -0
- package/dist/server/chat-games.d.ts +38 -0
- package/dist/server/chat-games.js +202 -0
- package/dist/server/chat-games.js.map +1 -0
- package/dist/server/chat.d.ts +42 -0
- package/dist/server/chat.js +318 -0
- package/dist/server/chat.js.map +1 -0
- package/dist/server/cli.d.ts +14 -0
- package/dist/server/cli.js +117 -0
- package/dist/server/cli.js.map +1 -0
- package/dist/server/core-client.d.ts +69 -0
- package/dist/server/core-client.js +357 -0
- package/dist/server/core-client.js.map +1 -0
- package/dist/server/directory-picker.d.ts +14 -0
- package/dist/server/directory-picker.js +113 -0
- package/dist/server/directory-picker.js.map +1 -0
- package/dist/server/engine.d.ts +36 -0
- package/dist/server/engine.js +507 -0
- package/dist/server/engine.js.map +1 -0
- package/dist/server/events.d.ts +12 -0
- package/dist/server/events.js +137 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/game-launch.d.ts +16 -0
- package/dist/server/game-launch.js +143 -0
- package/dist/server/game-launch.js.map +1 -0
- package/dist/server/html5.d.ts +9 -0
- package/dist/server/html5.js +107 -0
- package/dist/server/html5.js.map +1 -0
- package/dist/server/interview.d.ts +241 -0
- package/dist/server/interview.js +190 -0
- package/dist/server/interview.js.map +1 -0
- package/dist/server/mcp.d.ts +27 -0
- package/dist/server/mcp.js +91 -0
- package/dist/server/mcp.js.map +1 -0
- package/dist/server/plugins.d.ts +8 -0
- package/dist/server/plugins.js +144 -0
- package/dist/server/plugins.js.map +1 -0
- package/dist/server/process.d.ts +15 -0
- package/dist/server/process.js +89 -0
- package/dist/server/process.js.map +1 -0
- package/dist/server/publication-materials.d.ts +18 -0
- package/dist/server/publication-materials.js +104 -0
- package/dist/server/publication-materials.js.map +1 -0
- package/dist/server/publication-mcp.d.ts +3 -0
- package/dist/server/publication-mcp.js +43 -0
- package/dist/server/publication-mcp.js.map +1 -0
- package/dist/server/publication.d.ts +54 -0
- package/dist/server/publication.js +334 -0
- package/dist/server/publication.js.map +1 -0
- package/dist/server/push.d.ts +81 -0
- package/dist/server/push.js +122 -0
- package/dist/server/push.js.map +1 -0
- package/dist/server/reports.d.ts +4 -0
- package/dist/server/reports.js +40 -0
- package/dist/server/reports.js.map +1 -0
- package/dist/server/roblox-assets.d.ts +41 -0
- package/dist/server/roblox-assets.js +131 -0
- package/dist/server/roblox-assets.js.map +1 -0
- package/dist/server/roblox-connection.d.ts +54 -0
- package/dist/server/roblox-connection.js +331 -0
- package/dist/server/roblox-connection.js.map +1 -0
- package/dist/server/roblox-install.d.ts +4 -0
- package/dist/server/roblox-install.js +170 -0
- package/dist/server/roblox-install.js.map +1 -0
- package/dist/server/roblox-mcp.d.ts +2 -0
- package/dist/server/roblox-mcp.js +34 -0
- package/dist/server/roblox-mcp.js.map +1 -0
- package/dist/server/roblox-play.d.ts +23 -0
- package/dist/server/roblox-play.js +84 -0
- package/dist/server/roblox-play.js.map +1 -0
- package/dist/server/roblox-project-tools.d.ts +11 -0
- package/dist/server/roblox-project-tools.js +119 -0
- package/dist/server/roblox-project-tools.js.map +1 -0
- package/dist/server/roblox-setup.d.ts +37 -0
- package/dist/server/roblox-setup.js +206 -0
- package/dist/server/roblox-setup.js.map +1 -0
- package/dist/server/roblox-skills.d.ts +15 -0
- package/dist/server/roblox-skills.js +15 -0
- package/dist/server/roblox-skills.js.map +1 -0
- package/dist/server/roblox.d.ts +30 -0
- package/dist/server/roblox.js +248 -0
- package/dist/server/roblox.js.map +1 -0
- package/dist/server/routes.d.ts +11 -0
- package/dist/server/routes.js +223 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/security.d.ts +4 -0
- package/dist/server/security.js +64 -0
- package/dist/server/security.js.map +1 -0
- package/dist/server/skills.d.ts +4 -0
- package/dist/server/skills.js +123 -0
- package/dist/server/skills.js.map +1 -0
- package/dist/server/store.d.ts +25 -0
- package/dist/server/store.js +89 -0
- package/dist/server/store.js.map +1 -0
- package/dist/server/templates/roblox-coin-run.d.ts +2 -0
- package/dist/server/templates/roblox-coin-run.js +326 -0
- package/dist/server/templates/roblox-coin-run.js.map +1 -0
- package/dist/server/templates/roblox-skills.json +55 -0
- package/dist/server/templates/yandex-sdk.d.ts +3 -0
- package/dist/server/templates/yandex-sdk.js +177 -0
- package/dist/server/templates/yandex-sdk.js.map +1 -0
- package/dist/server/updates.d.ts +28 -0
- package/dist/server/updates.js +99 -0
- package/dist/server/updates.js.map +1 -0
- package/dist/server/video-media.d.ts +47 -0
- package/dist/server/video-media.js +224 -0
- package/dist/server/video-media.js.map +1 -0
- package/dist/server/video-routes.d.ts +7 -0
- package/dist/server/video-routes.js +91 -0
- package/dist/server/video-routes.js.map +1 -0
- package/dist/server/video.d.ts +214 -0
- package/dist/server/video.js +301 -0
- package/dist/server/video.js.map +1 -0
- package/dist/server/web-build.d.ts +16 -0
- package/dist/server/web-build.js +232 -0
- package/dist/server/web-build.js.map +1 -0
- package/dist/server/workflow.d.ts +221 -0
- package/dist/server/workflow.js +826 -0
- package/dist/server/workflow.js.map +1 -0
- package/dist/server/yandex-config.d.ts +33 -0
- package/dist/server/yandex-config.js +12 -0
- package/dist/server/yandex-config.js.map +1 -0
- package/dist/server/yandex-godot.d.ts +5 -0
- package/dist/server/yandex-godot.js +107 -0
- package/dist/server/yandex-godot.js.map +1 -0
- package/dist/shared/aspects.d.ts +48 -0
- package/dist/shared/aspects.js +22 -0
- package/dist/shared/aspects.js.map +1 -0
- package/dist/shared/assistant.d.ts +7 -0
- package/dist/shared/assistant.js +36 -0
- package/dist/shared/assistant.js.map +1 -0
- package/dist/shared/chat.d.ts +46 -0
- package/dist/shared/chat.js +2 -0
- package/dist/shared/chat.js.map +1 -0
- package/dist/shared/settings.d.ts +11 -0
- package/dist/shared/settings.js +25 -0
- package/dist/shared/settings.js.map +1 -0
- package/dist/shared/types.d.ts +510 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/assets/AgentActivity-sYJeY5Yf.js +9 -0
- package/dist/web/assets/ArrowRight.es-9w41WqD8.js +1 -0
- package/dist/web/assets/ArrowUpRight.es-BmIrpjWN.js +1 -0
- package/dist/web/assets/AssetMediaPreview-D2cd6oQO.js +1 -0
- package/dist/web/assets/AssetSelectionWizard-a43_6NO7.js +1 -0
- package/dist/web/assets/CaretDown.es-Cq33NYip.js +1 -0
- package/dist/web/assets/CheckCircle.es-D5B3hdvz.js +1 -0
- package/dist/web/assets/CoreAccount-D4ZC8CWv.js +1 -0
- package/dist/web/assets/DownloadSimple.es-DwOWFbs2.js +1 -0
- package/dist/web/assets/FileText.es-D83IIiOG.js +1 -0
- package/dist/web/assets/ImageSquare.es-DxCFt99_.js +1 -0
- package/dist/web/assets/KidsApp-BXWaALoK.css +1 -0
- package/dist/web/assets/KidsApp-D-8t3HPy.js +2 -0
- package/dist/web/assets/MagnifyingGlass.es-rkdOU2rH.js +1 -0
- package/dist/web/assets/PaperPlaneRight.es-U0twumpP.js +1 -0
- package/dist/web/assets/PencilSimple.es-JVHwRvGx.js +1 -0
- package/dist/web/assets/PlainChat-BC4K_tmj.css +1 -0
- package/dist/web/assets/PlainChat-CMxYwKko.js +1 -0
- package/dist/web/assets/Sparkle.es-CmGpvnNm.js +1 -0
- package/dist/web/assets/Square.es-BjNN9Ypc.js +1 -0
- package/dist/web/assets/UploadSimple.es-DxIuxpCC.js +1 -0
- package/dist/web/assets/WarningCircle.es-D63H8cL9.js +1 -0
- package/dist/web/assets/WorkflowJournal-Did0sRta.js +1 -0
- package/dist/web/assets/YandexConfigFields-DDL22Bfw.js +1 -0
- package/dist/web/assets/YandexGamesView-R64RSJRL.js +2 -0
- package/dist/web/assets/bg-build-DIalyCDw.svg +3 -0
- package/dist/web/assets/bg-idea-DHcE-Dhs.svg +3 -0
- package/dist/web/assets/bg-materials-UDVFaueI.svg +3 -0
- package/dist/web/assets/bg-plan-DpfwIsqJ.svg +3 -0
- package/dist/web/assets/bg-play-CNc1QZFK.svg +3 -0
- package/dist/web/assets/dist-7vx7MTaJ.js +1 -0
- package/dist/web/assets/index-Izzbh11U.css +1 -0
- package/dist/web/assets/index-sNciOZYG.js +13 -0
- package/dist/web/assets/jsx-runtime-CdVFa8IP.js +1 -0
- package/dist/web/assets/manrope-cyrillic-wght-normal-Dvxsihut.woff2 +0 -0
- package/dist/web/assets/manrope-greek-wght-normal-DL7QRZyv.woff2 +0 -0
- package/dist/web/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2 +0 -0
- package/dist/web/assets/manrope-latin-wght-normal-DHIcAJRg.woff2 +0 -0
- package/dist/web/assets/manrope-vietnamese-wght-normal-usUDDRr7.woff2 +0 -0
- package/dist/web/assets/resources-DppXCagL.js +1 -0
- package/dist/web/assets/robot-happy-B6YvlU94.svg +24 -0
- package/dist/web/assets/robot-hello-CCsQgsS-.svg +24 -0
- package/dist/web/assets/robot-listen-Buuz9xII.svg +24 -0
- package/dist/web/assets/robot-sad-CRtiNVWs.svg +24 -0
- package/dist/web/assets/robot-think-CJ5fDBTm.svg +24 -0
- package/dist/web/assets/star-big-CSFDKjws.svg +22 -0
- package/dist/web/assets/star-empty-CC-5vioJ.svg +3 -0
- package/dist/web/assets/star-next-ztuDjOHU.svg +9 -0
- package/dist/web/assets/star-on-l_krO3aV.svg +10 -0
- package/dist/web/assets/ui-wHE_yBK0.js +41 -0
- package/dist/web/assets/umgt-logo-DAOkTN9u.png +0 -0
- package/dist/web/assets/unbounded-cyrillic-wght-normal-C627f1f8.woff2 +0 -0
- package/dist/web/assets/unbounded-latin-ext-wght-normal-D6H-S6MW.woff2 +0 -0
- package/dist/web/assets/unbounded-latin-wght-normal-D5afBTBx.woff2 +0 -0
- package/dist/web/assets/unbounded-vietnamese-wght-normal-rat7GD3o.woff2 +0 -0
- package/dist/web/assets/workflow-BhcU9uvG.css +1 -0
- package/dist/web/assets/workflow-BrIKN5ER.js +34 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/icons/favicon-16.png +0 -0
- package/dist/web/icons/favicon-32.png +0 -0
- package/dist/web/icons/icon-192.png +0 -0
- package/dist/web/icons/icon-512.png +0 -0
- package/dist/web/icons/maskable-192.png +0 -0
- package/dist/web/icons/maskable-512.png +0 -0
- package/dist/web/index.html +28 -0
- package/dist/web/manifest.webmanifest +19 -0
- package/dist/web/sw.js +25 -0
- package/docs/agent-activity-ui.md +33 -0
- package/docs/agent-activity.md +43 -0
- package/docs/agent-permissions-ui.md +15 -0
- package/docs/api.md +145 -0
- package/docs/approval-journal.md +13 -0
- package/docs/asset-media.md +27 -0
- package/docs/asset-providers.md +34 -0
- package/docs/asset-selection.md +31 -0
- package/docs/automatic-approvals.md +15 -0
- package/docs/background-game-launch.md +17 -0
- package/docs/billing-validation.md +23 -0
- package/docs/core-isolation.md +37 -0
- package/docs/core.md +45 -0
- package/docs/generated-text-ui.md +41 -0
- package/docs/interactive-interview-ui.md +29 -0
- package/docs/interactive-interview.md +52 -0
- package/docs/local-codex.md +51 -0
- package/docs/local-meshy.md +13 -0
- package/docs/markdown.md +11 -0
- package/docs/permission-ui.md +11 -0
- package/docs/plan.md +47 -0
- package/docs/remote-core-deployment.md +47 -0
- package/docs/roblox.md +90 -0
- package/docs/validation.md +228 -0
- package/docs/video-reference.md +35 -0
- package/docs/workflow-journal-ui.md +21 -0
- package/docs/yandex-foundation.md +27 -0
- package/docs/yandex-games.md +45 -0
- package/package.json +95 -4
- package/scripts/create-roblox-demo.mjs +31 -0
- package/scripts/diagnose-roblox-mcp.mjs +45 -0
- package/scripts/open-hub-chrome.mjs +60 -0
- package/scripts/smoke-agent.mjs +91 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Browser-only runtime shipped verbatim into both HTML5 and Godot Web builds. */
|
|
2
|
+
export declare const yandexSdkSource: string;
|
|
3
|
+
export declare const yandexSdkTypes = "export interface PlatformHooks { pause?(paused:boolean):void; /** Forced pause mute; combine with the player's saved mute preference. */ mute?(muted:boolean):void; warning?(message:string):void; checkpoint?():void|Promise<void> }\nexport interface GamePlatform {\n mode:'local'|'yandex'; readonly storageStatus:'local'|'cloud'|'local-fallback'; language:string; ready():void; start():void; stop():void;\n load():Promise<Record<string,unknown>>; save(data:Record<string,unknown>):Promise<void>;\n authorized():Promise<boolean>; authorize():Promise<boolean>;\n advertisement(kind:'interstitial'|'rewarded',onReward?:()=>void|Promise<void>):Promise<{shown:boolean;rewarded:boolean}>;\n catalogue():Promise<unknown[]>; purchase(id:string):Promise<Record<string,number>>; recoverPurchases():Promise<Record<string,number>>;\n leaderboard(name:string,operation?:'entries'|'score'|'player',value?:unknown):Promise<unknown>; dispose():void;\n}\nexport declare function createGamePlatform(config:unknown,hooks?:PlatformHooks,environment?:unknown):Promise<GamePlatform>;\n";
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** Browser-only runtime shipped verbatim into both HTML5 and Godot Web builds. */
|
|
2
|
+
export const yandexSdkSource = String.raw `
|
|
3
|
+
const initializedSDKs = new WeakMap();
|
|
4
|
+
export async function createGamePlatform(config, hooks = {}, environment = globalThis) {
|
|
5
|
+
const local = config === null;
|
|
6
|
+
let initialization = initializedSDKs.get(environment);
|
|
7
|
+
if (!local && !initialization && environment.YaGames) {
|
|
8
|
+
initialization = Promise.resolve().then(() => environment.YaGames.init());
|
|
9
|
+
initializedSDKs.set(environment, initialization);
|
|
10
|
+
void initialization.catch(() => { if (initializedSDKs.get(environment) === initialization) initializedSDKs.delete(environment); });
|
|
11
|
+
}
|
|
12
|
+
const sdk = local ? null : await initialization;
|
|
13
|
+
const identity = sdk?.environment?.app?.id ?? config?.appId ?? environment.location?.pathname ?? 'local';
|
|
14
|
+
const saveKey = identity === 'local' ? 'lmgh-save' : 'lmgh-save:' + encodeURIComponent(identity);
|
|
15
|
+
const pendingKey = saveKey + '-pending';
|
|
16
|
+
if (!local && !sdk) throw new Error('Yandex Games SDK unavailable: open through the SDK development proxy or Yandex Games.');
|
|
17
|
+
let player; let ready = false; let wanted = false; let playing = false; let adActive = false; let cloudQueue = Promise.resolve(); let disposed = false; let endAd;
|
|
18
|
+
let storageStatus = local || !config.cloudSaves ? 'local' : 'cloud';
|
|
19
|
+
const quotas = new Map();
|
|
20
|
+
const delay = milliseconds => new Promise(resolve => (environment.setTimeout ?? setTimeout)(resolve, milliseconds));
|
|
21
|
+
// Sliding windows retain documented limits even when many callers race.
|
|
22
|
+
const limited = (name, count, window, operation) => {
|
|
23
|
+
const bucket = quotas.get(name) ?? {times: [], queue: Promise.resolve()}; quotas.set(name, bucket);
|
|
24
|
+
const task = bucket.queue.then(async () => {
|
|
25
|
+
if (disposed) throw new Error('Platform disposed');
|
|
26
|
+
while (true) {
|
|
27
|
+
const now = Date.now(); bucket.times = bucket.times.filter(time => now - time < window);
|
|
28
|
+
if (bucket.times.length < count) { bucket.times.push(now); return operation(); }
|
|
29
|
+
await delay(Math.max(1, bucket.times[0] + window - now));
|
|
30
|
+
if (disposed) throw new Error('Platform disposed');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
bucket.queue = task.catch(() => {}); return task;
|
|
34
|
+
};
|
|
35
|
+
const serial = operation => { const task = cloudQueue.then(operation); cloudQueue = task.catch(() => {}); return task; };
|
|
36
|
+
const encoded = data => {
|
|
37
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('Save must be an object');
|
|
38
|
+
const text = JSON.stringify(data);
|
|
39
|
+
if (new TextEncoder().encode(text).byteLength > 190000) throw new Error('Save exceeds cloud UTF-8 byte limit');
|
|
40
|
+
return text;
|
|
41
|
+
};
|
|
42
|
+
const readCloud = async account => limited('player.getData', 100, 300000, () => account.getData());
|
|
43
|
+
const writeCloud = async (account, data) => { encoded(data); return limited('player.setData', 100, 300000, () => account.setData(data, true)); };
|
|
44
|
+
const writeLocal = data => { try { environment.localStorage?.setItem(saveKey, encoded(data)); } catch(error) { hooks.warning?.('Local backup unavailable: ' + String(error)); } };
|
|
45
|
+
const pauses = new Set(); const listeners = [];
|
|
46
|
+
const apply = () => {
|
|
47
|
+
const next = wanted && pauses.size === 0;
|
|
48
|
+
if (next !== playing) { playing = next; if (next) sdk?.features?.GameplayAPI?.start(); else sdk?.features?.GameplayAPI?.stop(); }
|
|
49
|
+
hooks.pause?.(!next); hooks.mute?.(!next);
|
|
50
|
+
};
|
|
51
|
+
const pause = (reason, value) => { if (value) pauses.add(reason); else pauses.delete(reason); apply(); };
|
|
52
|
+
const listen = (object, event, callback) => { object?.addEventListener?.(event, callback); listeners.push(() => object?.removeEventListener?.(event, callback)); };
|
|
53
|
+
listen(environment.document, 'visibilitychange', () => pause('hidden', environment.document.hidden));
|
|
54
|
+
listen(environment, 'blur', () => pause('focus', true));
|
|
55
|
+
listen(environment, 'focus', () => pause('focus', false));
|
|
56
|
+
const onPause = () => pause('platform', true); const onResume = () => pause('platform', false);
|
|
57
|
+
sdk?.on?.('game_api_pause', onPause); sdk?.on?.('game_api_resume', onResume);
|
|
58
|
+
if (environment.document?.hidden) pauses.add('hidden');
|
|
59
|
+
const getPlayer = async () => {
|
|
60
|
+
if (!player) { player = limited('getPlayer', 20, 300000, () => sdk.getPlayer()); player.catch(() => { player = undefined; }); }
|
|
61
|
+
return player;
|
|
62
|
+
};
|
|
63
|
+
const readLocal = () => { try { const value = JSON.parse(environment.localStorage?.getItem(saveKey) ?? '{}'); return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; } catch { return {}; } };
|
|
64
|
+
const publicApi = {
|
|
65
|
+
mode: local ? 'local' : 'yandex',
|
|
66
|
+
get storageStatus() { return storageStatus; },
|
|
67
|
+
language: sdk?.environment?.i18n?.lang ?? config?.languages?.[0] ?? 'en',
|
|
68
|
+
ready() { if (!ready) { ready = true; sdk?.features?.LoadingAPI?.ready(); } },
|
|
69
|
+
start() { wanted = true; apply(); }, stop() { wanted = false; apply(); },
|
|
70
|
+
load() { return serial(async () => {
|
|
71
|
+
if (local || !config.cloudSaves) return readLocal();
|
|
72
|
+
try {
|
|
73
|
+
const data = await readCloud(await getPlayer());
|
|
74
|
+
if (environment.localStorage?.getItem(pendingKey) === 'true') { storageStatus = 'local-fallback'; hooks.warning?.('Unsynced local progress retained. Save explicitly to sync; cloud state was not overwritten during load.'); return readLocal(); }
|
|
75
|
+
storageStatus = 'cloud'; writeLocal(data); return data;
|
|
76
|
+
}
|
|
77
|
+
catch(error) { storageStatus = 'local-fallback'; hooks.warning?.('Cloud load unavailable; using local backup: ' + String(error)); return readLocal(); }
|
|
78
|
+
}); },
|
|
79
|
+
save(data) {
|
|
80
|
+
// Snapshot caller data before queueing; ordinary saves cannot erase or forge purchases.
|
|
81
|
+
const snapshot = JSON.parse(encoded(data));
|
|
82
|
+
delete snapshot.lmghPurchaseLedger; delete snapshot.lmghWallet; delete snapshot.lmghSaveVersion;
|
|
83
|
+
return serial(async () => {
|
|
84
|
+
if (local || !config.cloudSaves) { writeLocal({...snapshot, lmghSaveVersion: 1}); return; }
|
|
85
|
+
try {
|
|
86
|
+
const account = await getPlayer(); const remote = await readCloud(account);
|
|
87
|
+
const saved = {...snapshot, lmghSaveVersion: 1};
|
|
88
|
+
for (const key of ['lmghPurchaseLedger', 'lmghWallet']) if (Object.hasOwn(remote, key)) saved[key] = remote[key];
|
|
89
|
+
await writeCloud(account, saved); storageStatus = 'cloud'; writeLocal(saved); environment.localStorage?.removeItem?.(pendingKey);
|
|
90
|
+
} catch(error) { storageStatus = 'local-fallback'; writeLocal({...snapshot, lmghSaveVersion: 1}); try { environment.localStorage?.setItem(pendingKey, 'true'); } catch {} hooks.warning?.('Cloud save failed; local progress retained. Retry explicitly: ' + String(error)); throw error; }
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
async authorized() { return !local && (await getPlayer()).isAuthorized(); },
|
|
94
|
+
async authorize() { if (local || !config.authorization) throw new Error('Authorization is disabled'); await sdk.auth.openAuthDialog(); player = limited('getPlayer', 20, 300000, () => sdk.getPlayer()); return (await getPlayer()).isAuthorized(); },
|
|
95
|
+
async advertisement(kind, onReward = () => {}) {
|
|
96
|
+
if (!['interstitial', 'rewarded'].includes(kind) || local || !config.ads[kind]) throw new Error('Advertisement is disabled');
|
|
97
|
+
if (adActive) throw new Error('Advertisement already active');
|
|
98
|
+
adActive = true;
|
|
99
|
+
try { await hooks.checkpoint?.(); } catch(error) { adActive = false; throw error; }
|
|
100
|
+
pause('ad', true);
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
let closing = false; let rewarded = false; let reward = Promise.resolve();
|
|
103
|
+
const timer = (environment.setTimeout ?? setTimeout)(() => finish(new Error('Advertisement timed out')), 120000);
|
|
104
|
+
const finish = (error, shown) => {
|
|
105
|
+
if (closing) return; closing = true;
|
|
106
|
+
(environment.clearTimeout ?? clearTimeout)(timer);
|
|
107
|
+
void reward.then(() => {
|
|
108
|
+
adActive = false; endAd = undefined; pause('ad', false);
|
|
109
|
+
error ? reject(error) : resolve({shown: !!shown, rewarded});
|
|
110
|
+
}, rewardError => { adActive = false; endAd = undefined; pause('ad', false); reject(rewardError); });
|
|
111
|
+
};
|
|
112
|
+
endAd = () => finish(new Error('Platform disposed'));
|
|
113
|
+
const callbacks = {
|
|
114
|
+
onOpen: () => { if (!closing) pause('ad', true); },
|
|
115
|
+
onClose: shown => finish(null, shown), onError: error => finish(error),
|
|
116
|
+
onRewarded: () => {
|
|
117
|
+
if (kind !== 'rewarded' || closing || rewarded) return;
|
|
118
|
+
rewarded = true; reward = Promise.resolve().then(onReward); void reward.catch(() => {});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
try { if (kind === 'rewarded') sdk.adv.showRewardedVideo({callbacks}); else sdk.adv.showFullscreenAdv({callbacks}); } catch(error) { finish(error); }
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
async catalogue() { if (local || !config.payments.enabled) throw new Error('Payments disabled'); return (await sdk.getPayments()).getCatalog(); },
|
|
125
|
+
async purchase(id) {
|
|
126
|
+
if (!config?.payments.enabled || !config.payments.products.some(product => product.id === id)) throw new Error('Unknown or disabled product');
|
|
127
|
+
const payment = await (await sdk.getPayments()).purchase({id}); return publicApi.recoverPurchases([payment]);
|
|
128
|
+
},
|
|
129
|
+
recoverPurchases(provided) {
|
|
130
|
+
return serial(async () => {
|
|
131
|
+
if (local || !config.payments.enabled || !config.cloudSaves) throw new Error('Payments require cloud saves');
|
|
132
|
+
const paymentApi = await sdk.getPayments(); const items = provided ?? await paymentApi.getPurchases(); const account = await getPlayer();
|
|
133
|
+
const data = await readCloud(account); const ledger = Object.assign(Object.create(null), data.lmghPurchaseLedger ?? {}); const wallet = Object.assign(Object.create(null), data.lmghWallet ?? {});
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
const product = config.payments.products.find(product => product.id === item.productID);
|
|
136
|
+
if (!product || !item.purchaseToken) continue;
|
|
137
|
+
if (!Object.hasOwn(ledger, item.purchaseToken)) {
|
|
138
|
+
for (const [key, amount] of Object.entries(product.grant)) { const balance = Number(Object.hasOwn(wallet, key) ? wallet[key] : 0) + amount; if (!Number.isFinite(balance) || balance < 0) throw new Error('Invalid wallet balance'); wallet[key] = balance; }
|
|
139
|
+
ledger[item.purchaseToken] = {productId: item.productID, grantedAt: Date.now()};
|
|
140
|
+
const saved = {...data, lmghSaveVersion: 1, lmghWallet: wallet, lmghPurchaseLedger: ledger};
|
|
141
|
+
encoded(saved); await writeCloud(account, saved);
|
|
142
|
+
// Purchase repair must not discard gameplay progress awaiting sync.
|
|
143
|
+
if (environment.localStorage?.getItem(pendingKey) === 'true') {
|
|
144
|
+
storageStatus = 'local-fallback'; writeLocal({...readLocal(), lmghSaveVersion: 1, lmghWallet: wallet, lmghPurchaseLedger: ledger});
|
|
145
|
+
} else { storageStatus = 'cloud'; writeLocal(saved); }
|
|
146
|
+
}
|
|
147
|
+
if (product.consumable) await paymentApi.consumePurchase(item.purchaseToken);
|
|
148
|
+
}
|
|
149
|
+
return wallet;
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
async leaderboard(name, operation = 'entries', value) {
|
|
153
|
+
if (local || !config.leaderboards.includes(name)) throw new Error('Unknown leaderboard');
|
|
154
|
+
const method = operation === 'score' ? 'setScore' : operation === 'player' ? 'getPlayerEntry' : 'getEntries';
|
|
155
|
+
if (!await sdk.isAvailableMethod('leaderboards.' + method)) throw new Error('Leaderboard method unavailable; authorization may be required');
|
|
156
|
+
if (operation === 'score' && (!Number.isSafeInteger(value) || value < 0)) throw new Error('Score must be a nonnegative safe integer');
|
|
157
|
+
const count = method === 'setScore' ? 1 : method === 'getPlayerEntry' ? 60 : 20;
|
|
158
|
+
const window = method === 'setScore' ? 1000 : 300000;
|
|
159
|
+
return limited('leaderboards.' + method, count, window, () => sdk.leaderboards[method](name, value));
|
|
160
|
+
},
|
|
161
|
+
dispose() { disposed = true; endAd?.(); wanted = false; apply(); listeners.forEach(remove => remove()); sdk?.off?.('game_api_pause', onPause); sdk?.off?.('game_api_resume', onResume); }
|
|
162
|
+
};
|
|
163
|
+
return publicApi;
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
export const yandexSdkTypes = `export interface PlatformHooks { pause?(paused:boolean):void; /** Forced pause mute; combine with the player's saved mute preference. */ mute?(muted:boolean):void; warning?(message:string):void; checkpoint?():void|Promise<void> }
|
|
167
|
+
export interface GamePlatform {
|
|
168
|
+
mode:'local'|'yandex'; readonly storageStatus:'local'|'cloud'|'local-fallback'; language:string; ready():void; start():void; stop():void;
|
|
169
|
+
load():Promise<Record<string,unknown>>; save(data:Record<string,unknown>):Promise<void>;
|
|
170
|
+
authorized():Promise<boolean>; authorize():Promise<boolean>;
|
|
171
|
+
advertisement(kind:'interstitial'|'rewarded',onReward?:()=>void|Promise<void>):Promise<{shown:boolean;rewarded:boolean}>;
|
|
172
|
+
catalogue():Promise<unknown[]>; purchase(id:string):Promise<Record<string,number>>; recoverPurchases():Promise<Record<string,number>>;
|
|
173
|
+
leaderboard(name:string,operation?:'entries'|'score'|'player',value?:unknown):Promise<unknown>; dispose():void;
|
|
174
|
+
}
|
|
175
|
+
export declare function createGamePlatform(config:unknown,hooks?:PlatformHooks,environment?:unknown):Promise<GamePlatform>;
|
|
176
|
+
`;
|
|
177
|
+
//# sourceMappingURL=yandex-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yandex-sdk.js","sourceRoot":"","sources":["../../../src/server/templates/yandex-sdk.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmKxC,CAAC;AACF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;CAU7B,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
export declare const packageName = "@darkclaw921/umgt";
|
|
3
|
+
export declare const installedVersion: string;
|
|
4
|
+
export interface StudioUpdate {
|
|
5
|
+
currentVersion: string;
|
|
6
|
+
latestVersion?: string;
|
|
7
|
+
available: boolean;
|
|
8
|
+
status: "checked" | "unavailable";
|
|
9
|
+
title?: string;
|
|
10
|
+
changes: string[];
|
|
11
|
+
command: string;
|
|
12
|
+
globalCommand: string;
|
|
13
|
+
checkedAt: string;
|
|
14
|
+
}
|
|
15
|
+
/** Only stable versions from the fixed official npm package can trigger an update. */
|
|
16
|
+
export declare function newerVersion(latest: string, current: string): boolean;
|
|
17
|
+
export declare class UpdateChecker {
|
|
18
|
+
private current;
|
|
19
|
+
private fetcher;
|
|
20
|
+
private now;
|
|
21
|
+
private cache?;
|
|
22
|
+
private expires;
|
|
23
|
+
private pending?;
|
|
24
|
+
constructor(current?: string, fetcher?: typeof fetch, now?: () => number);
|
|
25
|
+
check(): Promise<StudioUpdate>;
|
|
26
|
+
private lookup;
|
|
27
|
+
}
|
|
28
|
+
export declare function registerUpdates(app: FastifyInstance, checker?: UpdateChecker): void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const packageName = "@darkclaw921/umgt";
|
|
4
|
+
const stableVersion = /^(0|[1-9]\d{0,8})\.(0|[1-9]\d{0,8})\.(0|[1-9]\d{0,8})$/;
|
|
5
|
+
const manifest = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
6
|
+
export const installedVersion = manifest.version;
|
|
7
|
+
const metadata = z.object({
|
|
8
|
+
name: z.literal(packageName),
|
|
9
|
+
version: z.string().regex(stableVersion),
|
|
10
|
+
umgtReleaseNotes: z
|
|
11
|
+
.object({
|
|
12
|
+
title: z.string().max(200),
|
|
13
|
+
changes: z.array(z.string().min(1).max(1000)).max(30),
|
|
14
|
+
})
|
|
15
|
+
.optional(),
|
|
16
|
+
});
|
|
17
|
+
/** Only stable versions from the fixed official npm package can trigger an update. */
|
|
18
|
+
export function newerVersion(latest, current) {
|
|
19
|
+
if (!stableVersion.test(latest) || !stableVersion.test(current))
|
|
20
|
+
return false;
|
|
21
|
+
const a = latest.split(".").map(Number), b = current.split(".").map(Number);
|
|
22
|
+
for (let i = 0; i < 3; i++) {
|
|
23
|
+
if (a[i] !== b[i])
|
|
24
|
+
return a[i] > b[i];
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
export class UpdateChecker {
|
|
29
|
+
current;
|
|
30
|
+
fetcher;
|
|
31
|
+
now;
|
|
32
|
+
cache;
|
|
33
|
+
expires = 0;
|
|
34
|
+
pending;
|
|
35
|
+
constructor(current = installedVersion, fetcher = fetch, now = () => Date.now()) {
|
|
36
|
+
this.current = current;
|
|
37
|
+
this.fetcher = fetcher;
|
|
38
|
+
this.now = now;
|
|
39
|
+
}
|
|
40
|
+
async check() {
|
|
41
|
+
if (this.cache && this.now() < this.expires)
|
|
42
|
+
return this.cache;
|
|
43
|
+
if (this.pending)
|
|
44
|
+
return this.pending;
|
|
45
|
+
this.pending = this.lookup().finally(() => {
|
|
46
|
+
this.pending = undefined;
|
|
47
|
+
});
|
|
48
|
+
return this.pending;
|
|
49
|
+
}
|
|
50
|
+
async lookup() {
|
|
51
|
+
const common = {
|
|
52
|
+
currentVersion: this.current,
|
|
53
|
+
command: `npx --yes ${packageName}@latest`,
|
|
54
|
+
globalCommand: `npm install --global ${packageName}@latest`,
|
|
55
|
+
checkedAt: new Date(this.now()).toISOString(),
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
const response = await this.fetcher(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, {
|
|
59
|
+
headers: { Accept: "application/json" },
|
|
60
|
+
redirect: "error",
|
|
61
|
+
signal: AbortSignal.timeout(5000),
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok || !response.body)
|
|
64
|
+
throw new Error("Registry unavailable");
|
|
65
|
+
const chunks = [];
|
|
66
|
+
let size = 0;
|
|
67
|
+
for await (const chunk of response.body) {
|
|
68
|
+
size += chunk.length;
|
|
69
|
+
if (size > 512 * 1024)
|
|
70
|
+
throw new Error("Registry response too large");
|
|
71
|
+
chunks.push(chunk);
|
|
72
|
+
}
|
|
73
|
+
const value = metadata.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
74
|
+
this.cache = {
|
|
75
|
+
...common,
|
|
76
|
+
status: "checked",
|
|
77
|
+
latestVersion: value.version,
|
|
78
|
+
available: newerVersion(value.version, this.current),
|
|
79
|
+
title: value.umgtReleaseNotes?.title,
|
|
80
|
+
changes: value.umgtReleaseNotes?.changes ?? [],
|
|
81
|
+
};
|
|
82
|
+
this.expires = this.now() + 60 * 60 * 1000;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
this.cache = {
|
|
86
|
+
...common,
|
|
87
|
+
status: "unavailable",
|
|
88
|
+
available: false,
|
|
89
|
+
changes: [],
|
|
90
|
+
};
|
|
91
|
+
this.expires = this.now() + 5 * 60 * 1000;
|
|
92
|
+
}
|
|
93
|
+
return this.cache;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function registerUpdates(app, checker = new UpdateChecker()) {
|
|
97
|
+
app.get("/api/v1/updates", async () => checker.check());
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=updates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/server/updates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const MAX_VIDEO_BYTES: number;
|
|
2
|
+
export declare const VIDEO_CHUNK_BYTES: number;
|
|
3
|
+
export declare const SEGMENT_SECONDS = 30;
|
|
4
|
+
export interface VideoMetadata {
|
|
5
|
+
duration: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
hasAudio: boolean;
|
|
9
|
+
mime: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PreparedSegment {
|
|
12
|
+
images: Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
mime: 'image/jpeg';
|
|
15
|
+
time: number;
|
|
16
|
+
}>;
|
|
17
|
+
transcript: string;
|
|
18
|
+
audio: Array<{
|
|
19
|
+
start: number;
|
|
20
|
+
end: number;
|
|
21
|
+
label: string;
|
|
22
|
+
confidence: number;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export interface MediaSetupStatus {
|
|
26
|
+
status: 'missing' | 'installing' | 'ready' | 'failed';
|
|
27
|
+
message: string;
|
|
28
|
+
downloadBytes: number;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function ensureVideoSpace(path: string, bytes: number): Promise<void>;
|
|
32
|
+
export declare class VideoMedia {
|
|
33
|
+
readonly root: string;
|
|
34
|
+
private setupJob?;
|
|
35
|
+
private setupAbort?;
|
|
36
|
+
private statusValue;
|
|
37
|
+
constructor(dataDir: string);
|
|
38
|
+
private get python();
|
|
39
|
+
executable(name: 'ffmpeg' | 'ffprobe'): Promise<string>;
|
|
40
|
+
status(): Promise<MediaSetupStatus>;
|
|
41
|
+
install(): MediaSetupStatus;
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
probe(path: string, signal?: AbortSignal): Promise<VideoMetadata>;
|
|
44
|
+
prepare(path: string, directory: string, start: number, end: number, hasAudio: boolean, signal: AbortSignal, onAudio?: () => void): Promise<PreparedSegment>;
|
|
45
|
+
referenceFrames(path: string, directory: string, times: number[], signal: AbortSignal): Promise<PreparedSegment['images']>;
|
|
46
|
+
preview(path: string, out: string, start: number, end: number, signal?: AbortSignal): Promise<void>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, rm, writeFile, statfs, stat, rename } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import sharp from 'sharp';
|
|
5
|
+
import { runProcess } from './process.js';
|
|
6
|
+
export const MAX_VIDEO_BYTES = 10 * 1024 ** 3;
|
|
7
|
+
export const VIDEO_CHUNK_BYTES = 8 * 1024 ** 2;
|
|
8
|
+
export const SEGMENT_SECONDS = 30;
|
|
9
|
+
// All execution uses argument arrays, never shell interpolation of uploaded names.
|
|
10
|
+
async function command(executable, args, signal, timeout = 0) {
|
|
11
|
+
const result = await runProcess(executable, args, { signal, timeout });
|
|
12
|
+
signal?.throwIfAborted();
|
|
13
|
+
if (result.code !== 0)
|
|
14
|
+
throw new Error(`${executable} failed: ${result.output.slice(-2000)}`);
|
|
15
|
+
return result.output;
|
|
16
|
+
}
|
|
17
|
+
export async function ensureVideoSpace(path, bytes) {
|
|
18
|
+
const fs = await statfs(path);
|
|
19
|
+
if (Number(fs.bavail) * Number(fs.bsize) < bytes + 256 * 1024 ** 2)
|
|
20
|
+
throw Object.assign(new Error('Недостаточно свободного места для видео и обработки.'), { statusCode: 507 });
|
|
21
|
+
}
|
|
22
|
+
const audioWorker = String.raw `
|
|
23
|
+
import sys, os, json, pathlib
|
|
24
|
+
root=pathlib.Path(sys.argv[2])
|
|
25
|
+
lockfile=root/'models.json'
|
|
26
|
+
if sys.argv[1]=='setup':
|
|
27
|
+
from huggingface_hub import HfApi, snapshot_download
|
|
28
|
+
pins={}
|
|
29
|
+
for key,repo in [('speech','Systran/faster-whisper-small'),('sound','laion/clap-htsat-unfused')]:
|
|
30
|
+
sha=HfApi().model_info(repo).sha
|
|
31
|
+
path=snapshot_download(repo_id=repo,revision=sha,cache_dir=str(root/'models'))
|
|
32
|
+
pins[key]={'repo':repo,'revision':sha,'path':path}
|
|
33
|
+
tmp=lockfile.with_suffix('.tmp')
|
|
34
|
+
tmp.write_text(json.dumps(pins));tmp.replace(lockfile)
|
|
35
|
+
sys.exit(0)
|
|
36
|
+
import numpy as np, soundfile as sf, torch
|
|
37
|
+
from faster_whisper import WhisperModel
|
|
38
|
+
from transformers import ClapModel, ClapProcessor
|
|
39
|
+
pins=json.loads(lockfile.read_text())
|
|
40
|
+
wave,rate=sf.read(sys.argv[3],dtype='float32')
|
|
41
|
+
if wave.ndim>1: wave=wave.mean(axis=1)
|
|
42
|
+
result={'transcript':'','audio':[]}
|
|
43
|
+
if len(wave) and float(np.sqrt(np.mean(wave*wave)))>0.001:
|
|
44
|
+
speech=WhisperModel(pins['speech']['path'],device='cpu',compute_type='int8',local_files_only=True)
|
|
45
|
+
segments,info=speech.transcribe(sys.argv[3],vad_filter=True,word_timestamps=False)
|
|
46
|
+
result['transcript']='\n'.join('[%.2f-%.2f] %s'%(s.start,s.end,s.text) for s in segments)
|
|
47
|
+
del speech
|
|
48
|
+
labels=['silence','speech','background music','footsteps','running','jumping','landing','collision impact','explosion','gunshot','engine','water splash','wind','rain','animals','door opening','button click','item pickup chime','victory fanfare','failure sound','electronic game sound','ambient environment']
|
|
49
|
+
model=ClapModel.from_pretrained(pins['sound']['path'],local_files_only=True).eval()
|
|
50
|
+
processor=ClapProcessor.from_pretrained(pins['sound']['path'],local_files_only=True)
|
|
51
|
+
# Wave is normalized to 48 kHz by FFmpeg; fixed 5 s windows cover all audio.
|
|
52
|
+
for start in range(0,len(wave),rate*5):
|
|
53
|
+
clip=wave[start:start+rate*5]
|
|
54
|
+
if len(clip)<rate: clip=np.pad(clip,(0,rate-len(clip)))
|
|
55
|
+
inputs=processor(text=['The sound of '+s for s in labels],audios=clip,sampling_rate=rate,return_tensors='pt',padding=True)
|
|
56
|
+
with torch.no_grad(): scores=model(**inputs).logits_per_audio.softmax(dim=-1)[0]
|
|
57
|
+
ix=int(scores.argmax());confidence=float(scores[ix])
|
|
58
|
+
result['audio'].append({'start':start/rate,'end':min((start+rate*5)/rate,len(wave)/rate),'label':labels[ix] if confidence>=0.2 else 'unknown sound','confidence':confidence})
|
|
59
|
+
pathlib.Path(sys.argv[4]).write_text(json.dumps(result,ensure_ascii=False))
|
|
60
|
+
`;
|
|
61
|
+
export class VideoMedia {
|
|
62
|
+
root;
|
|
63
|
+
setupJob;
|
|
64
|
+
setupAbort;
|
|
65
|
+
statusValue = { status: 'missing', message: 'Нужны локальные инструменты видео и звука', downloadBytes: 3 * 1024 ** 3 };
|
|
66
|
+
constructor(dataDir) { this.root = join(dataDir, 'video-tools'); }
|
|
67
|
+
get python() { return join(this.root, 'venv', process.platform === 'win32' ? 'Scripts' : 'bin', process.platform === 'win32' ? 'python.exe' : 'python'); }
|
|
68
|
+
async executable(name) {
|
|
69
|
+
try {
|
|
70
|
+
await command(name, ['-version'], undefined, 6000);
|
|
71
|
+
return name;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
try {
|
|
75
|
+
const require = createRequire(join(this.root, 'package.json'));
|
|
76
|
+
return require(name === 'ffmpeg' ? 'ffmpeg-static' : 'ffprobe-static').path ?? require('ffmpeg-static');
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error('Установите инструменты видео через мастер подготовки.');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async status() {
|
|
84
|
+
if (this.setupJob)
|
|
85
|
+
return this.statusValue;
|
|
86
|
+
try {
|
|
87
|
+
await Promise.all([this.executable('ffmpeg'), this.executable('ffprobe'), readFile(join(this.root, 'models.json')), command(this.python, ['-c', 'import faster_whisper,transformers,torch,soundfile,av; assert int(av.__version__.split(chr(46))[0]) < 19'], undefined, 15000)]);
|
|
88
|
+
this.statusValue = { ...this.statusValue, status: 'ready', message: 'Инструменты видео и звука готовы', error: undefined };
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
if (this.statusValue.status !== 'failed')
|
|
92
|
+
this.statusValue = { ...this.statusValue, status: 'missing' };
|
|
93
|
+
}
|
|
94
|
+
return this.statusValue;
|
|
95
|
+
}
|
|
96
|
+
install() {
|
|
97
|
+
if (this.setupJob)
|
|
98
|
+
return this.statusValue;
|
|
99
|
+
this.setupAbort = new AbortController();
|
|
100
|
+
const signal = this.setupAbort.signal;
|
|
101
|
+
this.statusValue = { ...this.statusValue, status: 'installing', message: 'Устанавливаем локальные инструменты и модели', error: undefined };
|
|
102
|
+
this.setupJob = (async () => {
|
|
103
|
+
await mkdir(this.root, { recursive: true });
|
|
104
|
+
await ensureVideoSpace(this.root, 6 * 1024 ** 3);
|
|
105
|
+
try {
|
|
106
|
+
await Promise.all([this.executable('ffmpeg'), this.executable('ffprobe')]);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
await command(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', '--prefix', this.root, '--no-audit', '--no-fund', 'ffmpeg-static@5.2.0', 'ffprobe-static@3.1.0'], signal);
|
|
110
|
+
}
|
|
111
|
+
const candidates = process.platform === 'win32' ? ['python', 'python3'] : ['python3.12', 'python3.11', 'python3'];
|
|
112
|
+
let python;
|
|
113
|
+
for (const candidate of candidates)
|
|
114
|
+
try {
|
|
115
|
+
const version = await command(candidate, ['-c', 'import sys; print(sys.version_info.minor if sys.version_info.major==3 else 0)'], signal, 6000);
|
|
116
|
+
if ([11, 12, 13].includes(Number(version.trim()))) {
|
|
117
|
+
python = candidate;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
if (!python)
|
|
123
|
+
throw new Error('Для локального анализа звука установите Python 3.11–3.13 и повторите подготовку.');
|
|
124
|
+
await command(python, ['-m', 'venv', join(this.root, 'venv')], signal);
|
|
125
|
+
await command(this.python, ['-m', 'pip', 'install', 'faster-whisper==1.2.1', 'av==16.0.1', 'transformers==4.57.1', 'torch==2.9.0', 'soundfile==0.13.1', 'librosa==0.11.0'], signal);
|
|
126
|
+
await writeFile(join(this.root, 'audio-worker.py'), audioWorker);
|
|
127
|
+
await command(this.python, [join(this.root, 'audio-worker.py'), 'setup', this.root], signal);
|
|
128
|
+
await writeFile(join(this.root, 'requirements.lock'), await command(this.python, ['-m', 'pip', 'freeze'], signal));
|
|
129
|
+
this.statusValue = { ...this.statusValue, status: 'ready', message: 'Инструменты видео и звука готовы' };
|
|
130
|
+
})().catch(error => { this.statusValue = { ...this.statusValue, status: 'failed', error: String(error.message ?? error), message: 'Не удалось подготовить инструменты' }; }).finally(() => { this.setupJob = undefined; });
|
|
131
|
+
return this.statusValue;
|
|
132
|
+
}
|
|
133
|
+
async close() { this.setupAbort?.abort(); await this.setupJob; }
|
|
134
|
+
async probe(path, signal) {
|
|
135
|
+
const data = JSON.parse(await command(await this.executable('ffprobe'), ['-v', 'error', '-protocol_whitelist', 'file,pipe', '-show_format', '-show_streams', '-of', 'json', path], signal, 30000));
|
|
136
|
+
const video = data.streams?.find((s) => s.codec_type === 'video');
|
|
137
|
+
const format = String(data.format?.format_name ?? '');
|
|
138
|
+
if (!video || !/(?:mov|mp4|matroska|webm)/.test(format))
|
|
139
|
+
throw new Error('Поддерживаются видео MP4, MOV, WebM и MKV.');
|
|
140
|
+
const duration = Number(video.duration ?? data.format?.duration);
|
|
141
|
+
if (!Number.isFinite(duration) || duration <= 0 || duration > Number.MAX_SAFE_INTEGER)
|
|
142
|
+
throw new Error('Не удалось определить длительность видео.');
|
|
143
|
+
if (!video.width || !video.height || video.width > 16384 || video.height > 16384)
|
|
144
|
+
throw new Error('Неподдерживаемый размер кадров.');
|
|
145
|
+
return { duration, width: video.width, height: video.height, hasAudio: data.streams.some((s) => s.codec_type === 'audio'), mime: /webm/.test(format) ? 'video/webm' : /matroska/.test(format) ? 'video/x-matroska' : 'video/mp4' };
|
|
146
|
+
}
|
|
147
|
+
async prepare(path, directory, start, end, hasAudio, signal, onAudio) {
|
|
148
|
+
await rm(directory, { recursive: true, force: true });
|
|
149
|
+
await mkdir(directory, { recursive: true });
|
|
150
|
+
await ensureVideoSpace(directory, 256 * 1024 ** 2);
|
|
151
|
+
const ffmpeg = await this.executable('ffmpeg');
|
|
152
|
+
const common = ['-nostdin', '-v', 'error', '-protocol_whitelist', 'file,pipe', '-ss', String(start), '-i', path, '-t', String(end - start)];
|
|
153
|
+
await command(ffmpeg, [...common, '-an', '-vf', 'fps=8,scale=1280:720:force_original_aspect_ratio=decrease', '-q:v', '3', join(directory, 'frame-%06d.jpg')], signal);
|
|
154
|
+
const files = (await readdir(directory)).filter(name => /^frame-\d+\.jpg$/.test(name)).sort();
|
|
155
|
+
if (!files.length)
|
|
156
|
+
throw new Error('Видео не содержит декодируемых кадров.');
|
|
157
|
+
const selected = new Set([0, files.length - 1]);
|
|
158
|
+
let previous;
|
|
159
|
+
for (let i = 0; i < files.length; i++) {
|
|
160
|
+
signal.throwIfAborted();
|
|
161
|
+
const pixels = await sharp(join(directory, files[i])).resize(64, 36, { fit: 'fill' }).greyscale().raw().toBuffer();
|
|
162
|
+
if (i % 8 === 0)
|
|
163
|
+
selected.add(i);
|
|
164
|
+
if (previous) {
|
|
165
|
+
let delta = 0, changed = 0;
|
|
166
|
+
for (let p = 0; p < pixels.length; p++) {
|
|
167
|
+
const difference = Math.abs(pixels[p] - previous[p]);
|
|
168
|
+
delta += difference;
|
|
169
|
+
if (difference > 18)
|
|
170
|
+
changed++;
|
|
171
|
+
}
|
|
172
|
+
if (delta / pixels.length > 12 || changed / pixels.length >= 0.001)
|
|
173
|
+
for (let j = Math.max(0, i - 12); j <= Math.min(files.length - 1, i + 12); j++)
|
|
174
|
+
selected.add(j);
|
|
175
|
+
}
|
|
176
|
+
previous = pixels;
|
|
177
|
+
}
|
|
178
|
+
const images = [];
|
|
179
|
+
for (let i = 0; i < files.length; i++) {
|
|
180
|
+
const file = join(directory, files[i]);
|
|
181
|
+
if (selected.has(i))
|
|
182
|
+
images.push({ path: file, mime: 'image/jpeg', time: Math.min(start + i / 8, end) });
|
|
183
|
+
else
|
|
184
|
+
await rm(file);
|
|
185
|
+
}
|
|
186
|
+
let transcript = '', audio = [];
|
|
187
|
+
if (hasAudio) {
|
|
188
|
+
onAudio?.();
|
|
189
|
+
await command(ffmpeg, [...common, '-vn', '-ac', '1', '-ar', '48000', '-c:a', 'pcm_s16le', join(directory, 'audio.wav')], signal);
|
|
190
|
+
await writeFile(join(this.root, 'audio-worker.py'), audioWorker);
|
|
191
|
+
await command(this.python, [join(this.root, 'audio-worker.py'), 'analyze', this.root, join(directory, 'audio.wav'), join(directory, 'audio.json')], signal);
|
|
192
|
+
const result = JSON.parse(await readFile(join(directory, 'audio.json'), 'utf8'));
|
|
193
|
+
transcript = result.transcript;
|
|
194
|
+
audio = result.audio.map((item) => ({ ...item, start: start + item.start, end: start + item.end }));
|
|
195
|
+
}
|
|
196
|
+
return { images, transcript, audio };
|
|
197
|
+
}
|
|
198
|
+
async referenceFrames(path, directory, times, signal) {
|
|
199
|
+
await mkdir(directory, { recursive: true });
|
|
200
|
+
const ffmpeg = await this.executable('ffmpeg'), images = [];
|
|
201
|
+
for (const time of times) {
|
|
202
|
+
signal.throwIfAborted();
|
|
203
|
+
const frame = join(directory, `reference-${Math.round(time * 1000)}.jpg`);
|
|
204
|
+
if (!(await stat(frame).catch(() => null))?.size) {
|
|
205
|
+
const temporary = frame + '.tmp.jpg';
|
|
206
|
+
try {
|
|
207
|
+
await command(ffmpeg, ['-nostdin', '-y', '-v', 'error', '-protocol_whitelist', 'file,pipe', '-ss', String(time), '-i', path, '-frames:v', '1', '-an', '-vf', 'scale=1280:720:force_original_aspect_ratio=decrease', '-q:v', '3', temporary], signal);
|
|
208
|
+
if (!(await stat(temporary)).size)
|
|
209
|
+
throw new Error('Не удалось извлечь кадр визуального референса.');
|
|
210
|
+
await rename(temporary, frame);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
await rm(temporary, { force: true });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
images.push({ path: frame, mime: 'image/jpeg', time });
|
|
217
|
+
}
|
|
218
|
+
return images;
|
|
219
|
+
}
|
|
220
|
+
async preview(path, out, start, end, signal) {
|
|
221
|
+
await command(await this.executable('ffmpeg'), ['-nostdin', '-y', '-v', 'error', '-protocol_whitelist', 'file,pipe', '-ss', String(start), '-i', path, '-t', String(end - start), '-vf', 'scale=1280:720:force_original_aspect_ratio=decrease', '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '25', '-c:a', 'aac', '-movflags', '+faststart', out], signal);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=video-media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-media.js","sourceRoot":"","sources":["../../src/server/video-media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { Workflow } from './workflow.js';
|
|
3
|
+
export declare function parseVideoRange(header: string | undefined, size: number): {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
} | undefined;
|
|
7
|
+
export declare function registerVideoRoutes(app: FastifyInstance, workflow: Workflow): void;
|