@sync.so/mcp-server 0.1.4 → 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/README.md +261 -86
- package/deploy/generation-status/app.html.gz +0 -0
- package/deploy/generation-status/release.json +6 -0
- package/dist/abort-signals.d.ts +6 -0
- package/dist/abort-signals.d.ts.map +1 -0
- package/dist/abort-signals.js +23 -0
- package/dist/account-features.d.ts +7 -0
- package/dist/account-features.d.ts.map +1 -0
- package/dist/account-features.js +48 -0
- package/dist/app-upload-relay.d.ts +13 -0
- package/dist/app-upload-relay.d.ts.map +1 -0
- package/dist/app-upload-relay.js +136 -0
- package/dist/asset-thumbnail.d.ts +3 -0
- package/dist/asset-thumbnail.d.ts.map +1 -0
- package/dist/asset-thumbnail.js +2 -0
- package/dist/auth/async-context.d.ts +5 -0
- package/dist/auth/async-context.d.ts.map +1 -0
- package/dist/auth/async-context.js +14 -0
- package/dist/auth/bearer-auth.d.ts +16 -0
- package/dist/auth/bearer-auth.d.ts.map +1 -0
- package/dist/auth/bearer-auth.js +55 -0
- package/dist/auth/client-credentials.d.ts +13 -0
- package/dist/auth/client-credentials.d.ts.map +1 -0
- package/dist/auth/client-credentials.js +67 -0
- package/dist/auth/device-auth.d.ts.map +1 -1
- package/dist/auth/device-auth.js +3 -2
- package/dist/auth/fixture-cleanup.d.ts +4 -0
- package/dist/auth/fixture-cleanup.d.ts.map +1 -0
- package/dist/auth/fixture-cleanup.js +9 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -0
- package/dist/auth/oauth-diagnostics.d.ts +11 -0
- package/dist/auth/oauth-diagnostics.d.ts.map +1 -0
- package/dist/auth/oauth-diagnostics.js +78 -0
- package/dist/auth/oauth-provider.d.ts +16 -0
- package/dist/auth/oauth-provider.d.ts.map +1 -0
- package/dist/auth/oauth-provider.js +107 -0
- package/dist/auth/resource-metadata.d.ts +2 -0
- package/dist/auth/resource-metadata.d.ts.map +1 -0
- package/dist/auth/resource-metadata.js +3 -0
- package/dist/auth/token-verification.d.ts +11 -0
- package/dist/auth/token-verification.d.ts.map +1 -0
- package/dist/auth/token-verification.js +99 -0
- package/dist/auth/verification-cache.d.ts +8 -0
- package/dist/auth/verification-cache.d.ts.map +1 -0
- package/dist/auth/verification-cache.js +111 -0
- package/dist/chatgpt-analytics.d.ts +22 -0
- package/dist/chatgpt-analytics.d.ts.map +1 -0
- package/dist/chatgpt-analytics.js +172 -0
- package/dist/chatgpt-app.d.ts +15 -0
- package/dist/chatgpt-app.d.ts.map +1 -0
- package/dist/chatgpt-app.js +133 -0
- package/dist/chatgpt-release.d.ts +12 -0
- package/dist/chatgpt-release.d.ts.map +1 -0
- package/dist/chatgpt-release.js +30 -0
- package/dist/cli.js +30 -16
- package/dist/client-profile.d.ts +10 -0
- package/dist/client-profile.d.ts.map +1 -0
- package/dist/client-profile.js +45 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +52 -0
- package/dist/generation-status-html.d.ts +2 -0
- package/dist/generation-status-html.d.ts.map +1 -0
- package/dist/generation-status-html.js +26 -0
- package/dist/generation-status.d.ts +6 -0
- package/dist/generation-status.d.ts.map +1 -0
- package/dist/generation-status.js +37 -0
- package/dist/http-client.d.ts +45 -1
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +171 -34
- package/dist/http-server.d.ts +53 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +753 -0
- package/dist/http-session.d.ts +18 -0
- package/dist/http-session.d.ts.map +1 -0
- package/dist/http-session.js +189 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/oauth-response.d.ts +3 -0
- package/dist/oauth-response.d.ts.map +1 -0
- package/dist/oauth-response.js +30 -0
- package/dist/openapi/index.d.ts +2 -2
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +1 -1
- package/dist/openapi/parser.d.ts +2 -0
- package/dist/openapi/parser.d.ts.map +1 -1
- package/dist/openapi/parser.js +41 -1
- package/dist/openapi/types.d.ts +3 -0
- package/dist/openapi/types.d.ts.map +1 -1
- package/dist/organization-selection.d.ts +61 -0
- package/dist/organization-selection.d.ts.map +1 -0
- package/dist/organization-selection.js +142 -0
- package/dist/rate-limits.d.ts +13 -0
- package/dist/rate-limits.d.ts.map +1 -0
- package/dist/rate-limits.js +67 -0
- package/dist/revision.d.ts +6 -0
- package/dist/revision.d.ts.map +1 -0
- package/dist/revision.js +17 -0
- package/dist/runtime-diagnostics.d.ts +32 -0
- package/dist/runtime-diagnostics.d.ts.map +1 -0
- package/dist/runtime-diagnostics.js +58 -0
- package/dist/server.d.ts +23 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +893 -32
- package/dist/session-registry.d.ts +54 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +202 -0
- package/dist/session-telemetry.d.ts +13 -0
- package/dist/session-telemetry.d.ts.map +1 -0
- package/dist/session-telemetry.js +87 -0
- package/dist/spend-confirmation.d.ts +21 -0
- package/dist/spend-confirmation.d.ts.map +1 -0
- package/dist/spend-confirmation.js +105 -0
- package/dist/tool-analytics-context.d.ts +11 -0
- package/dist/tool-analytics-context.d.ts.map +1 -0
- package/dist/tool-analytics-context.js +8 -0
- package/dist/tool-names.d.ts +35 -0
- package/dist/tool-names.d.ts.map +1 -0
- package/dist/tool-names.js +133 -0
- package/dist/tools/account-context.d.ts +5 -0
- package/dist/tools/account-context.d.ts.map +1 -0
- package/dist/tools/account-context.js +131 -0
- package/dist/tools/account-management.d.ts +4 -0
- package/dist/tools/account-management.d.ts.map +1 -0
- package/dist/tools/account-management.js +111 -0
- package/dist/tools/app-tools.d.ts +31 -0
- package/dist/tools/app-tools.d.ts.map +1 -0
- package/dist/tools/app-tools.js +350 -0
- package/dist/tools/asset-analysis.d.ts +4 -0
- package/dist/tools/asset-analysis.d.ts.map +1 -0
- package/dist/tools/asset-analysis.js +172 -0
- package/dist/tools/asset-creators.d.ts +4 -0
- package/dist/tools/asset-creators.d.ts.map +1 -0
- package/dist/tools/asset-creators.js +38 -0
- package/dist/tools/audio-trim.d.ts +6 -0
- package/dist/tools/audio-trim.d.ts.map +1 -0
- package/dist/tools/audio-trim.js +79 -0
- package/dist/tools/batch-results.d.ts +16 -0
- package/dist/tools/batch-results.d.ts.map +1 -0
- package/dist/tools/batch-results.js +103 -0
- package/dist/tools/batches.d.ts +28 -0
- package/dist/tools/batches.d.ts.map +1 -0
- package/dist/tools/batches.js +293 -0
- package/dist/tools/dialogue.d.ts +6 -0
- package/dist/tools/dialogue.d.ts.map +1 -0
- package/dist/tools/dialogue.js +309 -0
- package/dist/tools/estimate-duration.d.ts +35 -0
- package/dist/tools/estimate-duration.d.ts.map +1 -0
- package/dist/tools/estimate-duration.js +66 -0
- package/dist/tools/feedback.d.ts +14 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +72 -0
- package/dist/tools/generated-adapters.d.ts +25 -0
- package/dist/tools/generated-adapters.d.ts.map +1 -0
- package/dist/tools/generated-adapters.js +143 -0
- package/dist/tools/generation-context.d.ts +14 -0
- package/dist/tools/generation-context.d.ts.map +1 -0
- package/dist/tools/generation-context.js +39 -0
- package/dist/tools/generation-continuation.d.ts +4 -0
- package/dist/tools/generation-continuation.d.ts.map +1 -0
- package/dist/tools/generation-continuation.js +124 -0
- package/dist/tools/generation-costs.d.ts +6 -0
- package/dist/tools/generation-costs.d.ts.map +1 -0
- package/dist/tools/generation-costs.js +67 -0
- package/dist/tools/generation-output.d.ts +3 -0
- package/dist/tools/generation-output.d.ts.map +1 -0
- package/dist/tools/generation-output.js +39 -0
- package/dist/tools/generator.d.ts +35 -2
- package/dist/tools/generator.d.ts.map +1 -1
- package/dist/tools/generator.js +127 -9
- package/dist/tools/job-status.d.ts +35 -0
- package/dist/tools/job-status.d.ts.map +1 -0
- package/dist/tools/job-status.js +92 -0
- package/dist/tools/job-wait.d.ts +41 -0
- package/dist/tools/job-wait.d.ts.map +1 -0
- package/dist/tools/job-wait.js +84 -0
- package/dist/tools/jobs.d.ts +3 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +210 -0
- package/dist/tools/media-upload.d.ts +27 -0
- package/dist/tools/media-upload.d.ts.map +1 -0
- package/dist/tools/media-upload.js +247 -0
- package/dist/tools/model-options.d.ts +36 -0
- package/dist/tools/model-options.d.ts.map +1 -0
- package/dist/tools/model-options.js +48 -0
- package/dist/tools/organization-choice.d.ts +9 -0
- package/dist/tools/organization-choice.d.ts.map +1 -0
- package/dist/tools/organization-choice.js +136 -0
- package/dist/tools/organizations.d.ts +56 -0
- package/dist/tools/organizations.d.ts.map +1 -0
- package/dist/tools/organizations.js +235 -0
- package/dist/tools/output-schemas.d.ts +54 -0
- package/dist/tools/output-schemas.d.ts.map +1 -0
- package/dist/tools/output-schemas.js +58 -0
- package/dist/tools/overrides.d.ts +3 -0
- package/dist/tools/overrides.d.ts.map +1 -1
- package/dist/tools/overrides.js +113 -15
- package/dist/tools/playback-metadata.d.ts +4 -0
- package/dist/tools/playback-metadata.d.ts.map +1 -0
- package/dist/tools/playback-metadata.js +51 -0
- package/dist/tools/project-destination.d.ts +13 -0
- package/dist/tools/project-destination.d.ts.map +1 -0
- package/dist/tools/project-destination.js +45 -0
- package/dist/tools/project-history.d.ts +6 -0
- package/dist/tools/project-history.d.ts.map +1 -0
- package/dist/tools/project-history.js +45 -0
- package/dist/tools/project-intent.d.ts +8 -0
- package/dist/tools/project-intent.d.ts.map +1 -0
- package/dist/tools/project-intent.js +87 -0
- package/dist/tools/running-submissions.d.ts +7 -0
- package/dist/tools/running-submissions.d.ts.map +1 -0
- package/dist/tools/running-submissions.js +91 -0
- package/dist/tools/speech.d.ts +6 -0
- package/dist/tools/speech.d.ts.map +1 -0
- package/dist/tools/speech.js +116 -0
- package/dist/tools/studio-draft.d.ts +7 -0
- package/dist/tools/studio-draft.d.ts.map +1 -0
- package/dist/tools/studio-draft.js +195 -0
- package/dist/tools/studio-view-contract.d.ts +184 -0
- package/dist/tools/studio-view-contract.d.ts.map +1 -0
- package/dist/tools/studio-view-contract.js +67 -0
- package/dist/tools/studio-view.d.ts +6 -0
- package/dist/tools/studio-view.d.ts.map +1 -0
- package/dist/tools/studio-view.js +126 -0
- package/dist/tools/timeline-lipsync.d.ts +316 -0
- package/dist/tools/timeline-lipsync.d.ts.map +1 -0
- package/dist/tools/timeline-lipsync.js +304 -0
- package/dist/tools/translation-preview.d.ts +8 -0
- package/dist/tools/translation-preview.d.ts.map +1 -0
- package/dist/tools/translation-preview.js +146 -0
- package/dist/tools/translation.d.ts +6 -0
- package/dist/tools/translation.d.ts.map +1 -0
- package/dist/tools/translation.js +131 -0
- package/dist/tools/upload-widget.d.ts +13 -0
- package/dist/tools/upload-widget.d.ts.map +1 -0
- package/dist/tools/upload-widget.js +788 -0
- package/dist/toolsets.d.ts +18 -0
- package/dist/toolsets.d.ts.map +1 -0
- package/dist/toolsets.js +157 -0
- package/dist/update-check.d.ts +38 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +150 -0
- package/dist/upload-runtime.d.ts +45 -0
- package/dist/upload-runtime.d.ts.map +1 -0
- package/dist/upload-runtime.js +156 -0
- package/dist/upload-source.d.ts +4 -0
- package/dist/upload-source.d.ts.map +1 -0
- package/dist/upload-source.js +46 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/package.json +77 -31
- package/dist/openapi/parser.test.d.ts +0 -2
- package/dist/openapi/parser.test.d.ts.map +0 -1
- package/dist/openapi/parser.test.js +0 -188
- package/dist/tools/generator.test.d.ts +0 -2
- package/dist/tools/generator.test.d.ts.map +0 -1
- package/dist/tools/generator.test.js +0 -178
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ClientProfile } from '../client-profile.js';
|
|
3
|
+
import type { HttpClient } from '../http-client.js';
|
|
4
|
+
import type { ParsedOperation } from '../openapi/types.js';
|
|
5
|
+
import type { McpToolDefinition } from './generator.js';
|
|
6
|
+
declare const keptInput: z.ZodObject<{
|
|
7
|
+
projectCreationKey: z.ZodOptional<z.ZodUUID>;
|
|
8
|
+
videoAssetId: z.ZodUUID;
|
|
9
|
+
model: z.ZodOptional<z.ZodString>;
|
|
10
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
11
|
+
sync_mode: z.ZodOptional<z.ZodEnum<{
|
|
12
|
+
bounce: "bounce";
|
|
13
|
+
cut_off: "cut_off";
|
|
14
|
+
loop: "loop";
|
|
15
|
+
remap: "remap";
|
|
16
|
+
silence: "silence";
|
|
17
|
+
}>>;
|
|
18
|
+
temperature: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
19
|
+
active_speaker_detection: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
20
|
+
auto_detect: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
use_v2: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
v3: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
+
face_image: z.ZodOptional<z.ZodString>;
|
|
24
|
+
frame_number: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
coordinates: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
26
|
+
}, z.core.$strict>]>>;
|
|
27
|
+
occlusion_detection_enabled: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
reasoning_enabled: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
model_mode: z.ZodOptional<z.ZodEnum<{
|
|
30
|
+
face: "face";
|
|
31
|
+
head: "head";
|
|
32
|
+
lips: "lips";
|
|
33
|
+
}>>;
|
|
34
|
+
}, z.core.$strict>>;
|
|
35
|
+
idempotencyKey: z.ZodOptional<z.ZodString>;
|
|
36
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
37
|
+
projectName: z.ZodOptional<z.ZodString>;
|
|
38
|
+
clips: z.ZodArray<z.ZodObject<{
|
|
39
|
+
audioAssetId: z.ZodOptional<z.ZodUUID>;
|
|
40
|
+
audioUrl: z.ZodOptional<z.ZodURL>;
|
|
41
|
+
startTime: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
endTime: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
startFrame: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
endFrame: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
audioStartTime: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
audioEndTime: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
syncMode: z.ZodOptional<z.ZodEnum<{
|
|
48
|
+
bounce: "bounce";
|
|
49
|
+
cut_off: "cut_off";
|
|
50
|
+
loop: "loop";
|
|
51
|
+
remap: "remap";
|
|
52
|
+
silence: "silence";
|
|
53
|
+
}>>;
|
|
54
|
+
speaker: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
55
|
+
frameNumber: z.ZodNumber;
|
|
56
|
+
x: z.ZodNumber;
|
|
57
|
+
y: z.ZodNumber;
|
|
58
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
59
|
+
faceTrackUrl: z.ZodURL;
|
|
60
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
61
|
+
detect: z.ZodLiteral<true>;
|
|
62
|
+
}, z.core.$strict>]>>;
|
|
63
|
+
lipsync: z.ZodOptional<z.ZodLiteral<false>>;
|
|
64
|
+
}, z.core.$strict>>;
|
|
65
|
+
outputAudio: z.ZodOptional<z.ZodObject<{
|
|
66
|
+
audioAssetId: z.ZodOptional<z.ZodUUID>;
|
|
67
|
+
audioUrl: z.ZodOptional<z.ZodURL>;
|
|
68
|
+
}, z.core.$strict>>;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
type TimelineInput = z.infer<typeof keptInput>;
|
|
71
|
+
/**
|
|
72
|
+
* Lipsyncs one video with several audio clips placed at given times, the shape
|
|
73
|
+
* of a Studio timeline. Each distinct audio asset becomes one input, and each
|
|
74
|
+
* clip a segment that reads its part of that audio.
|
|
75
|
+
*/
|
|
76
|
+
export declare function generationBody(args: TimelineInput, projectId: string): {
|
|
77
|
+
model: string;
|
|
78
|
+
options?: {
|
|
79
|
+
sync_mode?: "bounce" | "cut_off" | "loop" | "remap" | "silence" | undefined;
|
|
80
|
+
temperature?: number | null | undefined;
|
|
81
|
+
active_speaker_detection?: boolean | {
|
|
82
|
+
auto_detect?: boolean | undefined;
|
|
83
|
+
use_v2?: boolean | undefined;
|
|
84
|
+
v3?: boolean | undefined;
|
|
85
|
+
face_image?: string | undefined;
|
|
86
|
+
frame_number?: number | undefined;
|
|
87
|
+
coordinates?: [number, number] | undefined;
|
|
88
|
+
} | undefined;
|
|
89
|
+
occlusion_detection_enabled?: boolean | undefined;
|
|
90
|
+
reasoning_enabled?: boolean | undefined;
|
|
91
|
+
model_mode?: "face" | "head" | "lips" | undefined;
|
|
92
|
+
} | undefined;
|
|
93
|
+
input: ({
|
|
94
|
+
type: string;
|
|
95
|
+
assetId: string;
|
|
96
|
+
} | {
|
|
97
|
+
type: string;
|
|
98
|
+
assetId: string;
|
|
99
|
+
refId: string;
|
|
100
|
+
url?: undefined;
|
|
101
|
+
} | {
|
|
102
|
+
assetId?: undefined;
|
|
103
|
+
type: string;
|
|
104
|
+
url: string;
|
|
105
|
+
refId: string;
|
|
106
|
+
})[];
|
|
107
|
+
segments: ({
|
|
108
|
+
startTime: number | undefined;
|
|
109
|
+
endTime: number | undefined;
|
|
110
|
+
lipsync: boolean;
|
|
111
|
+
} | {
|
|
112
|
+
optionsOverride?: undefined;
|
|
113
|
+
startTime: number | undefined;
|
|
114
|
+
endTime: number | undefined;
|
|
115
|
+
audioInput: {
|
|
116
|
+
refId: string;
|
|
117
|
+
startTime?: number | undefined;
|
|
118
|
+
endTime?: number | undefined;
|
|
119
|
+
};
|
|
120
|
+
} | {
|
|
121
|
+
optionsOverride: {
|
|
122
|
+
sync_mode?: "bounce" | "cut_off" | "loop" | "remap" | "silence" | undefined;
|
|
123
|
+
active_speaker_detection?: {
|
|
124
|
+
auto_detect: boolean;
|
|
125
|
+
bounding_boxes_url?: undefined;
|
|
126
|
+
frame_number?: undefined;
|
|
127
|
+
coordinates?: undefined;
|
|
128
|
+
} | {
|
|
129
|
+
auto_detect: boolean;
|
|
130
|
+
bounding_boxes_url: string;
|
|
131
|
+
frame_number?: undefined;
|
|
132
|
+
coordinates?: undefined;
|
|
133
|
+
} | {
|
|
134
|
+
bounding_boxes_url?: undefined;
|
|
135
|
+
auto_detect: boolean;
|
|
136
|
+
frame_number: number;
|
|
137
|
+
coordinates: number[];
|
|
138
|
+
} | undefined;
|
|
139
|
+
};
|
|
140
|
+
startTime: number | undefined;
|
|
141
|
+
endTime: number | undefined;
|
|
142
|
+
audioInput: {
|
|
143
|
+
refId: string;
|
|
144
|
+
startTime?: number | undefined;
|
|
145
|
+
endTime?: number | undefined;
|
|
146
|
+
};
|
|
147
|
+
} | {
|
|
148
|
+
startFrame: number;
|
|
149
|
+
endFrame: number | undefined;
|
|
150
|
+
lipsync: boolean;
|
|
151
|
+
} | {
|
|
152
|
+
optionsOverride?: undefined;
|
|
153
|
+
startFrame: number;
|
|
154
|
+
endFrame: number | undefined;
|
|
155
|
+
audioInput: {
|
|
156
|
+
refId: string;
|
|
157
|
+
startTime?: number | undefined;
|
|
158
|
+
endTime?: number | undefined;
|
|
159
|
+
};
|
|
160
|
+
} | {
|
|
161
|
+
optionsOverride: {
|
|
162
|
+
sync_mode?: "bounce" | "cut_off" | "loop" | "remap" | "silence" | undefined;
|
|
163
|
+
active_speaker_detection?: {
|
|
164
|
+
auto_detect: boolean;
|
|
165
|
+
bounding_boxes_url?: undefined;
|
|
166
|
+
frame_number?: undefined;
|
|
167
|
+
coordinates?: undefined;
|
|
168
|
+
} | {
|
|
169
|
+
auto_detect: boolean;
|
|
170
|
+
bounding_boxes_url: string;
|
|
171
|
+
frame_number?: undefined;
|
|
172
|
+
coordinates?: undefined;
|
|
173
|
+
} | {
|
|
174
|
+
bounding_boxes_url?: undefined;
|
|
175
|
+
auto_detect: boolean;
|
|
176
|
+
frame_number: number;
|
|
177
|
+
coordinates: number[];
|
|
178
|
+
} | undefined;
|
|
179
|
+
};
|
|
180
|
+
startFrame: number;
|
|
181
|
+
endFrame: number | undefined;
|
|
182
|
+
audioInput: {
|
|
183
|
+
refId: string;
|
|
184
|
+
startTime?: number | undefined;
|
|
185
|
+
endTime?: number | undefined;
|
|
186
|
+
};
|
|
187
|
+
})[];
|
|
188
|
+
projectId: string;
|
|
189
|
+
outputAudio: {
|
|
190
|
+
refId: string;
|
|
191
|
+
};
|
|
192
|
+
} | {
|
|
193
|
+
model: string;
|
|
194
|
+
options?: {
|
|
195
|
+
sync_mode?: "bounce" | "cut_off" | "loop" | "remap" | "silence" | undefined;
|
|
196
|
+
temperature?: number | null | undefined;
|
|
197
|
+
active_speaker_detection?: boolean | {
|
|
198
|
+
auto_detect?: boolean | undefined;
|
|
199
|
+
use_v2?: boolean | undefined;
|
|
200
|
+
v3?: boolean | undefined;
|
|
201
|
+
face_image?: string | undefined;
|
|
202
|
+
frame_number?: number | undefined;
|
|
203
|
+
coordinates?: [number, number] | undefined;
|
|
204
|
+
} | undefined;
|
|
205
|
+
occlusion_detection_enabled?: boolean | undefined;
|
|
206
|
+
reasoning_enabled?: boolean | undefined;
|
|
207
|
+
model_mode?: "face" | "head" | "lips" | undefined;
|
|
208
|
+
} | undefined;
|
|
209
|
+
input: ({
|
|
210
|
+
type: string;
|
|
211
|
+
assetId: string;
|
|
212
|
+
} | {
|
|
213
|
+
type: string;
|
|
214
|
+
assetId: string;
|
|
215
|
+
refId: string;
|
|
216
|
+
url?: undefined;
|
|
217
|
+
} | {
|
|
218
|
+
assetId?: undefined;
|
|
219
|
+
type: string;
|
|
220
|
+
url: string;
|
|
221
|
+
refId: string;
|
|
222
|
+
})[];
|
|
223
|
+
segments: ({
|
|
224
|
+
startTime: number | undefined;
|
|
225
|
+
endTime: number | undefined;
|
|
226
|
+
lipsync: boolean;
|
|
227
|
+
} | {
|
|
228
|
+
optionsOverride?: undefined;
|
|
229
|
+
startTime: number | undefined;
|
|
230
|
+
endTime: number | undefined;
|
|
231
|
+
audioInput: {
|
|
232
|
+
refId: string;
|
|
233
|
+
startTime?: number | undefined;
|
|
234
|
+
endTime?: number | undefined;
|
|
235
|
+
};
|
|
236
|
+
} | {
|
|
237
|
+
optionsOverride: {
|
|
238
|
+
sync_mode?: "bounce" | "cut_off" | "loop" | "remap" | "silence" | undefined;
|
|
239
|
+
active_speaker_detection?: {
|
|
240
|
+
auto_detect: boolean;
|
|
241
|
+
bounding_boxes_url?: undefined;
|
|
242
|
+
frame_number?: undefined;
|
|
243
|
+
coordinates?: undefined;
|
|
244
|
+
} | {
|
|
245
|
+
auto_detect: boolean;
|
|
246
|
+
bounding_boxes_url: string;
|
|
247
|
+
frame_number?: undefined;
|
|
248
|
+
coordinates?: undefined;
|
|
249
|
+
} | {
|
|
250
|
+
bounding_boxes_url?: undefined;
|
|
251
|
+
auto_detect: boolean;
|
|
252
|
+
frame_number: number;
|
|
253
|
+
coordinates: number[];
|
|
254
|
+
} | undefined;
|
|
255
|
+
};
|
|
256
|
+
startTime: number | undefined;
|
|
257
|
+
endTime: number | undefined;
|
|
258
|
+
audioInput: {
|
|
259
|
+
refId: string;
|
|
260
|
+
startTime?: number | undefined;
|
|
261
|
+
endTime?: number | undefined;
|
|
262
|
+
};
|
|
263
|
+
} | {
|
|
264
|
+
startFrame: number;
|
|
265
|
+
endFrame: number | undefined;
|
|
266
|
+
lipsync: boolean;
|
|
267
|
+
} | {
|
|
268
|
+
optionsOverride?: undefined;
|
|
269
|
+
startFrame: number;
|
|
270
|
+
endFrame: number | undefined;
|
|
271
|
+
audioInput: {
|
|
272
|
+
refId: string;
|
|
273
|
+
startTime?: number | undefined;
|
|
274
|
+
endTime?: number | undefined;
|
|
275
|
+
};
|
|
276
|
+
} | {
|
|
277
|
+
optionsOverride: {
|
|
278
|
+
sync_mode?: "bounce" | "cut_off" | "loop" | "remap" | "silence" | undefined;
|
|
279
|
+
active_speaker_detection?: {
|
|
280
|
+
auto_detect: boolean;
|
|
281
|
+
bounding_boxes_url?: undefined;
|
|
282
|
+
frame_number?: undefined;
|
|
283
|
+
coordinates?: undefined;
|
|
284
|
+
} | {
|
|
285
|
+
auto_detect: boolean;
|
|
286
|
+
bounding_boxes_url: string;
|
|
287
|
+
frame_number?: undefined;
|
|
288
|
+
coordinates?: undefined;
|
|
289
|
+
} | {
|
|
290
|
+
bounding_boxes_url?: undefined;
|
|
291
|
+
auto_detect: boolean;
|
|
292
|
+
frame_number: number;
|
|
293
|
+
coordinates: number[];
|
|
294
|
+
} | undefined;
|
|
295
|
+
};
|
|
296
|
+
startFrame: number;
|
|
297
|
+
endFrame: number | undefined;
|
|
298
|
+
audioInput: {
|
|
299
|
+
refId: string;
|
|
300
|
+
startTime?: number | undefined;
|
|
301
|
+
endTime?: number | undefined;
|
|
302
|
+
};
|
|
303
|
+
})[];
|
|
304
|
+
projectId: string;
|
|
305
|
+
outputAudio?: undefined;
|
|
306
|
+
};
|
|
307
|
+
/** segments.maxItems on POST /v2/generate, when the API's spec publishes it. */
|
|
308
|
+
export declare function timelineClipLimit(operations: ParsedOperation[]): number | undefined;
|
|
309
|
+
/** Whether the API takes segments[].lipsync: false and a top-level outputAudio. */
|
|
310
|
+
export declare function timelineKeepsClips(operations: ParsedOperation[]): boolean;
|
|
311
|
+
export declare function createTimelineLipsyncTool(httpClient: HttpClient, getProfile?: () => ClientProfile, features?: {
|
|
312
|
+
maxClips?: number;
|
|
313
|
+
keptClips?: boolean;
|
|
314
|
+
}): McpToolDefinition;
|
|
315
|
+
export {};
|
|
316
|
+
//# sourceMappingURL=timeline-lipsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline-lipsync.d.ts","sourceRoot":"","sources":["../../src/tools/timeline-lipsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA2IxD,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBASb,CAAC;AAGH,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AA6F/C;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqCnB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBtD;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,MAAM,GAAG,SAAS,CAOnF;AAED,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAOzE;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,UAAU,EACtB,UAAU,GAAE,MAAM,aAA4C,EAE9D,QAAQ,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GACxD,iBAAiB,CAsDnB"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { resolveClientProfile } from '../client-profile.js';
|
|
3
|
+
import { schemaProperty } from '../openapi/parser.js';
|
|
4
|
+
import { modelOptionsSchema, parseModelOptions } from './model-options.js';
|
|
5
|
+
import { generationOutputSchema } from './output-schemas.js';
|
|
6
|
+
import { assertProjectDestination, createProjectId, newProjectFields, } from './project-destination.js';
|
|
7
|
+
const seconds = z.number().finite().nonnegative();
|
|
8
|
+
const frame = z.number().int().nonnegative();
|
|
9
|
+
// How Studio sets the face a clip lipsyncs: a point on it or a face track,
|
|
10
|
+
// both counted from the clip's own first frame, or automatic detection.
|
|
11
|
+
const speaker = z.union([
|
|
12
|
+
z
|
|
13
|
+
.object({
|
|
14
|
+
frameNumber: frame.describe('Frame of this clip, counted from its first frame, showing the face.'),
|
|
15
|
+
x: z
|
|
16
|
+
.number()
|
|
17
|
+
.finite()
|
|
18
|
+
.describe('Pixel x on the face in that frame of the video. A point off the face (even on the hair) rejects the whole generation, so aim at the face centre.'),
|
|
19
|
+
y: z.number().finite().describe('Pixel y on the face in that frame of the video.'),
|
|
20
|
+
})
|
|
21
|
+
.strict(),
|
|
22
|
+
z
|
|
23
|
+
.object({
|
|
24
|
+
faceTrackUrl: z
|
|
25
|
+
.url({ protocol: /^https$/ })
|
|
26
|
+
.describe('HTTPS URL of a JSON face track: one bounding box per frame of this clip.'),
|
|
27
|
+
})
|
|
28
|
+
.strict(),
|
|
29
|
+
z
|
|
30
|
+
.object({
|
|
31
|
+
detect: z.literal(true).describe('Detect the speaking face in this clip automatically.'),
|
|
32
|
+
})
|
|
33
|
+
.strict(),
|
|
34
|
+
]);
|
|
35
|
+
const clip = z
|
|
36
|
+
.object({
|
|
37
|
+
audioAssetId: z
|
|
38
|
+
.uuid()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Sync asset id of the audio clip. Give audioAssetId or audioUrl.'),
|
|
41
|
+
audioUrl: z
|
|
42
|
+
.url({ protocol: /^https$/ })
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('HTTPS URL of the audio instead of an asset, such as a completed translation preview’s outputMediaUrl.'),
|
|
45
|
+
startTime: seconds
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Second on the video where this clip starts. Give startTime and endTime, or startFrame and endFrame.'),
|
|
48
|
+
endTime: seconds.optional().describe('Second on the video where this clip ends.'),
|
|
49
|
+
startFrame: frame
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('First frame of the video this clip covers, used when its speaker is picked.'),
|
|
52
|
+
endFrame: frame.optional().describe('Last frame of the video this clip covers, inclusive.'),
|
|
53
|
+
audioStartTime: seconds
|
|
54
|
+
.optional()
|
|
55
|
+
.describe('Second into the audio file this clip plays from. Omit to start at 0.'),
|
|
56
|
+
audioEndTime: seconds
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Second into the audio file this clip plays to. Omit to play on from the start.'),
|
|
59
|
+
syncMode: modelOptionsSchema.shape.sync_mode.describe('How this clip fits when its audio and video lengths differ. Omit to use options.sync_mode.'),
|
|
60
|
+
speaker: speaker
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('The face this clip lipsyncs: a point on it in one of the clip’s frames, a face track, or automatic detection. Omit to use options.active_speaker_detection.'),
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
const input = z
|
|
66
|
+
.object({
|
|
67
|
+
videoAssetId: z.uuid().describe('Sync asset id of the video.'),
|
|
68
|
+
clips: z
|
|
69
|
+
.array(clip)
|
|
70
|
+
.min(1)
|
|
71
|
+
.max(200)
|
|
72
|
+
.describe('Audio clips placed on the video, in any order, without overlapping. Parts of the video no clip covers keep their original picture and sound.'),
|
|
73
|
+
model: z.string().trim().min(1).optional().describe('Model to use. Defaults to sync-3.'),
|
|
74
|
+
options: modelOptionsSchema.optional(),
|
|
75
|
+
idempotencyKey: z
|
|
76
|
+
.string()
|
|
77
|
+
.regex(/^[A-Za-z0-9._~-]{1,128}$/)
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('Unique key for this generation. Reuse it with identical arguments when retrying after a lost response. Passed as the Idempotency-Key header.'),
|
|
80
|
+
...newProjectFields,
|
|
81
|
+
projectId: z
|
|
82
|
+
.string()
|
|
83
|
+
.trim()
|
|
84
|
+
.min(1)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Existing Sync project id, only for a project the user explicitly selected or one just created for this request.'),
|
|
87
|
+
projectName: z
|
|
88
|
+
.string()
|
|
89
|
+
.trim()
|
|
90
|
+
.min(1)
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Name for a NEW project only. Never search or reuse a matching name. Cannot be combined with projectId.'),
|
|
93
|
+
})
|
|
94
|
+
.strict();
|
|
95
|
+
// Offered only when the API publishes segments[].lipsync and outputAudio.
|
|
96
|
+
const keptClip = clip.extend({
|
|
97
|
+
lipsync: z
|
|
98
|
+
.literal(false)
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('false keeps this part of the video exactly as it is: not lipsynced and not billed, while outputAudio still plays over it. Give no audio, syncMode or speaker. Use it for shots that should hear the dub without moving lips (listeners, people off camera or facing away).'),
|
|
101
|
+
});
|
|
102
|
+
const outputAudio = z
|
|
103
|
+
.object({
|
|
104
|
+
audioAssetId: z.uuid().optional().describe('Sync asset id of the audio.'),
|
|
105
|
+
audioUrl: z
|
|
106
|
+
.url({ protocol: /^https$/ })
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('HTTPS URL of the audio.'),
|
|
109
|
+
})
|
|
110
|
+
.strict()
|
|
111
|
+
.describe('One audio that plays over the whole finished output from its first second, replacing the sound of every part, such as the full dubbed track. Defaults, when a clip sets lipsync: false, to the only audio the clips use.');
|
|
112
|
+
const keptInput = input.extend({
|
|
113
|
+
clips: z
|
|
114
|
+
.array(keptClip)
|
|
115
|
+
.min(1)
|
|
116
|
+
.max(200)
|
|
117
|
+
.describe('Clips placed on the video, in any order, without overlapping. Parts no clip covers keep their original picture and sound; clips with lipsync: false keep their picture but play outputAudio.'),
|
|
118
|
+
outputAudio: outputAudio.optional(),
|
|
119
|
+
});
|
|
120
|
+
/** A clip's stretch of the video, in seconds or in frames (end inclusive). */
|
|
121
|
+
function spanOf({ startTime, endTime, startFrame, endFrame }, index) {
|
|
122
|
+
const given = [startTime, endTime, startFrame, endFrame].filter((v) => v !== undefined).length;
|
|
123
|
+
if (given === 2 && startTime !== undefined && endTime !== undefined)
|
|
124
|
+
return { unit: 'seconds', start: startTime, end: endTime, index };
|
|
125
|
+
if (given === 2 && startFrame !== undefined && endFrame !== undefined)
|
|
126
|
+
return { unit: 'frames', start: startFrame, end: endFrame + 1, index };
|
|
127
|
+
throw new Error(`clips[${index}]: give startTime and endTime, or startFrame and endFrame, not a mix.`);
|
|
128
|
+
}
|
|
129
|
+
function describeSpan(span) {
|
|
130
|
+
return span.unit === 'frames'
|
|
131
|
+
? `frames ${span.start}-${span.end - 1}`
|
|
132
|
+
: `seconds ${span.start}-${span.end}`;
|
|
133
|
+
}
|
|
134
|
+
/** Clips must have a positive span and may not share any part of the video. */
|
|
135
|
+
function assertPlaceable(clips, maxClips) {
|
|
136
|
+
if (maxClips !== undefined && clips.length > maxClips)
|
|
137
|
+
throw new Error(`A generation takes at most ${maxClips} clips; this request has ${clips.length}. Merge neighbouring clips of the same face, or split the video into parts.`);
|
|
138
|
+
const byUnit = { seconds: [], frames: [] };
|
|
139
|
+
if (clips.every((c) => c.lipsync === false))
|
|
140
|
+
throw new Error('Every clip sets lipsync: false, so there is nothing to lipsync.');
|
|
141
|
+
clips.forEach((c, index) => {
|
|
142
|
+
const kept = c.lipsync === false;
|
|
143
|
+
if (kept &&
|
|
144
|
+
[c.audioAssetId, c.audioUrl, c.audioStartTime, c.audioEndTime, c.syncMode, c.speaker].some((value) => value !== undefined))
|
|
145
|
+
throw new Error(`clips[${index}] sets lipsync: false, so it takes no audio, syncMode or speaker; outputAudio plays there.`);
|
|
146
|
+
if (!kept && (c.audioAssetId === undefined) === (c.audioUrl === undefined))
|
|
147
|
+
throw new Error(`clips[${index}]: give audioAssetId or audioUrl, exactly one.`);
|
|
148
|
+
const span = spanOf(c, index);
|
|
149
|
+
if (span.end <= span.start + (span.unit === 'frames' ? 1 : 0))
|
|
150
|
+
throw new Error(`clips[${index}] (${describeSpan(span)}) must end after it starts.`);
|
|
151
|
+
if (c.audioEndTime !== undefined && c.audioEndTime <= (c.audioStartTime ?? 0))
|
|
152
|
+
throw new Error(`clips[${index}]: audioEndTime must be after audioStartTime, which defaults to 0.`);
|
|
153
|
+
if (span.unit === 'frames' &&
|
|
154
|
+
c.speaker &&
|
|
155
|
+
'frameNumber' in c.speaker &&
|
|
156
|
+
c.speaker.frameNumber >= span.end - span.start)
|
|
157
|
+
throw new Error(`clips[${index}]: speaker.frameNumber ${c.speaker.frameNumber} is not one of its ${span.end - span.start} frames; it counts from the clip’s first frame (0), not the video’s.`);
|
|
158
|
+
byUnit[span.unit].push(span);
|
|
159
|
+
});
|
|
160
|
+
for (const spans of Object.values(byUnit)) {
|
|
161
|
+
const sorted = spans.sort((a, b) => a.start - b.start);
|
|
162
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
163
|
+
const [previous, current] = [sorted[i - 1], sorted[i]];
|
|
164
|
+
if (current.start < previous.end)
|
|
165
|
+
throw new Error(`clips[${previous.index}] (${describeSpan(previous)}) and clips[${current.index}] (${describeSpan(current)}) overlap; each part of the video takes one clip, so end one before the other starts.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Per-clip settings, in the shape the API's segment override takes. */
|
|
170
|
+
function clipOverride({ syncMode, speaker: face }) {
|
|
171
|
+
const detection = face === undefined
|
|
172
|
+
? undefined
|
|
173
|
+
: 'detect' in face
|
|
174
|
+
? { auto_detect: true }
|
|
175
|
+
: 'faceTrackUrl' in face
|
|
176
|
+
? { auto_detect: false, bounding_boxes_url: face.faceTrackUrl }
|
|
177
|
+
: { auto_detect: false, frame_number: face.frameNumber, coordinates: [face.x, face.y] };
|
|
178
|
+
if (syncMode === undefined && detection === undefined)
|
|
179
|
+
return {};
|
|
180
|
+
return {
|
|
181
|
+
optionsOverride: {
|
|
182
|
+
...(syncMode === undefined ? {} : { sync_mode: syncMode }),
|
|
183
|
+
...(detection === undefined ? {} : { active_speaker_detection: detection }),
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Lipsyncs one video with several audio clips placed at given times, the shape
|
|
189
|
+
* of a Studio timeline. Each distinct audio asset becomes one input, and each
|
|
190
|
+
* clip a segment that reads its part of that audio.
|
|
191
|
+
*/
|
|
192
|
+
export function generationBody(args, projectId) {
|
|
193
|
+
// One input per distinct audio, keyed by asset id or URL.
|
|
194
|
+
const audioKey = (c) => c.audioAssetId ? `asset:${c.audioAssetId}` : `url:${c.audioUrl}`;
|
|
195
|
+
const refs = new Map();
|
|
196
|
+
const audios = [
|
|
197
|
+
...args.clips.filter((placed) => placed.lipsync !== false),
|
|
198
|
+
...(args.outputAudio ? [args.outputAudio] : []),
|
|
199
|
+
];
|
|
200
|
+
for (const audio of audios)
|
|
201
|
+
if (!refs.has(audioKey(audio)))
|
|
202
|
+
refs.set(audioKey(audio), `audio-${refs.size + 1}`);
|
|
203
|
+
const model = args.model ?? 'sync-3';
|
|
204
|
+
const options = parseModelOptions(args.options, { model });
|
|
205
|
+
return {
|
|
206
|
+
model,
|
|
207
|
+
...(options === undefined ? {} : { options }),
|
|
208
|
+
input: [
|
|
209
|
+
{ type: 'video', assetId: args.videoAssetId },
|
|
210
|
+
...[...refs].map(([key, refId]) => key.startsWith('asset:')
|
|
211
|
+
? { type: 'audio', assetId: key.slice('asset:'.length), refId }
|
|
212
|
+
: { type: 'audio', url: key.slice('url:'.length), refId }),
|
|
213
|
+
],
|
|
214
|
+
segments: args.clips.map((c) => ({
|
|
215
|
+
...(c.startFrame === undefined
|
|
216
|
+
? { startTime: c.startTime, endTime: c.endTime }
|
|
217
|
+
: { startFrame: c.startFrame, endFrame: c.endFrame }),
|
|
218
|
+
...(c.lipsync === false ? { lipsync: false } : audioPart(c)),
|
|
219
|
+
})),
|
|
220
|
+
...outputAudioPart(),
|
|
221
|
+
projectId,
|
|
222
|
+
};
|
|
223
|
+
// Kept clips need one track over the whole output: outputAudio, or the only audio the clips
|
|
224
|
+
// use. Sent explicitly so the request says which track plays, not the API's default.
|
|
225
|
+
function outputAudioPart() {
|
|
226
|
+
if (args.outputAudio)
|
|
227
|
+
return { outputAudio: { refId: refs.get(audioKey(args.outputAudio)) } };
|
|
228
|
+
if (args.clips.some((placed) => placed.lipsync === false) && refs.size === 1)
|
|
229
|
+
return { outputAudio: { refId: [...refs.values()][0] } };
|
|
230
|
+
return {};
|
|
231
|
+
}
|
|
232
|
+
function audioPart(c) {
|
|
233
|
+
return {
|
|
234
|
+
audioInput: {
|
|
235
|
+
refId: refs.get(audioKey(c)),
|
|
236
|
+
...(c.audioStartTime === undefined ? {} : { startTime: c.audioStartTime }),
|
|
237
|
+
...(c.audioEndTime === undefined ? {} : { endTime: c.audioEndTime }),
|
|
238
|
+
},
|
|
239
|
+
...clipOverride(c),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** segments.maxItems on POST /v2/generate, when the API's spec publishes it. */
|
|
244
|
+
export function timelineClipLimit(operations) {
|
|
245
|
+
const generate = operations.find((operation) => operation.method === 'post' && operation.path === '/v2/generate');
|
|
246
|
+
const segments = generate?.requestBody && schemaProperty(generate.requestBody.schema, 'segments');
|
|
247
|
+
const limit = segments?.maxItems;
|
|
248
|
+
return typeof limit === 'number' && Number.isInteger(limit) && limit > 0 ? limit : undefined;
|
|
249
|
+
}
|
|
250
|
+
/** Whether the API takes segments[].lipsync: false and a top-level outputAudio. */
|
|
251
|
+
export function timelineKeepsClips(operations) {
|
|
252
|
+
const generate = operations.find((operation) => operation.method === 'post' && operation.path === '/v2/generate');
|
|
253
|
+
const body = generate?.requestBody?.schema;
|
|
254
|
+
const segment = schemaProperty(body, 'segments')?.items;
|
|
255
|
+
return !!schemaProperty(segment, 'lipsync') && !!schemaProperty(body, 'outputAudio');
|
|
256
|
+
}
|
|
257
|
+
export function createTimelineLipsyncTool(httpClient, getProfile = () => resolveClientProfile(),
|
|
258
|
+
// Read from the API's spec: its segment limit, and whether clips can be kept.
|
|
259
|
+
features = {}) {
|
|
260
|
+
const schema = features.keptClips ? keptInput : input;
|
|
261
|
+
return {
|
|
262
|
+
name: 'lipsync_create-timeline',
|
|
263
|
+
title: 'Create timeline lipsync',
|
|
264
|
+
description: 'Lipsync one video with several audio clips placed at given times, like a Studio timeline. Each clip names a Sync audio asset or audio URL, the seconds (or frames) of the video it covers, and optionally the part of the audio file it plays, its sync mode and the face it lipsyncs. Parts of the video no clip covers keep their original picture and sound, so leave a gap wherever the user wants no lipsync. For one audio across the whole video, use lipsync_create. Defaults to sync-3 and, unless projectId names a project the user picked, a new project; a keyed request without projectId needs a fresh projectCreationKey. Estimate and confirm before creating; this starts an asynchronous generation and returns its id. Poll it with generations_get and wait: true. Clips are checked before anything is created, and errors name the clip index.' +
|
|
265
|
+
(features.keptClips
|
|
266
|
+
? ' To dub a whole video while lipsyncing only some shots, pass the full dubbed track as outputAudio, lipsync the speaking shots with clips that play their part of it, and add clips with lipsync: false for the rest; the result plays the dub continuously with no cuts.'
|
|
267
|
+
: ''),
|
|
268
|
+
inputSchema: schema.shape,
|
|
269
|
+
outputSchema: generationOutputSchema,
|
|
270
|
+
annotations: {
|
|
271
|
+
readOnlyHint: false,
|
|
272
|
+
destructiveHint: true,
|
|
273
|
+
idempotentHint: false,
|
|
274
|
+
openWorldHint: true,
|
|
275
|
+
},
|
|
276
|
+
meta: {
|
|
277
|
+
'openai/toolInvocation/invoking': 'Creating your timeline lipsync…',
|
|
278
|
+
'openai/toolInvocation/invoked': 'Timeline lipsync started.',
|
|
279
|
+
},
|
|
280
|
+
handler: async (rawArgs, context) => {
|
|
281
|
+
const signal = context?.signal;
|
|
282
|
+
const args = schema.parse(rawArgs);
|
|
283
|
+
assertProjectDestination(args.projectId, args.projectName, args.projectCreationKey);
|
|
284
|
+
assertPlaceable(args.clips, features.maxClips);
|
|
285
|
+
if (args.outputAudio &&
|
|
286
|
+
(args.outputAudio.audioAssetId === undefined) === (args.outputAudio.audioUrl === undefined))
|
|
287
|
+
throw new Error('outputAudio: give audioAssetId or audioUrl, exactly one.');
|
|
288
|
+
// Like lipsync_create: a new project unless the user picked one.
|
|
289
|
+
const projectId = args.projectId ??
|
|
290
|
+
(await createProjectId(httpClient, {
|
|
291
|
+
name: args.projectName ?? (context?.clientProfile ?? getProfile()).defaultProjectName,
|
|
292
|
+
projectCreationKey: args.projectCreationKey,
|
|
293
|
+
idempotencyKey: args.idempotencyKey,
|
|
294
|
+
defaultWorkflow: 'lipsync',
|
|
295
|
+
}, signal));
|
|
296
|
+
signal?.throwIfAborted();
|
|
297
|
+
return httpClient.request('post', '/v2/generate', {
|
|
298
|
+
signal,
|
|
299
|
+
...(args.idempotencyKey ? { headers: { 'Idempotency-Key': args.idempotencyKey } } : {}),
|
|
300
|
+
body: generationBody(args, projectId),
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HttpClient } from '../http-client.js';
|
|
2
|
+
import type { ParsedOperation } from '../openapi/types.js';
|
|
3
|
+
import type { McpToolDefinition } from './generator.js';
|
|
4
|
+
export declare function translationPreviewOperations(operations: ParsedOperation[]): ParsedOperation[];
|
|
5
|
+
export declare function translationPreviewAvailable(operations: ParsedOperation[]): boolean;
|
|
6
|
+
/** The existing translation API must advertise saved-source authorization and keyed recovery. */
|
|
7
|
+
export declare function createTranslationPreviewTools(operations: ParsedOperation[], http: HttpClient): McpToolDefinition[];
|
|
8
|
+
//# sourceMappingURL=translation-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translation-preview.d.ts","sourceRoot":"","sources":["../../src/tools/translation-preview.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAUxD,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAM7F;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAkClF;AA+BD,iGAAiG;AACjG,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,eAAe,EAAE,EAC7B,IAAI,EAAE,UAAU,GACf,iBAAiB,EAAE,CAwFrB"}
|