@nodaro/shared 2.11.0 → 2.12.2
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 +67 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +66 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/video-analysis-catalog-sync.test.ts +6 -2
- package/src/__tests__/video-analysis-pricing.test.ts +8 -8
- package/src/__tests__/video-analysis-shot-craft.test.ts +38 -0
- package/src/i18n/framing.ar.ts +0 -1
- package/src/i18n/framing.de.ts +0 -1
- package/src/i18n/framing.es.ts +0 -1
- package/src/i18n/framing.fr.ts +0 -1
- package/src/i18n/framing.he.ts +0 -1
- package/src/i18n/framing.hi.ts +0 -1
- package/src/i18n/framing.ja.ts +0 -1
- package/src/i18n/framing.ko.ts +0 -1
- package/src/i18n/framing.pt-BR.ts +0 -1
- package/src/i18n/framing.ru.ts +0 -1
- package/src/i18n/framing.zh-CN.ts +0 -1
- package/src/model-catalog.ts +32 -32
- package/src/video-analysis-pricing.ts +36 -25
- package/src/video-analysis.ts +21 -0
package/package.json
CHANGED
|
@@ -157,8 +157,12 @@ describe("bare video-analysis node-type credit id", () => {
|
|
|
157
157
|
// 24 and the schedule regenerated ~47% lower at the ceiling bucket, and 300
|
|
158
158
|
// wrote 2064: the V1 hybrid-smart reprice (task A3) moved `smart` to a
|
|
159
159
|
// multi-roll plan (native skeleton + donor rolls, always refined) and
|
|
160
|
-
// trued up every tier's judge/refine terms)
|
|
161
|
-
|
|
160
|
+
// trued up every tier's judge/refine terms), 355 wrote 2068: the
|
|
161
|
+
// recast shot-craft Stage-1 doctrine (transitions + on-screen text, three
|
|
162
|
+
// required wire fields) grew the analyser's system prompt, moving the
|
|
163
|
+
// plugin's token pin 8_203 -> 8_482, and 356 wrote 2071: Stage 1.3 grew
|
|
164
|
+
// that prompt again, moving the pin 8_482 -> 8_706.
|
|
165
|
+
expect(ceiling).toBe(2071)
|
|
162
166
|
})
|
|
163
167
|
|
|
164
168
|
it("the bare id still bounds the default tier at the ceiling bucket", () => {
|
|
@@ -178,12 +178,12 @@ describe("video-audit-pricing", () => {
|
|
|
178
178
|
})
|
|
179
179
|
|
|
180
180
|
it("videoAuditCreditsForBucket: table lookup by resolved bucket + family, snapping a raw duration onto the ladder", () => {
|
|
181
|
-
expect(videoAuditCreditsForBucket(60, false)).toBe(
|
|
182
|
-
expect(videoAuditCreditsForBucket(60, true)).toBe(
|
|
183
|
-
expect(videoAuditCreditsForBucket(180, false)).toBe(
|
|
184
|
-
expect(videoAuditCreditsForBucket(360, false)).toBe(
|
|
185
|
-
expect(videoAuditCreditsForBucket(600, false)).toBe(
|
|
186
|
-
expect(videoAuditCreditsForBucket(600, true)).toBe(
|
|
181
|
+
expect(videoAuditCreditsForBucket(60, false)).toBe(214)
|
|
182
|
+
expect(videoAuditCreditsForBucket(60, true)).toBe(394)
|
|
183
|
+
expect(videoAuditCreditsForBucket(180, false)).toBe(290)
|
|
184
|
+
expect(videoAuditCreditsForBucket(360, false)).toBe(661)
|
|
185
|
+
expect(videoAuditCreditsForBucket(600, false)).toBe(1070)
|
|
186
|
+
expect(videoAuditCreditsForBucket(600, true)).toBe(1917)
|
|
187
187
|
// Not just exact ladder values — a raw duration snaps up to its bucket.
|
|
188
188
|
expect(videoAuditCreditsForBucket(70, false)).toBe(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:180s"])
|
|
189
189
|
expect(videoAuditCreditsForBucket(9999, true)).toBe(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:auto:600s"])
|
|
@@ -224,8 +224,8 @@ describe("video-audit-pricing", () => {
|
|
|
224
224
|
// 600s bucket here, same convention as VIDEO_ANALYSIS_BUCKET_CREDITS).
|
|
225
225
|
expect(Object.keys(VIDEO_AUDIT_BUCKET_CREDITS)).toHaveLength(8)
|
|
226
226
|
// Bare-id values quoted in the task/catalog must equal each family's 600s ceiling.
|
|
227
|
-
expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:600s"]).toBe(
|
|
228
|
-
expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:auto:600s"]).toBe(
|
|
227
|
+
expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:600s"]).toBe(1070)
|
|
228
|
+
expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:auto:600s"]).toBe(1917)
|
|
229
229
|
})
|
|
230
230
|
|
|
231
231
|
it("auto family = base family + the gemini-3-flash (legacy fast tier) row at the same bucket, exactly — single-source, never hand-added", () => {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
VIDEO_ANALYSIS_TEXT_KINDS, VIDEO_ANALYSIS_CLIP_TRANSITIONS_IN, VIDEO_ANALYSIS_TRANSITIONS,
|
|
4
|
+
windowAnalysisSchema, videoAnalysisResultSchema,
|
|
5
|
+
} from "../video-analysis.js"
|
|
6
|
+
|
|
7
|
+
const scene = { startSec: 0, endSec: 4, label: "hook", shotType: "Wide", camera: "static", visual: "{slot:a} waits", audio: [] }
|
|
8
|
+
const slot = { slotId: "a", label: "A", source: "wired-character", role: "person", description: "d" }
|
|
9
|
+
|
|
10
|
+
describe("shot-craft fields (keys only)", () => {
|
|
11
|
+
it("adds a text-kind vocabulary and a clip transition-in vocabulary without widening the transition enum", () => {
|
|
12
|
+
expect(VIDEO_ANALYSIS_TEXT_KINDS).toEqual(["title", "caption", "lower-third", "subtitle", "logo", "other"])
|
|
13
|
+
expect(VIDEO_ANALYSIS_CLIP_TRANSITIONS_IN).toEqual(["fade"])
|
|
14
|
+
expect(VIDEO_ANALYSIS_TRANSITIONS).toEqual(["cut", "fade", "dissolve", "wipe", "whip"])
|
|
15
|
+
})
|
|
16
|
+
it("window and result accept transitionIn at the CLIP level and onScreenTextKind on a scene", () => {
|
|
17
|
+
const w = windowAnalysisSchema.parse({ transitionIn: "fade", slots: [slot], scenes: [{ ...scene, onScreenText: "HELLO", onScreenTextKind: "title", transitionOut: "cut" }] })
|
|
18
|
+
expect(w.transitionIn).toBe("fade")
|
|
19
|
+
expect(w.scenes[0]!.onScreenTextKind).toBe("title")
|
|
20
|
+
const r = videoAnalysisResultSchema.parse({
|
|
21
|
+
meta: { durationSec: 4, width: 16, height: 9, aspectRatio: "16:9" }, transitionIn: "fade", slots: [slot],
|
|
22
|
+
scenes: [{ ...scene, sceneNumber: 1, visualResolved: "A waits", slotRefs: ["a"], onScreenTextKind: "subtitle", onScreenText: "hi" }],
|
|
23
|
+
})
|
|
24
|
+
expect(r.transitionIn).toBe("fade")
|
|
25
|
+
})
|
|
26
|
+
it("a pre-change result still parses, and an unknown key is stripped (readers never break)", () => {
|
|
27
|
+
const r = videoAnalysisResultSchema.parse({
|
|
28
|
+
meta: { durationSec: 4, width: 16, height: 9, aspectRatio: "16:9" }, slots: [slot],
|
|
29
|
+
scenes: [{ ...scene, sceneNumber: 1, visualResolved: "A waits", slotRefs: ["a"], futureKey: 1 }],
|
|
30
|
+
})
|
|
31
|
+
expect(r.transitionIn).toBeUndefined()
|
|
32
|
+
expect((r.scenes[0] as Record<string, unknown>).futureKey).toBeUndefined()
|
|
33
|
+
})
|
|
34
|
+
it("rejects a value outside the vocabularies", () => {
|
|
35
|
+
expect(() => windowAnalysisSchema.parse({ transitionIn: "wipe", slots: [], scenes: [] })).toThrow()
|
|
36
|
+
expect(() => windowAnalysisSchema.parse({ slots: [], scenes: [{ ...scene, onScreenTextKind: "banner" }] })).toThrow()
|
|
37
|
+
})
|
|
38
|
+
})
|
package/src/i18n/framing.ar.ts
CHANGED
|
@@ -18,7 +18,6 @@ const map: LocaleCatalogMap = {
|
|
|
18
18
|
"macro": { label: "ماكرو", description: "تفصيل قريب جدا لموضوع صغير" },
|
|
19
19
|
"full-shot": { label: "لقطة كاملة", description: "الجسم بالكامل من الرأس إلى القدم في الإطار" },
|
|
20
20
|
"cowboy-shot": { label: "لقطة Cowboy", description: "من منتصف الفخذ فأعلى، تأطير غربي كلاسيكي" },
|
|
21
|
-
"head-to-knees": { label: "من الرأس للركبتين", description: "من الرأس نزولا للركبتين" },
|
|
22
21
|
"head-to-hip": { label: "من الرأس للخصر", description: "من الرأس نزولا للوركين" },
|
|
23
22
|
"half-body": { label: "نصف جسم", description: "بورتريه نظيف من الخصر فأعلى" },
|
|
24
23
|
|
package/src/i18n/framing.de.ts
CHANGED
|
@@ -18,7 +18,6 @@ const map: LocaleCatalogMap = {
|
|
|
18
18
|
"macro": { label: "Makro", description: "Extremes Nahdetail eines kleinen Motivs" },
|
|
19
19
|
"full-shot": { label: "Ganzkörper-Aufnahme", description: "Ganzer Körper von Kopf bis Fuß im Bild" },
|
|
20
20
|
"cowboy-shot": { label: "Cowboy Shot", description: "Ab Mitte Oberschenkel, klassische Western-Einstellung" },
|
|
21
|
-
"head-to-knees": { label: "Kopf bis Knie", description: "Vom Kopf bis zu den Knien" },
|
|
22
21
|
"head-to-hip": { label: "Kopf bis Hüfte", description: "Vom Kopf bis zur Hüftlinie" },
|
|
23
22
|
"half-body": { label: "Halbporträt", description: "Sauberes Porträt ab der Hüfte aufwärts" },
|
|
24
23
|
"eye-level": { label: "Augenhöhe", description: "Kamera auf Augenhöhe des Motivs" },
|
package/src/i18n/framing.es.ts
CHANGED
|
@@ -18,7 +18,6 @@ const map: LocaleCatalogMap = {
|
|
|
18
18
|
"macro": { label: "Macro", description: "Detalle extremamente cercano de un sujeto pequeño" },
|
|
19
19
|
"full-shot": { label: "Plano Entero", description: "Cuerpo entero de cabeza a pies en el cuadro" },
|
|
20
20
|
"cowboy-shot": { label: "Plano Vaquero", description: "Desde mitad del muslo hacia arriba, encuadre clásico del Oeste" },
|
|
21
|
-
"head-to-knees": { label: "De Cabeza a Rodillas", description: "Desde la cabeza hasta las rodillas" },
|
|
22
21
|
"head-to-hip": { label: "De Cabeza a Cadera", description: "Desde la cabeza hasta las caderas" },
|
|
23
22
|
"half-body": { label: "Medio Cuerpo", description: "Retrato limpio de cintura para arriba" },
|
|
24
23
|
|
package/src/i18n/framing.fr.ts
CHANGED
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { description: "Détail extrêmement rapproché d'un petit sujet" },
|
|
20
20
|
"full-shot": { label: "Plan en pied", description: "Corps entier de la tête aux pieds dans le cadre" },
|
|
21
21
|
"cowboy-shot": { description: "Du milieu des cuisses, cadrage western classique" },
|
|
22
|
-
"head-to-knees": { label: "De la tête aux genoux", description: "De la tête jusqu'aux genoux" },
|
|
23
22
|
"head-to-hip": { label: "De la tête à la hanche", description: "De la tête jusqu'aux hanches" },
|
|
24
23
|
"half-body": { label: "Demi-corps", description: "Portrait propre à partir de la taille" },
|
|
25
24
|
// Angle
|
package/src/i18n/framing.he.ts
CHANGED
|
@@ -18,7 +18,6 @@ const map: LocaleCatalogMap = {
|
|
|
18
18
|
"macro": { label: "Macro", description: "פרט קרוב קיצוני של סובייקט קטן" },
|
|
19
19
|
"full-shot": { label: "Full Shot", description: "כל הגוף מראש עד רגליים בפריים" },
|
|
20
20
|
"cowboy-shot": { label: "Cowboy Shot", description: "מאמצע הירך ומעלה, מסגור Western קלאסי" },
|
|
21
|
-
"head-to-knees": { label: "ראש עד ברכיים", description: "מהראש למטה עד הברכיים" },
|
|
22
21
|
"head-to-hip": { label: "ראש עד מותן", description: "מהראש למטה עד המותניים" },
|
|
23
22
|
"half-body": { label: "חצי גוף", description: "פורטרט נקי מהמותניים ומעלה" },
|
|
24
23
|
|
package/src/i18n/framing.hi.ts
CHANGED
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { label: "Macro", description: "छोटे subject का अत्यधिक नज़दीकी detail" },
|
|
20
20
|
"full-shot": { label: "Full Shot", description: "Frame में सिर-से-पैर तक पूरा बदन" },
|
|
21
21
|
"cowboy-shot": { label: "Cowboy Shot", description: "जाँघ के बीच से ऊपर, क्लासिक Western framing" },
|
|
22
|
-
"head-to-knees": { label: "सिर से घुटनों तक", description: "सिर से लेकर घुटनों तक" },
|
|
23
22
|
"head-to-hip": { label: "सिर से कूल्हे तक", description: "सिर से लेकर कूल्हे तक" },
|
|
24
23
|
"half-body": { label: "आधा बदन", description: "साफ़ कमर-से-ऊपर portrait" },
|
|
25
24
|
|
package/src/i18n/framing.ja.ts
CHANGED
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { label: "マクロ", description: "小さな被写体の極端な拡大" },
|
|
20
20
|
"full-shot": { label: "フルショット", description: "頭からつま先まで全身がフレーム内に収まる" },
|
|
21
21
|
"cowboy-shot": { label: "カウボーイショット", description: "太もも中央から上、クラシックな西部劇のフレーミング" },
|
|
22
|
-
"head-to-knees": { label: "頭から膝まで", description: "頭から膝までを写す" },
|
|
23
22
|
"head-to-hip": { label: "頭から腰まで", description: "頭から腰までを写す" },
|
|
24
23
|
"half-body": { label: "ハーフボディ", description: "クリーンな腰上のポートレート" },
|
|
25
24
|
|
package/src/i18n/framing.ko.ts
CHANGED
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { label: "매크로", description: "작은 피사체의 극단적인 클로즈 디테일입니다" },
|
|
20
20
|
"full-shot": { label: "풀 샷", description: "머리부터 발끝까지 전신이 프레임에 들어갑니다" },
|
|
21
21
|
"cowboy-shot": { label: "카우보이 샷", description: "허벅지 중간부터 위로, 클래식한 서부극 프레이밍입니다" },
|
|
22
|
-
"head-to-knees": { label: "머리부터 무릎까지", description: "머리부터 무릎까지의 프레이밍입니다" },
|
|
23
22
|
"head-to-hip": { label: "머리부터 엉덩이까지", description: "머리부터 엉덩이까지의 프레이밍입니다" },
|
|
24
23
|
"half-body": { label: "반신", description: "허리 위의 깔끔한 인물 사진입니다" },
|
|
25
24
|
|
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { description: "Detalhe extremo de um pequeno sujeito" },
|
|
20
20
|
"full-shot": { label: "Plano Inteiro", description: "Corpo inteiro da cabeça aos pés no quadro" },
|
|
21
21
|
"cowboy-shot": { description: "Da metade da coxa para cima, enquadramento clássico de Western" },
|
|
22
|
-
"head-to-knees": { label: "Da Cabeça aos Joelhos", description: "Da cabeça até os joelhos" },
|
|
23
22
|
"head-to-hip": { label: "Da Cabeça ao Quadril", description: "Da cabeça até o quadril" },
|
|
24
23
|
"half-body": { label: "Meio Corpo", description: "Retrato limpo da cintura para cima" },
|
|
25
24
|
|
package/src/i18n/framing.ru.ts
CHANGED
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { label: "Макро", description: "Сверхблизкая деталь маленького объекта" },
|
|
20
20
|
"full-shot": { label: "Полный план", description: "Всё тело с головы до пят в кадре" },
|
|
21
21
|
"cowboy-shot": { label: "Ковбойский план", description: "От середины бедра, классическое вестерн-кадрирование" },
|
|
22
|
-
"head-to-knees": { label: "Голова до колен", description: "От головы до колен" },
|
|
23
22
|
"head-to-hip": { label: "Голова до бёдер", description: "От головы до бёдер" },
|
|
24
23
|
"half-body": { label: "Поясной портрет", description: "Чистый поясной портрет" },
|
|
25
24
|
|
|
@@ -19,7 +19,6 @@ const map: LocaleCatalogMap = {
|
|
|
19
19
|
"macro": { label: "微距", description: "对小型主体的极近距细节" },
|
|
20
20
|
"full-shot": { label: "全身镜头", description: "全身入画" },
|
|
21
21
|
"cowboy-shot": { label: "牛仔镜头", description: "大腿中部以上,经典西部片取景" },
|
|
22
|
-
"head-to-knees": { label: "头到膝盖", description: "从头部到膝盖" },
|
|
23
22
|
"head-to-hip": { label: "头到髋", description: "从头部到髋部" },
|
|
24
23
|
"half-body": { label: "半身像", description: "干净的腰部以上人像" },
|
|
25
24
|
|
package/src/model-catalog.ts
CHANGED
|
@@ -1971,11 +1971,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1971
1971
|
description: "Legacy fast-tier analysis model (pre-2026-07). Kept so stored raw-model configs keep running and keep pricing under their own identifier; new fast-tier runs use the current fast model.",
|
|
1972
1972
|
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1973
1973
|
pricing: [
|
|
1974
|
-
{ identifier: "video-analysis:gemini-3-flash", credits:
|
|
1974
|
+
{ identifier: "video-analysis:gemini-3-flash", credits: 847, note: "10-min ceiling (no duration given)" },
|
|
1975
1975
|
{ identifier: "video-analysis:gemini-3-flash:60s", credits: 180 },
|
|
1976
1976
|
{ identifier: "video-analysis:gemini-3-flash:180s", credits: 185 },
|
|
1977
|
-
{ identifier: "video-analysis:gemini-3-flash:360s", credits:
|
|
1978
|
-
{ identifier: "video-analysis:gemini-3-flash:600s", credits:
|
|
1977
|
+
{ identifier: "video-analysis:gemini-3-flash:360s", credits: 515 },
|
|
1978
|
+
{ identifier: "video-analysis:gemini-3-flash:600s", credits: 847, note: "10-min ceiling" },
|
|
1979
1979
|
],
|
|
1980
1980
|
},
|
|
1981
1981
|
"gemini-3.6-flash-video-analysis": {
|
|
@@ -1988,11 +1988,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1988
1988
|
description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast, economy tier. Billed per duration bucket.",
|
|
1989
1989
|
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1990
1990
|
pricing: [
|
|
1991
|
-
{ identifier: "video-analysis:gemini-3.6-flash", credits:
|
|
1992
|
-
{ identifier: "video-analysis:gemini-3.6-flash:60s", credits:
|
|
1991
|
+
{ identifier: "video-analysis:gemini-3.6-flash", credits: 990, note: "10-min ceiling (no duration given)" },
|
|
1992
|
+
{ identifier: "video-analysis:gemini-3.6-flash:60s", credits: 204 },
|
|
1993
1993
|
{ identifier: "video-analysis:gemini-3.6-flash:180s", credits: 218 },
|
|
1994
|
-
{ identifier: "video-analysis:gemini-3.6-flash:360s", credits:
|
|
1995
|
-
{ identifier: "video-analysis:gemini-3.6-flash:600s", credits:
|
|
1994
|
+
{ identifier: "video-analysis:gemini-3.6-flash:360s", credits: 600 },
|
|
1995
|
+
{ identifier: "video-analysis:gemini-3.6-flash:600s", credits: 990, note: "10-min ceiling" },
|
|
1996
1996
|
],
|
|
1997
1997
|
},
|
|
1998
1998
|
"gemini-3.1-pro-video-analysis": {
|
|
@@ -2005,11 +2005,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
2005
2005
|
description: "Analyze a video into a structured shot list (scenes, camera, audio) — higher-fidelity, default tier. Billed per duration bucket.",
|
|
2006
2006
|
useCases: ["video-analysis", "shot-list", "cinematic"],
|
|
2007
2007
|
pricing: [
|
|
2008
|
-
{ identifier: "video-analysis:gemini-3.1-pro", credits:
|
|
2009
|
-
{ identifier: "video-analysis:gemini-3.1-pro:60s", credits:
|
|
2010
|
-
{ identifier: "video-analysis:gemini-3.1-pro:180s", credits:
|
|
2011
|
-
{ identifier: "video-analysis:gemini-3.1-pro:360s", credits:
|
|
2012
|
-
{ identifier: "video-analysis:gemini-3.1-pro:600s", credits:
|
|
2008
|
+
{ identifier: "video-analysis:gemini-3.1-pro", credits: 1053, note: "10-min ceiling (no duration given)" },
|
|
2009
|
+
{ identifier: "video-analysis:gemini-3.1-pro:60s", credits: 216 },
|
|
2010
|
+
{ identifier: "video-analysis:gemini-3.1-pro:180s", credits: 232 },
|
|
2011
|
+
{ identifier: "video-analysis:gemini-3.1-pro:360s", credits: 639 },
|
|
2012
|
+
{ identifier: "video-analysis:gemini-3.1-pro:600s", credits: 1053, note: "10-min ceiling" },
|
|
2013
2013
|
],
|
|
2014
2014
|
},
|
|
2015
2015
|
// Both mixed tiers are variants of the same advanced multi-engine analysis
|
|
@@ -2025,11 +2025,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
2025
2025
|
description: "Our most advanced analysis tier — multiple analysis engines combined into one result for maximum completeness and accuracy. Billed per duration bucket.",
|
|
2026
2026
|
useCases: ["video-analysis", "shot-list", "premium", "most-complete"],
|
|
2027
2027
|
pricing: [
|
|
2028
|
-
{ identifier: "video-analysis:mixed", credits:
|
|
2029
|
-
{ identifier: "video-analysis:mixed:60s", credits:
|
|
2030
|
-
{ identifier: "video-analysis:mixed:180s", credits:
|
|
2031
|
-
{ identifier: "video-analysis:mixed:360s", credits:
|
|
2032
|
-
{ identifier: "video-analysis:mixed:600s", credits:
|
|
2028
|
+
{ identifier: "video-analysis:mixed", credits: 1174, note: "10-min ceiling (no duration given)" },
|
|
2029
|
+
{ identifier: "video-analysis:mixed:60s", credits: 269 },
|
|
2030
|
+
{ identifier: "video-analysis:mixed:180s", credits: 290 },
|
|
2031
|
+
{ identifier: "video-analysis:mixed:360s", credits: 727 },
|
|
2032
|
+
{ identifier: "video-analysis:mixed:600s", credits: 1174, note: "10-min ceiling" },
|
|
2033
2033
|
],
|
|
2034
2034
|
},
|
|
2035
2035
|
// SMART — the accuracy tier. Since the 2026-08-03 hybrid re-plan it is a
|
|
@@ -2047,11 +2047,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
2047
2047
|
description: "Highest-accuracy analysis — a hybrid pass that blends a native skeleton read with several donor analysis rolls, then always refines the merged result to find shot boundaries and identify the cast by appearance. Best choice when the shot list will drive regeneration. Billed per duration bucket.",
|
|
2048
2048
|
useCases: ["video-analysis", "shot-list", "premium", "most-accurate"],
|
|
2049
2049
|
pricing: [
|
|
2050
|
-
{ identifier: "video-analysis:smart", credits:
|
|
2051
|
-
{ identifier: "video-analysis:smart:60s", credits:
|
|
2052
|
-
{ identifier: "video-analysis:smart:180s", credits:
|
|
2053
|
-
{ identifier: "video-analysis:smart:360s", credits:
|
|
2054
|
-
{ identifier: "video-analysis:smart:600s", credits:
|
|
2050
|
+
{ identifier: "video-analysis:smart", credits: 2071, note: "10-min ceiling (no duration given)" },
|
|
2051
|
+
{ identifier: "video-analysis:smart:60s", credits: 412 },
|
|
2052
|
+
{ identifier: "video-analysis:smart:180s", credits: 502 },
|
|
2053
|
+
{ identifier: "video-analysis:smart:360s", credits: 1264 },
|
|
2054
|
+
{ identifier: "video-analysis:smart:600s", credits: 2071, note: "10-min ceiling" },
|
|
2055
2055
|
],
|
|
2056
2056
|
},
|
|
2057
2057
|
// ── AI Audit (`video-audit`) ── a SEPARATE node from video-analysis (own
|
|
@@ -2075,11 +2075,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
2075
2075
|
description: "Re-watches a clip against a wired analysis, applies video-verified corrections under guards, and returns a disclosed report of what changed. Billed per duration bucket.",
|
|
2076
2076
|
useCases: ["video-audit", "qa", "shot-list", "verification"],
|
|
2077
2077
|
pricing: [
|
|
2078
|
-
{ identifier: "video-audit", credits:
|
|
2079
|
-
{ identifier: "video-audit:60s", credits:
|
|
2080
|
-
{ identifier: "video-audit:180s", credits:
|
|
2081
|
-
{ identifier: "video-audit:360s", credits:
|
|
2082
|
-
{ identifier: "video-audit:600s", credits:
|
|
2078
|
+
{ identifier: "video-audit", credits: 1070, note: "10-min ceiling (no duration given)" },
|
|
2079
|
+
{ identifier: "video-audit:60s", credits: 214 },
|
|
2080
|
+
{ identifier: "video-audit:180s", credits: 290 },
|
|
2081
|
+
{ identifier: "video-audit:360s", credits: 661 },
|
|
2082
|
+
{ identifier: "video-audit:600s", credits: 1070, note: "10-min ceiling" },
|
|
2083
2083
|
],
|
|
2084
2084
|
},
|
|
2085
2085
|
"video-audit-auto": {
|
|
@@ -2091,11 +2091,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
2091
2091
|
description: "Same video-verified audit as AI Audit, but auto-runs a fast analysis first when none is wired in, then applies corrections under guards and returns a disclosed report. Billed per duration bucket.",
|
|
2092
2092
|
useCases: ["video-audit", "qa", "shot-list", "verification", "auto-analysis"],
|
|
2093
2093
|
pricing: [
|
|
2094
|
-
{ identifier: "video-audit:auto", credits:
|
|
2095
|
-
{ identifier: "video-audit:auto:60s", credits:
|
|
2096
|
-
{ identifier: "video-audit:auto:180s", credits:
|
|
2097
|
-
{ identifier: "video-audit:auto:360s", credits:
|
|
2098
|
-
{ identifier: "video-audit:auto:600s", credits:
|
|
2094
|
+
{ identifier: "video-audit:auto", credits: 1917, note: "10-min ceiling (no duration given)" },
|
|
2095
|
+
{ identifier: "video-audit:auto:60s", credits: 394 },
|
|
2096
|
+
{ identifier: "video-audit:auto:180s", credits: 475 },
|
|
2097
|
+
{ identifier: "video-audit:auto:360s", credits: 1176 },
|
|
2098
|
+
{ identifier: "video-audit:auto:600s", credits: 1917, note: "10-min ceiling" },
|
|
2099
2099
|
],
|
|
2100
2100
|
},
|
|
2101
2101
|
}
|
|
@@ -86,39 +86,50 @@ export const VIDEO_ANALYSIS_WINDOW = { LEN: WINDOW_LEN, STRIDE: WINDOW_STRIDE, O
|
|
|
86
86
|
//
|
|
87
87
|
// Values are pasted verbatim from the plugin generator's output — never hand
|
|
88
88
|
// computed. The plugin's cost test cross-checks every row, sentinels included.
|
|
89
|
+
// REGENERATED 2026-08-28 — recast shot-craft Stage 1: the analyser's system
|
|
90
|
+
// prompt grew (transition/on-screen-text doctrine + three required wire
|
|
91
|
+
// fields), so the plugin's system-prompt token pin moved 8_203 → 8_482 and
|
|
92
|
+
// 21 of 28 rows tick up by 1–4 credits (~0.1–0.3%). Output of the plugin's
|
|
93
|
+
// `scripts/gen-va-buckets.mjs` on `feat/va-shot-craft`, pasted verbatim.
|
|
94
|
+
//
|
|
95
|
+
// REGENERATED 2026-08-29 — recast shot-craft Stage 1.3: the analyser's system
|
|
96
|
+
// prompt grew again, moving the plugin's system-prompt token pin 8_482 → 8_706
|
|
97
|
+
// (cloud-plugins 0.209.0). 12 of 20 analysis rows and 6 of 8 audit rows tick up
|
|
98
|
+
// by 1–3 credits (~0.1–0.3%); the legacy `gemini-3-flash` family is unchanged.
|
|
99
|
+
// Output of the plugin's `scripts/gen-va-buckets.mjs`, pasted verbatim.
|
|
89
100
|
export const VIDEO_ANALYSIS_BUCKET_CREDITS: Record<string, number> = {
|
|
90
101
|
// Legacy fast-tier model (pre-2026-07) — kept for stored raw-id configs.
|
|
91
102
|
"video-analysis:gemini-3-flash:60s": 180,
|
|
92
103
|
"video-analysis:gemini-3-flash:180s": 185,
|
|
93
|
-
"video-analysis:gemini-3-flash:360s":
|
|
94
|
-
"video-analysis:gemini-3-flash:600s":
|
|
104
|
+
"video-analysis:gemini-3-flash:360s": 515,
|
|
105
|
+
"video-analysis:gemini-3-flash:600s": 847,
|
|
95
106
|
// Current fast tier — regenerated from the private formula for its backing
|
|
96
107
|
// model; higher than the legacy fast schedule but still ≤ pro per bucket.
|
|
97
|
-
"video-analysis:gemini-3.6-flash:60s":
|
|
108
|
+
"video-analysis:gemini-3.6-flash:60s": 204,
|
|
98
109
|
"video-analysis:gemini-3.6-flash:180s": 218,
|
|
99
|
-
"video-analysis:gemini-3.6-flash:360s":
|
|
100
|
-
"video-analysis:gemini-3.6-flash:600s":
|
|
101
|
-
"video-analysis:gemini-3.1-pro:60s":
|
|
102
|
-
"video-analysis:gemini-3.1-pro:180s":
|
|
103
|
-
"video-analysis:gemini-3.1-pro:360s":
|
|
104
|
-
"video-analysis:gemini-3.1-pro:600s":
|
|
110
|
+
"video-analysis:gemini-3.6-flash:360s": 600,
|
|
111
|
+
"video-analysis:gemini-3.6-flash:600s": 990,
|
|
112
|
+
"video-analysis:gemini-3.1-pro:60s": 216,
|
|
113
|
+
"video-analysis:gemini-3.1-pro:180s": 232,
|
|
114
|
+
"video-analysis:gemini-3.1-pro:360s": 639,
|
|
115
|
+
"video-analysis:gemini-3.1-pro:600s": 1053,
|
|
105
116
|
// Mixed tiers (`mixed` + `mixed-fast`) share ONE credit family — they are
|
|
106
117
|
// variants of the same engine plan (plan internals live in the private
|
|
107
118
|
// analysis plugin). Admin-tunable via model_pricing like every other row.
|
|
108
|
-
"video-analysis:mixed:60s":
|
|
109
|
-
"video-analysis:mixed:180s":
|
|
110
|
-
"video-analysis:mixed:360s":
|
|
111
|
-
"video-analysis:mixed:600s":
|
|
119
|
+
"video-analysis:mixed:60s": 269,
|
|
120
|
+
"video-analysis:mixed:180s": 290,
|
|
121
|
+
"video-analysis:mixed:360s": 727,
|
|
122
|
+
"video-analysis:mixed:600s": 1174,
|
|
112
123
|
// SMART — the accuracy tier, and since the 2026-08-03 re-plan a multi-roll
|
|
113
124
|
// plan like the others, always refined (`selectionMode` does not apply
|
|
114
125
|
// here — smart always refines; it never offers a cheaper "choose" path).
|
|
115
126
|
// Priced above the economy tiers because it genuinely costs more to run;
|
|
116
127
|
// the only tier whose accuracy is validated against a hand-counted edit
|
|
117
128
|
// list, re-validated at the current plan before this schedule shipped.
|
|
118
|
-
"video-analysis:smart:60s":
|
|
119
|
-
"video-analysis:smart:180s":
|
|
120
|
-
"video-analysis:smart:360s":
|
|
121
|
-
"video-analysis:smart:600s":
|
|
129
|
+
"video-analysis:smart:60s": 412,
|
|
130
|
+
"video-analysis:smart:180s": 502,
|
|
131
|
+
"video-analysis:smart:360s": 1264,
|
|
132
|
+
"video-analysis:smart:600s": 2071,
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
/**
|
|
@@ -188,14 +199,14 @@ export function videoAnalysisNumWindows(bucketSec: number): number {
|
|
|
188
199
|
* never hand computed. The plugin's cost test cross-checks every row.
|
|
189
200
|
*/
|
|
190
201
|
export const VIDEO_AUDIT_BUCKET_CREDITS: Record<string, number> = {
|
|
191
|
-
"video-audit:60s":
|
|
192
|
-
"video-audit:180s":
|
|
193
|
-
"video-audit:360s":
|
|
194
|
-
"video-audit:600s":
|
|
195
|
-
"video-audit:auto:60s":
|
|
196
|
-
"video-audit:auto:180s":
|
|
197
|
-
"video-audit:auto:360s":
|
|
198
|
-
"video-audit:auto:600s":
|
|
202
|
+
"video-audit:60s": 214,
|
|
203
|
+
"video-audit:180s": 290,
|
|
204
|
+
"video-audit:360s": 661,
|
|
205
|
+
"video-audit:600s": 1070,
|
|
206
|
+
"video-audit:auto:60s": 394,
|
|
207
|
+
"video-audit:auto:180s": 475,
|
|
208
|
+
"video-audit:auto:360s": 1176,
|
|
209
|
+
"video-audit:auto:600s": 1917,
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
/**
|
package/src/video-analysis.ts
CHANGED
|
@@ -88,6 +88,21 @@ export type VideoAnalysisVisualEffect = (typeof VIDEO_ANALYSIS_VISUAL_EFFECTS)[n
|
|
|
88
88
|
export const VIDEO_ANALYSIS_TRANSITIONS = ["cut", "fade", "dissolve", "wipe", "whip"] as const
|
|
89
89
|
export type VideoAnalysisTransition = (typeof VIDEO_ANALYSIS_TRANSITIONS)[number]
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* What burned-in text IS (2026-08-28, recast shot craft). Decides render-vs-omit
|
|
93
|
+
* downstream: a `subtitle` is a transcription of the piece's own speech — a
|
|
94
|
+
* recreation re-speaks it and must not render it — while a `title`, `caption`,
|
|
95
|
+
* `lower-third` or `logo` is picture content. Absent when a scene carries no
|
|
96
|
+
* text. A new KEY on the scene: strip-mode readers drop it harmlessly.
|
|
97
|
+
*/
|
|
98
|
+
export const VIDEO_ANALYSIS_TEXT_KINDS = ["title", "caption", "lower-third", "subtitle", "logo", "other"] as const
|
|
99
|
+
export type VideoAnalysisTextKind = (typeof VIDEO_ANALYSIS_TEXT_KINDS)[number]
|
|
100
|
+
|
|
101
|
+
/** How the CLIP opens (clip-level, not per scene — a window's first scene is
|
|
102
|
+
* not the clip's). Absent ⇒ a hard open. Only `fade` today. */
|
|
103
|
+
export const VIDEO_ANALYSIS_CLIP_TRANSITIONS_IN = ["fade"] as const
|
|
104
|
+
export type VideoAnalysisClipTransitionIn = (typeof VIDEO_ANALYSIS_CLIP_TRANSITIONS_IN)[number]
|
|
105
|
+
|
|
91
106
|
/**
|
|
92
107
|
* Time manipulation — slow motion, ramps, timelapse, freeze, reverse.
|
|
93
108
|
*
|
|
@@ -302,6 +317,8 @@ const windowSceneBase = z.object({
|
|
|
302
317
|
* earlier scene's speech means the shot is replayed footage under a voice-over.
|
|
303
318
|
*/
|
|
304
319
|
onScreenText: z.string().optional(),
|
|
320
|
+
/** What the on-screen text IS. Meaningful only when `onScreenText` is non-empty. */
|
|
321
|
+
onScreenTextKind: z.enum(VIDEO_ANALYSIS_TEXT_KINDS).optional(),
|
|
305
322
|
/** Effects on this shot's PICTURE. Absent ⇒ a clean image. */
|
|
306
323
|
effects: z.array(z.enum(VIDEO_ANALYSIS_VISUAL_EFFECTS)).optional(),
|
|
307
324
|
transitionOut: z.enum(VIDEO_ANALYSIS_TRANSITIONS).optional(),
|
|
@@ -323,6 +340,8 @@ export const windowAnalysisSchema = z.object({
|
|
|
323
340
|
/** The clip-level look as read from THIS window. Merge folds the windows
|
|
324
341
|
* field-by-field (first non-empty wins), like `language`. */
|
|
325
342
|
look: clipLookSchema.optional(),
|
|
343
|
+
/** The clip opens with a fade-in. Folded from window 0 only. */
|
|
344
|
+
transitionIn: z.enum(VIDEO_ANALYSIS_CLIP_TRANSITIONS_IN).optional(),
|
|
326
345
|
slots: z.array(entitySlotSchema),
|
|
327
346
|
scenes: z.array(windowSceneSchema),
|
|
328
347
|
})
|
|
@@ -349,6 +368,8 @@ export const videoAnalysisResultSchema = z.object({
|
|
|
349
368
|
* rather than a member: `meta` is probed fact (ffprobe dimensions, probed
|
|
350
369
|
* duration), while this is the model's reading of the photography. */
|
|
351
370
|
look: clipLookSchema.optional(),
|
|
371
|
+
/** The clip opens with a fade-in. Folded from window 0 only. */
|
|
372
|
+
transitionIn: z.enum(VIDEO_ANALYSIS_CLIP_TRANSITIONS_IN).optional(),
|
|
352
373
|
slots: z.array(entitySlotSchema),
|
|
353
374
|
scenes: z.array(analyzedSceneSchema).min(1),
|
|
354
375
|
/** CAST VARIATIONS (§4 cap handling): looks the analyzer's merge FOLDED into
|