@nodaro/shared 1.13.0 → 1.14.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/dist/index.cjs +61 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +54 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/gvp-supported-providers.test.ts +31 -0
- package/src/__tests__/video-analysis.test.ts +83 -0
- package/src/i18n/styling.ar.ts +7 -0
- package/src/i18n/styling.de.ts +7 -0
- package/src/i18n/styling.es.ts +7 -0
- package/src/i18n/styling.fr.ts +7 -0
- package/src/i18n/styling.he.ts +7 -0
- package/src/i18n/styling.hi.ts +7 -0
- package/src/i18n/styling.ja.ts +7 -0
- package/src/i18n/styling.ko.ts +7 -0
- package/src/i18n/styling.pt-BR.ts +7 -0
- package/src/i18n/styling.ru.ts +7 -0
- package/src/i18n/styling.zh-CN.ts +7 -0
- package/src/index.ts +2 -0
- package/src/model-constants.ts +16 -0
- package/src/social-post.ts +6 -1
- package/src/video-analysis.ts +79 -0
package/src/i18n/styling.ru.ts
CHANGED
|
@@ -27,6 +27,7 @@ const map: LocaleCatalogMap = {
|
|
|
27
27
|
"headwear-bandana": { label: "Бандана", description: "Бандана, повязанная вокруг головы" },
|
|
28
28
|
"headwear-hood": { label: "Капюшон", description: "Капюшон худи поднят" },
|
|
29
29
|
"headwear-crown": { label: "Корона", description: "Королевская или декоративная корона" },
|
|
30
|
+
"headwear-nemes": { label: "Немес", description: "Полосатый фараонский немес с уреем" },
|
|
30
31
|
"headwear-helmet": { label: "Шлем", description: "Защитный шлем" },
|
|
31
32
|
"headwear-veil": { label: "Вуаль", description: "Вуаль, наброшенная на лицо/волосы" },
|
|
32
33
|
|
|
@@ -124,6 +125,7 @@ const map: LocaleCatalogMap = {
|
|
|
124
125
|
"face-paint-dramatic": { label: "Драматичная роспись лица", description: "Смелая художественная роспись лица" },
|
|
125
126
|
"face-paint-costume": { label: "Костюмная краска", description: "Косплейная / театральная краска" },
|
|
126
127
|
"face-paint-tribal": { label: "Племенные знаки", description: "Племенные знаки на лице" },
|
|
128
|
+
"face-paint-flag": { label: "Флаг на лице", description: "Национальный флаг на щеках, стиль болельщика" },
|
|
127
129
|
"face-paint-warpaint": { label: "Боевая раскраска", description: "Полосы боевой раскраски воина" },
|
|
128
130
|
|
|
129
131
|
// -------------------- Fabric --------------------
|
|
@@ -150,12 +152,14 @@ const map: LocaleCatalogMap = {
|
|
|
150
152
|
"outfit-evening-gown": { label: "Вечернее платье", description: "Парадное платье в пол" },
|
|
151
153
|
"outfit-wedding-dress": { label: "Свадебное платье", description: "Белое свадебное платье" },
|
|
152
154
|
"outfit-cocktail-dress": { label: "Коктейльное платье", description: "Коктейльное платье до колен" },
|
|
155
|
+
"outfit-sundress": { label: "Сарафан / макси", description: "Струящийся сарафан с горловиной холтер / макси с принтом" },
|
|
153
156
|
"outfit-lab-coat": { label: "Лабораторный халат", description: "Белый медицинский лабораторный халат" },
|
|
154
157
|
"outfit-scrubs": { label: "Медицинская роба", description: "Однотонная медицинская роба" },
|
|
155
158
|
"outfit-military": { label: "Военная форма", description: "Оливково-зелёная военная форма" },
|
|
156
159
|
"outfit-lifeguard": { label: "Форма спасателя", description: "Красные топ и шорты спасателя" },
|
|
157
160
|
"outfit-athleisure": { label: "Athleisure", description: "Подходящие топ и низ athleisure" },
|
|
158
161
|
"outfit-tracksuit": { label: "Спортивный костюм", description: "Двойной спортивный костюм с молнией спереди" },
|
|
162
|
+
"outfit-soccer-jersey": { label: "Футбольная форма", description: "Футболка национальной сборной с гербом" },
|
|
159
163
|
"outfit-bikini": { label: "Бикини", description: "Раздельный купальник" },
|
|
160
164
|
"outfit-one-piece-swim": { label: "Слитный купальник", description: "Облегающий слитный купальник" },
|
|
161
165
|
"outfit-lingerie": { label: "Комплект белья", description: "Подходящий комплект нижнего белья" },
|
|
@@ -260,6 +264,8 @@ const map: LocaleCatalogMap = {
|
|
|
260
264
|
"state-tucked-in": { label: "Заправленная", description: "Топ заправлен в низ" },
|
|
261
265
|
"state-half-tucked": { label: "Полузаправленная", description: "Перед заправлен, зад свободен" },
|
|
262
266
|
"state-off-shoulder": { label: "С открытым плечом", description: "Соскользнула с одного плеча" },
|
|
267
|
+
"state-halter-neck": { label: "Холтер", description: "Бретели завязаны за шеей" },
|
|
268
|
+
"state-plunging-neck": { label: "Глубокое декольте", description: "Глубокий V-образный вырез" },
|
|
263
269
|
"state-unbuttoned": { label: "Расстёгнутая", description: "Открытая / расстёгнутая" },
|
|
264
270
|
"state-rolled-sleeves": { label: "Закатанные рукава", description: "Рукава закатаны до предплечья" },
|
|
265
271
|
"state-layered": { label: "Многослойная", description: "Множество наложенных слоёв" },
|
|
@@ -274,6 +280,7 @@ const map: LocaleCatalogMap = {
|
|
|
274
280
|
"outfit-pirate": { label: "Пират", description: "Пиратский костюм: треуголка, кушак, рубашка с жабо, сапоги" },
|
|
275
281
|
"outfit-fairy": { label: "Фея", description: "Фэнтезийная фея: газовые крылья, цветочный венок, эфирное платье" },
|
|
276
282
|
"outfit-mermaid": { label: "Русалка", description: "Фэнтезийная русалка: чешуйчатый хвост/юбка, топ-ракушка" },
|
|
283
|
+
"outfit-pharaoh": { label: "Одеяние фараона", description: "Древнеегипетская знать: ожерелье усех, пектораль, плиссированная схенти" },
|
|
277
284
|
"headwear-bucket-hat": { label: "Панама", description: "Мягкая повседневная панама с опущенными полями" },
|
|
278
285
|
"headwear-beret": { label: "Берет", description: "Мягкий круглый французский берет" },
|
|
279
286
|
"headwear-turban": { label: "Тюрбан", description: "Намотанный головной тюрбан, культурный / модный" },
|
|
@@ -27,6 +27,7 @@ const map: LocaleCatalogMap = {
|
|
|
27
27
|
"headwear-bandana": { label: "头巾", description: "绑在头上的方巾" },
|
|
28
28
|
"headwear-hood": { label: "帽兜", description: "拉起的连帽衫帽兜" },
|
|
29
29
|
"headwear-crown": { label: "皇冠", description: "皇冠或装饰冠" },
|
|
30
|
+
"headwear-nemes": { label: "奈迈斯头巾", description: "带圣蛇的条纹法老头巾" },
|
|
30
31
|
"headwear-helmet": { label: "头盔", description: "防护头盔" },
|
|
31
32
|
"headwear-veil": { label: "面纱", description: "覆盖脸 / 头发的面纱" },
|
|
32
33
|
"headwear-bucket-hat": { label: "渔夫帽", description: "软帽檐的休闲渔夫帽" },
|
|
@@ -136,6 +137,7 @@ const map: LocaleCatalogMap = {
|
|
|
136
137
|
"face-paint-dramatic": { label: "戏剧脸绘", description: "大胆艺术化的脸部彩绘" },
|
|
137
138
|
"face-paint-costume": { label: "角色彩妆", description: "Cosplay / 戏剧妆" },
|
|
138
139
|
"face-paint-tribal": { label: "部族纹绘", description: "部族风格的脸部纹绘" },
|
|
140
|
+
"face-paint-flag": { label: "国旗脸彩", description: "画在脸颊上的国旗,球迷风格" },
|
|
139
141
|
"face-paint-warpaint": { label: "战士涂彩", description: "勇士战斗般的涂彩条纹" },
|
|
140
142
|
|
|
141
143
|
// -------------------- Fabric --------------------
|
|
@@ -162,12 +164,14 @@ const map: LocaleCatalogMap = {
|
|
|
162
164
|
"outfit-evening-gown": { label: "晚礼服", description: "落地长款正式礼服" },
|
|
163
165
|
"outfit-wedding-dress": { label: "婚纱", description: "白色婚纱礼服" },
|
|
164
166
|
"outfit-cocktail-dress":{ label: "鸡尾酒裙", description: "及膝鸡尾酒裙" },
|
|
167
|
+
"outfit-sundress": { label: "太阳裙/长裙", description: "飘逸的挂脖太阳裙/印花长裙" },
|
|
165
168
|
"outfit-lab-coat": { label: "实验室白大褂", description: "白色医用大褂" },
|
|
166
169
|
"outfit-scrubs": { label: "医用刷手服", description: "纯色医用刷手服" },
|
|
167
170
|
"outfit-military": { label: "军装", description: "橄榄绿军用作训服" },
|
|
168
171
|
"outfit-lifeguard": { label: "救生员服", description: "红色救生员上衣加短裤" },
|
|
169
172
|
"outfit-athleisure": { label: "运动休闲套装", description: "上下成套的运动休闲" },
|
|
170
173
|
"outfit-tracksuit": { label: "运动套装", description: "前拉链两件套运动套装" },
|
|
174
|
+
"outfit-soccer-jersey": { label: "足球球衣", description: "带队徽的国家队球衣" },
|
|
171
175
|
"outfit-bikini": { label: "比基尼", description: "两件式泳装" },
|
|
172
176
|
"outfit-one-piece-swim":{ label: "连体泳衣", description: "贴身连体泳衣" },
|
|
173
177
|
"outfit-lingerie": { label: "内衣套装", description: "成套内衣" },
|
|
@@ -187,6 +191,7 @@ const map: LocaleCatalogMap = {
|
|
|
187
191
|
"outfit-pirate": { label: "海盗装", description: "海盗服:三角帽、腰带、荷叶边衬衫、长靴" },
|
|
188
192
|
"outfit-fairy": { label: "仙女装", description: "奇幻仙女:轻纱翅膀、花环、空灵长裙" },
|
|
189
193
|
"outfit-mermaid": { label: "美人鱼装", description: "奇幻美人鱼:鱼鳞鱼尾 / 半裙、贝壳上衣" },
|
|
194
|
+
"outfit-pharaoh": { label: "法老盛装", description: "古埃及王室:乌塞克项圈、胸饰、百褶围裙" },
|
|
190
195
|
|
|
191
196
|
// -------------------- Top --------------------
|
|
192
197
|
"top-tshirt": { label: "T 恤", description: "纯色圆领 T 恤" },
|
|
@@ -299,6 +304,8 @@ const map: LocaleCatalogMap = {
|
|
|
299
304
|
"state-tucked-in": { label: "塞进腰间", description: "上衣塞进下装" },
|
|
300
305
|
"state-half-tucked": { label: "半塞", description: "前面塞进、后面散开" },
|
|
301
306
|
"state-off-shoulder": { label: "一字肩", description: "滑落一边肩膀" },
|
|
307
|
+
"state-halter-neck": { label: "挂脖式", description: "肩带系于颈后" },
|
|
308
|
+
"state-plunging-neck": { label: "深V领口", description: "深邃的V形领口" },
|
|
302
309
|
"state-unbuttoned": { label: "敞开", description: "敞开 / 不扣扣子" },
|
|
303
310
|
"state-rolled-sleeves":{ label: "卷起袖子", description: "袖子卷至前臂" },
|
|
304
311
|
"state-layered": { label: "叠搭", description: "多层叠搭" },
|
package/src/index.ts
CHANGED
|
@@ -106,6 +106,8 @@ export {
|
|
|
106
106
|
VIDEO_GEN_COLLAPSED_T2V_IDS,
|
|
107
107
|
resolveVideoProviderForMode,
|
|
108
108
|
isSeedance2Provider,
|
|
109
|
+
GVP_SUPPORTED_PROVIDERS,
|
|
110
|
+
isGvpSupportedProvider,
|
|
109
111
|
defaultVideoAspectRatio,
|
|
110
112
|
CHARACTER_MOTION_PROVIDERS,
|
|
111
113
|
LOCATION_ATMOSPHERE_PROVIDERS,
|
package/src/model-constants.ts
CHANGED
|
@@ -1124,6 +1124,22 @@ export function isSeedance2Provider(provider: string | undefined): boolean {
|
|
|
1124
1124
|
return !!provider && SEEDANCE_2_PROVIDERS.has(provider)
|
|
1125
1125
|
}
|
|
1126
1126
|
|
|
1127
|
+
/**
|
|
1128
|
+
* Generate/Edit Video Pro SUPPORT subset — the only SKUs the pro multi-segment
|
|
1129
|
+
* engine currently offers (mini withdrawn from selection, 2026-07-21).
|
|
1130
|
+
* Deliberately distinct from SEEDANCE_2_PROVIDERS: the family set gates
|
|
1131
|
+
* CAPABILITIES (ref limits, i2v params, adaptive aspect) and must keep every
|
|
1132
|
+
* variant; this list gates which SKUs the pro nodes offer in selection. The
|
|
1133
|
+
* pro plugin routes stay tolerant of the full family so previously-saved
|
|
1134
|
+
* workflows keep running — the editor fail-safe snaps stale selections to a
|
|
1135
|
+
* supported SKU instead.
|
|
1136
|
+
*/
|
|
1137
|
+
export const GVP_SUPPORTED_PROVIDERS = ["seedance-2", "seedance-2-fast"] as const
|
|
1138
|
+
|
|
1139
|
+
export function isGvpSupportedProvider(provider: string | undefined): boolean {
|
|
1140
|
+
return !!provider && (GVP_SUPPORTED_PROVIDERS as readonly string[]).includes(provider)
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1127
1143
|
/**
|
|
1128
1144
|
* Default aspect ratio for a video provider when the node carries no explicit
|
|
1129
1145
|
* `aspectRatio`. Seedance 2.x defaults to `"adaptive"` (output matches the
|
package/src/social-post.ts
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* on Instagram's carousel item limits.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
/** Node types that publish to a social platform via POST /v1/social/publish.
|
|
9
|
+
/** Node types that publish to a social platform via POST /v1/social/publish.
|
|
10
|
+
* The 7 per-platform nodes hardcode their platform via `node.type`;
|
|
11
|
+
* `publish-social` is the UNIFIED node whose platform is derived from the
|
|
12
|
+
* chosen connection (`data.platform`) instead. All shared-set-driven routing
|
|
13
|
+
* (carousel accumulation, caption, refMap gate) covers it automatically. */
|
|
10
14
|
export const SOCIAL_POST_NODE_TYPES = new Set([
|
|
11
15
|
"instagram-post",
|
|
12
16
|
"tiktok-post",
|
|
@@ -15,6 +19,7 @@ export const SOCIAL_POST_NODE_TYPES = new Set([
|
|
|
15
19
|
"x-post",
|
|
16
20
|
"facebook-post",
|
|
17
21
|
"telegram-post",
|
|
22
|
+
"publish-social",
|
|
18
23
|
])
|
|
19
24
|
|
|
20
25
|
/** Instagram carousel limits per Meta Graph API.
|
package/src/video-analysis.ts
CHANGED
|
@@ -25,12 +25,46 @@ export type VideoAnalysisEntitySource = (typeof VIDEO_ANALYSIS_ENTITY_SOURCES)[n
|
|
|
25
25
|
/** Matches {slot:<id>} tokens. Distinct from NODE_REF_PATTERN / {image:N} grammars. */
|
|
26
26
|
export const SLOT_TOKEN_RE = /\{slot:([a-z0-9-]+)\}/g
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Appearance variations (cast-variations spec, 2026-07-24): a slot's canonical
|
|
30
|
+
* `description` IS its default look; `variations` enumerates NON-default looks
|
|
31
|
+
* only (dream vs reality, flashback, disguise…). `"default"` is a reserved
|
|
32
|
+
* variationId — used in scene bindings / ledger keys / routing for unbound
|
|
33
|
+
* scenes, never inside `variations[]`. The closed slug vocabulary is
|
|
34
|
+
* doctrine-enforced (the schema pins only the id charset, like `role`);
|
|
35
|
+
* `alt-1`/`alt-2` are the escape hatches.
|
|
36
|
+
*/
|
|
37
|
+
export const VIDEO_ANALYSIS_VARIATION_SLUGS = ["dream", "flashback", "disguise", "costume", "transformation", "era", "alt-1", "alt-2"] as const
|
|
38
|
+
/** Max NON-default looks per slot. The window layer REJECTS past the cap (schema-forced retry); the cross-window merge FOLDS at the cap. */
|
|
39
|
+
export const VIDEO_ANALYSIS_MAX_VARIATIONS = 4
|
|
40
|
+
export const VIDEO_ANALYSIS_DEFAULT_VARIATION = "default"
|
|
41
|
+
|
|
42
|
+
export const slotVariationSchema = z.object({
|
|
43
|
+
variationId: z.string().min(1).regex(/^[a-z0-9-]+$/)
|
|
44
|
+
.refine((id) => id !== VIDEO_ANALYSIS_DEFAULT_VARIATION, { message: `"${VIDEO_ANALYSIS_DEFAULT_VARIATION}" is reserved for the slot's canonical look` }),
|
|
45
|
+
label: z.string().min(1),
|
|
46
|
+
/** Full STANDALONE casting-sheet look restating the slot's invariant identity
|
|
47
|
+
* core (face, build, age) — the manifest substitutes it wholesale, so a
|
|
48
|
+
* wardrobe-only delta would silently delete identity from the prompt. */
|
|
49
|
+
description: z.string().min(1),
|
|
50
|
+
/** Per-variation identity reference (auto-cast frame or user sheet). In the
|
|
51
|
+
* schema from day one: every strip-mode round-trip must carry it. */
|
|
52
|
+
refImageUrl: z.string().url().optional(),
|
|
53
|
+
})
|
|
54
|
+
export type SlotVariation = z.infer<typeof slotVariationSchema>
|
|
55
|
+
|
|
28
56
|
export const entitySlotSchema = z.object({
|
|
29
57
|
slotId: z.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
30
58
|
label: z.string().min(1),
|
|
31
59
|
source: z.enum(VIDEO_ANALYSIS_ENTITY_SOURCES),
|
|
32
60
|
role: z.string().min(1),
|
|
33
61
|
description: z.string().min(1),
|
|
62
|
+
/** Auto-cast visual reference — a hosted frame from the analyzed footage
|
|
63
|
+
* where this entity is clearly visible. Optional/additive: producers may
|
|
64
|
+
* omit it; consumers use it as an identity reference for recreation. */
|
|
65
|
+
refImageUrl: z.string().url().optional(),
|
|
66
|
+
/** NON-default looks only; present only when at least one exists. */
|
|
67
|
+
variations: z.array(slotVariationSchema).max(VIDEO_ANALYSIS_MAX_VARIATIONS).optional(),
|
|
34
68
|
})
|
|
35
69
|
export type EntitySlot = z.infer<typeof entitySlotSchema>
|
|
36
70
|
|
|
@@ -57,6 +91,11 @@ const windowSceneBase = z.object({
|
|
|
57
91
|
transitionOut: z.enum(["cut", "fade", "wipe", "whip"]).optional(),
|
|
58
92
|
// Array of concurrent layers (music + speech + sfx together); [] = silence.
|
|
59
93
|
audio: z.array(audioLayerSchema),
|
|
94
|
+
/** slotId → variationId for slots wearing a NON-default look in this scene
|
|
95
|
+
* (only slots referenced in the scene; absent key ⇒ the default look).
|
|
96
|
+
* Rides windowSceneBase so BOTH the window layer and analyzedSceneSchema
|
|
97
|
+
* inherit it — the out-of-band binding channel (no in-text markers, D6). */
|
|
98
|
+
slotVariations: z.record(z.string(), z.string()).optional(),
|
|
60
99
|
})
|
|
61
100
|
// .strip() (default) drops model-emitted oversized/slotRefs — validator-computed only.
|
|
62
101
|
const windowSceneSchema = windowSceneBase.refine((s) => s.endSec > s.startSec, { message: "endSec must be > startSec" })
|
|
@@ -113,6 +152,46 @@ export function unwrapUnresolvedTokens(text: string, validIds: Set<string>): { t
|
|
|
113
152
|
return { text: out, unresolved }
|
|
114
153
|
}
|
|
115
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Rewrite a scene's `slotVariations` after cross-window slot/variation
|
|
157
|
+
* unification: slot keys map through `slotRenames`, then each variation value
|
|
158
|
+
* maps through `variationRenames[<new slotId>]`. Absent renames pass through.
|
|
159
|
+
*/
|
|
160
|
+
export function rewriteSceneBindings(
|
|
161
|
+
sv: Record<string, string> | undefined,
|
|
162
|
+
slotRenames: Record<string, string>,
|
|
163
|
+
variationRenames?: Record<string, Record<string, string>>,
|
|
164
|
+
): Record<string, string> | undefined {
|
|
165
|
+
if (!sv) return undefined
|
|
166
|
+
const out: Record<string, string> = {}
|
|
167
|
+
for (const [slotId, variationId] of Object.entries(sv)) {
|
|
168
|
+
const newSlot = slotRenames[slotId] ?? slotId
|
|
169
|
+
out[newSlot] = variationRenames?.[newSlot]?.[variationId] ?? variationId
|
|
170
|
+
}
|
|
171
|
+
return out
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Drop bindings whose (slotId, variationId) no longer exists after a merge —
|
|
176
|
+
* the unwrap-rule mirror: never persist a dangling binding, and report what
|
|
177
|
+
* was dropped so the caller can warn. `"default"` is always valid for a known
|
|
178
|
+
* slot. `kept` is undefined when nothing survives (no `{}` materialization).
|
|
179
|
+
*/
|
|
180
|
+
export function dropUnknownBindings(
|
|
181
|
+
sv: Record<string, string> | undefined,
|
|
182
|
+
validBySlot: Map<string, Set<string>>,
|
|
183
|
+
): { kept?: Record<string, string>; dropped: Array<{ slotId: string; variationId: string }> } {
|
|
184
|
+
if (!sv) return { dropped: [] }
|
|
185
|
+
const kept: Record<string, string> = {}
|
|
186
|
+
const dropped: Array<{ slotId: string; variationId: string }> = []
|
|
187
|
+
for (const [slotId, variationId] of Object.entries(sv)) {
|
|
188
|
+
const valid = validBySlot.get(slotId)
|
|
189
|
+
if (valid && (variationId === VIDEO_ANALYSIS_DEFAULT_VARIATION || valid.has(variationId))) kept[slotId] = variationId
|
|
190
|
+
else dropped.push({ slotId, variationId })
|
|
191
|
+
}
|
|
192
|
+
return { kept: Object.keys(kept).length > 0 ? kept : undefined, dropped }
|
|
193
|
+
}
|
|
194
|
+
|
|
116
195
|
/** Substitute {slot:x}: castMap binding wins, else the slot's description, else literal id. */
|
|
117
196
|
export function renderAnalyzedScene(scene: { visual: string }, slots: EntitySlot[], castMap?: Record<string, string>): string {
|
|
118
197
|
const byId = new Map(slots.map((s) => [s.slotId, s]))
|