@nodaro/prompts 1.48.0 → 1.50.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/{catalog-packs-BDsrv8St.d.cts → catalog-packs-DeMcSVCR.d.cts} +1 -1
- package/dist/{catalog-packs-CAK42voB.d.ts → catalog-packs-DjqvicAR.d.ts} +1 -1
- package/dist/{chunk-RRHU6MD5.js → chunk-34JK2G34.js} +4 -4
- package/dist/{chunk-RRHU6MD5.js.map → chunk-34JK2G34.js.map} +1 -1
- package/dist/{chunk-LD47QOKS.js → chunk-3XSB5YDY.js} +92 -53
- package/dist/chunk-3XSB5YDY.js.map +1 -0
- package/dist/{chunk-XZXUU3YH.js → chunk-5XPELUES.js} +10 -5
- package/dist/chunk-5XPELUES.js.map +1 -0
- package/dist/{chunk-XBRW75LM.js → chunk-BWDQEAX2.js} +4 -4
- package/dist/{chunk-XBRW75LM.js.map → chunk-BWDQEAX2.js.map} +1 -1
- package/dist/{chunk-JHRVNWBS.js → chunk-C5LP7DAQ.js} +6 -6
- package/dist/{chunk-JHRVNWBS.js.map → chunk-C5LP7DAQ.js.map} +1 -1
- package/dist/{chunk-BTV2I7EN.js → chunk-DB6A5I6V.js} +3 -3
- package/dist/{chunk-BTV2I7EN.js.map → chunk-DB6A5I6V.js.map} +1 -1
- package/dist/{chunk-GZVCBSFG.js → chunk-FEOVUTFC.js} +10 -7
- package/dist/{chunk-GZVCBSFG.js.map → chunk-FEOVUTFC.js.map} +1 -1
- package/dist/{chunk-UNR34GYU.cjs → chunk-FFPFBNXH.cjs} +10 -5
- package/dist/chunk-FFPFBNXH.cjs.map +1 -0
- package/dist/chunk-GZ5SL2BI.js +30 -0
- package/dist/chunk-GZ5SL2BI.js.map +1 -0
- package/dist/{chunk-IMQB7GIK.cjs → chunk-HR4TFBU2.cjs} +35 -35
- package/dist/{chunk-IMQB7GIK.cjs.map → chunk-HR4TFBU2.cjs.map} +1 -1
- package/dist/{chunk-Z755PH3K.cjs → chunk-IADAATZK.cjs} +49 -49
- package/dist/{chunk-Z755PH3K.cjs.map → chunk-IADAATZK.cjs.map} +1 -1
- package/dist/{chunk-CMEUYFBZ.js → chunk-IXBTD2LW.js} +4 -4
- package/dist/{chunk-CMEUYFBZ.js.map → chunk-IXBTD2LW.js.map} +1 -1
- package/dist/{chunk-JV2WSMAA.cjs → chunk-JGEBO6L6.cjs} +8 -8
- package/dist/{chunk-JV2WSMAA.cjs.map → chunk-JGEBO6L6.cjs.map} +1 -1
- package/dist/{chunk-KNAKOSKV.cjs → chunk-KYKCHOOS.cjs} +13 -13
- package/dist/{chunk-KNAKOSKV.cjs.map → chunk-KYKCHOOS.cjs.map} +1 -1
- package/dist/{chunk-XGNUOU4F.cjs → chunk-MJIKM4YZ.cjs} +130 -127
- package/dist/{chunk-XGNUOU4F.cjs.map → chunk-MJIKM4YZ.cjs.map} +1 -1
- package/dist/{chunk-MGK6SFTV.cjs → chunk-RVBO63D5.cjs} +4 -4
- package/dist/{chunk-MGK6SFTV.cjs.map → chunk-RVBO63D5.cjs.map} +1 -1
- package/dist/{chunk-PJFX3MFI.cjs → chunk-TYJCCODP.cjs} +18 -18
- package/dist/{chunk-PJFX3MFI.cjs.map → chunk-TYJCCODP.cjs.map} +1 -1
- package/dist/{chunk-YXJSPQC6.js → chunk-VJYKE6AY.js} +3 -3
- package/dist/{chunk-YXJSPQC6.js.map → chunk-VJYKE6AY.js.map} +1 -1
- package/dist/{chunk-MYVJVOPB.cjs → chunk-VYCTRGJ7.cjs} +39 -39
- package/dist/{chunk-MYVJVOPB.cjs.map → chunk-VYCTRGJ7.cjs.map} +1 -1
- package/dist/{chunk-54BYDWAM.cjs → chunk-WLN2MJ7F.cjs} +102 -55
- package/dist/chunk-WLN2MJ7F.cjs.map +1 -0
- package/dist/chunk-XBY53RF5.cjs +33 -0
- package/dist/chunk-XBY53RF5.cjs.map +1 -0
- package/dist/{chunk-36XMRVY4.js → chunk-ZZ6UOK2P.js} +4 -4
- package/dist/{chunk-36XMRVY4.js.map → chunk-ZZ6UOK2P.js.map} +1 -1
- package/dist/entries/assembly.cjs +29 -29
- package/dist/entries/assembly.js +6 -6
- package/dist/entries/catalog-packs.d.cts +4 -4
- package/dist/entries/catalog-packs.d.ts +4 -4
- package/dist/entries/person.d.cts +3 -3
- package/dist/entries/person.d.ts +3 -3
- package/dist/entries/picker-art.cjs +28 -20
- package/dist/entries/picker-art.d.cts +18 -2
- package/dist/entries/picker-art.d.ts +18 -2
- package/dist/entries/picker-art.js +2 -2
- package/dist/entries/picker-catalogs.cjs +57 -57
- package/dist/entries/picker-catalogs.d.cts +5 -3
- package/dist/entries/picker-catalogs.d.ts +5 -3
- package/dist/entries/picker-catalogs.js +5 -5
- package/dist/entries/pickers.cjs +407 -375
- package/dist/entries/pickers.d.cts +59 -3
- package/dist/entries/pickers.d.ts +59 -3
- package/dist/entries/pickers.js +3 -3
- package/dist/entries/presets.cjs +35 -35
- package/dist/entries/presets.js +1 -1
- package/dist/entries/prompt-builder.cjs +76 -76
- package/dist/entries/prompt-builder.js +5 -5
- package/dist/{images-DrbQB9Yh.d.cts → images-ChAOLY5V.d.cts} +5 -4
- package/dist/{images-DrbQB9Yh.d.ts → images-ChAOLY5V.d.ts} +5 -4
- package/dist/index.cjs +621 -581
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +11 -11
- package/dist/{picker-catalogs-BiRCs_5G.d.cts → picker-catalogs-DzLCu6qQ.d.cts} +10 -1
- package/dist/{picker-catalogs-CX8pHi8q.d.ts → picker-catalogs-SDSWrufc.d.ts} +10 -1
- package/package.json +2 -2
- package/src/__tests__/fixtures/root-exports.baseline.json +5 -0
- package/src/__tests__/picker-art-images.test.ts +39 -0
- package/src/__tests__/style-display-name.test.ts +153 -0
- package/src/__tests__/style-media.test.ts +141 -0
- package/src/__tests__/style-video-wording.test.ts +22 -7
- package/src/factory-presets/generate-image.ts +1 -1
- package/src/factory-presets/generate-video.ts +1 -1
- package/src/picker-analyzer-registry.ts +9 -2
- package/src/picker-art/images.ts +6 -5
- package/src/picker-art/index.ts +1 -0
- package/src/picker-art/look-preview-sets.ts +5 -0
- package/src/picker-art/restyle-previews.ts +47 -0
- package/src/picker-catalogs.ts +19 -0
- package/src/setting.ts +1 -1
- package/src/style.ts +140 -50
- package/dist/chunk-54BYDWAM.cjs.map +0 -1
- package/dist/chunk-HOMR5XFP.js +0 -3
- package/dist/chunk-HOMR5XFP.js.map +0 -1
- package/dist/chunk-LD47QOKS.js.map +0 -1
- package/dist/chunk-T4OJFIJK.cjs +0 -4
- package/dist/chunk-T4OJFIJK.cjs.map +0 -1
- package/dist/chunk-UNR34GYU.cjs.map +0 -1
- package/dist/chunk-XZXUU3YH.js.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A style's medium, tones and media (`Style.medium` / `tones` / `media`):
|
|
3
|
+
* - every style classifies itself; the 49 styles offered for images and video
|
|
4
|
+
* keep exactly the medium and tones Nodaro Motion's art-style line has read
|
|
5
|
+
* them with so far (its own id lists), so moving them into the catalog
|
|
6
|
+
* changes no prompt;
|
|
7
|
+
* - the video-only styles are rendered 3D looks, offered for video alone;
|
|
8
|
+
* - image style lists leave them out, and the wire carries `media` so a thin
|
|
9
|
+
* client can do the same.
|
|
10
|
+
*/
|
|
11
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
12
|
+
import {
|
|
13
|
+
STYLES,
|
|
14
|
+
STYLE_MEDIUMS,
|
|
15
|
+
getStyleMedium,
|
|
16
|
+
getStyleTones,
|
|
17
|
+
styleOffersMedia,
|
|
18
|
+
stylesForMedia,
|
|
19
|
+
styleOffersRestyle,
|
|
20
|
+
stylesForRestyle,
|
|
21
|
+
getPickerCatalog,
|
|
22
|
+
projectPickerCatalog,
|
|
23
|
+
registerCatalogPack,
|
|
24
|
+
resetCatalogPacks,
|
|
25
|
+
} from "../index.js"
|
|
26
|
+
|
|
27
|
+
afterEach(() => resetCatalogPacks())
|
|
28
|
+
|
|
29
|
+
/** Nodaro Motion's lists as they read the 49 styles before the catalog carried a medium. */
|
|
30
|
+
const PHOTOGRAPHIC = ["photorealistic", "cinematic", "noir", "early-color-photo", "retro-vintage", "cyberpunk", "steampunk", "glitch-art"]
|
|
31
|
+
const RENDERED = ["3d-render", "low-poly", "isometric", "pixar-3d", "claymation"]
|
|
32
|
+
const BLACK_AND_WHITE = ["noir", "manga", "pencil-sketch", "charcoal", "etching", "woodcut", "ink-wash"]
|
|
33
|
+
const ONE_COLOUR = ["blueprint", "ascii-art", "chalkboard"]
|
|
34
|
+
const VIDEO_ONLY = ["felt-puppet", "knitted-yarn", "toy-figure-3d", "painterly-3d", "painted-clay"]
|
|
35
|
+
/** Left out of restyle lists: in tests each came out as the same plain 2D cartoon, without its own look. */
|
|
36
|
+
const NOT_FOR_RESTYLE = ["fantasy", "concept-art", "surrealism"]
|
|
37
|
+
|
|
38
|
+
const both = STYLES.filter((s) => s.media === undefined)
|
|
39
|
+
|
|
40
|
+
describe("medium and tones", () => {
|
|
41
|
+
it("every style declares a known medium", () => {
|
|
42
|
+
for (const s of STYLES) expect(STYLE_MEDIUMS, s.id).toContain(s.medium)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("the 49 styles for images and video keep the medium Nodaro Motion read them with", () => {
|
|
46
|
+
expect(both).toHaveLength(49)
|
|
47
|
+
for (const s of both) {
|
|
48
|
+
const expected = PHOTOGRAPHIC.includes(s.id) ? "photo" : RENDERED.includes(s.id) ? "3d" : "2d"
|
|
49
|
+
expect(getStyleMedium(s.id), s.id).toBe(expected)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("tones: black and white, one colour, or none", () => {
|
|
54
|
+
for (const s of STYLES) {
|
|
55
|
+
const expected = BLACK_AND_WHITE.includes(s.id) ? "black-and-white" : ONE_COLOUR.includes(s.id) ? "one-colour" : undefined
|
|
56
|
+
expect(getStyleTones(s.id), s.id).toBe(expected)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("an unknown id has no medium and no tones", () => {
|
|
61
|
+
expect(getStyleMedium("no-such-style")).toBeUndefined()
|
|
62
|
+
expect(getStyleMedium(undefined)).toBeUndefined()
|
|
63
|
+
expect(getStyleTones("no-such-style")).toBeUndefined()
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe("media", () => {
|
|
68
|
+
it("the video-only styles are rendered 3D looks, offered for video alone", () => {
|
|
69
|
+
const videoOnly = STYLES.filter((s) => s.media !== undefined)
|
|
70
|
+
expect(videoOnly.map((s) => s.id)).toEqual(VIDEO_ONLY)
|
|
71
|
+
for (const s of videoOnly) {
|
|
72
|
+
expect(s.media, s.id).toEqual(["video"])
|
|
73
|
+
expect(getStyleMedium(s.id), s.id).toBe("3d")
|
|
74
|
+
expect(styleOffersMedia(s, "video"), s.id).toBe(true)
|
|
75
|
+
expect(styleOffersMedia(s, "image"), s.id).toBe(false)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("an image list leaves out the video-only styles; a video list has every style, in catalog order", () => {
|
|
80
|
+
expect(stylesForMedia(STYLES, "image").map((s) => s.id)).toEqual(both.map((s) => s.id))
|
|
81
|
+
expect(stylesForMedia(STYLES, "video").map((s) => s.id)).toEqual(STYLES.map((s) => s.id))
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("a style with no media is offered for both", () => {
|
|
85
|
+
expect(styleOffersMedia({}, "image")).toBe(true)
|
|
86
|
+
expect(styleOffersMedia({}, "video")).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe("restyle", () => {
|
|
91
|
+
it("exactly the styles that do not hold up as a restyle say so", () => {
|
|
92
|
+
expect(STYLES.filter((s) => !styleOffersRestyle(s)).map((s) => s.id)).toEqual(NOT_FOR_RESTYLE)
|
|
93
|
+
for (const id of NOT_FOR_RESTYLE) expect(STYLES.find((s) => s.id === id)!.restyle, id).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("a restyle list is the video list without them, in catalog order", () => {
|
|
97
|
+
expect(stylesForRestyle(STYLES).map((s) => s.id)).toEqual(STYLES.filter((s) => !NOT_FOR_RESTYLE.includes(s.id)).map((s) => s.id))
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it("video generation still offers them", () => {
|
|
101
|
+
for (const id of NOT_FOR_RESTYLE) expect(stylesForMedia(STYLES, "video").some((s) => s.id === id), id).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("a style that says nothing is offered for restyle", () => {
|
|
105
|
+
expect(styleOffersRestyle({})).toBe(true)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe("the style catalog", () => {
|
|
110
|
+
const catalog = () => getPickerCatalog("style")!
|
|
111
|
+
|
|
112
|
+
it("carries media on the video-only options only", () => {
|
|
113
|
+
for (const o of catalog().options!) {
|
|
114
|
+
expect(o.media, o.id).toEqual(VIDEO_ONLY.includes(o.id) ? ["video"] : undefined)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("the wire carries media at both detail levels", () => {
|
|
119
|
+
for (const detail of ["compact", "full"] as const) {
|
|
120
|
+
const options = projectPickerCatalog(catalog(), { detail }).options!
|
|
121
|
+
const felt = options.find((o) => o.id === "felt-puppet")!
|
|
122
|
+
expect(felt.media, detail).toEqual(["video"])
|
|
123
|
+
expect(options.find((o) => o.id === "anime")!.media, detail).toBeUndefined()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("the catalog and the wire carry restyle: false on exactly those options, at both detail levels", () => {
|
|
128
|
+
for (const o of catalog().options!) expect(o.restyle, o.id).toBe(NOT_FOR_RESTYLE.includes(o.id) ? false : undefined)
|
|
129
|
+
for (const detail of ["compact", "full"] as const) {
|
|
130
|
+
const options = projectPickerCatalog(catalog(), { detail }).options!
|
|
131
|
+
expect(options.find((o) => o.id === "fantasy")!.restyle, detail).toBe(false)
|
|
132
|
+
expect(options.find((o) => o.id === "vaporwave")!.restyle, detail).toBeUndefined()
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("a catalog pack that removes a video-only style takes away its medium too", () => {
|
|
137
|
+
registerCatalogPack({ id: "t-deny", catalogId: "style", mode: "deny", denyIds: ["felt-puppet"] })
|
|
138
|
+
expect(getStyleMedium("felt-puppet")).toBeUndefined()
|
|
139
|
+
expect(getPickerCatalog("style")!.options!.some((o) => o.id === "felt-puppet")).toBe(false)
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -33,8 +33,13 @@ afterEach(() => resetCatalogPacks())
|
|
|
33
33
|
|
|
34
34
|
const LOW_POLY_VIDEO =
|
|
35
35
|
"Stylised low poly 3D animation, computer-generated: every person, face, vehicle and room built from large flat-shaded triangles, visible facets on every surface, flat untextured colours, crisp shadow edges between facets."
|
|
36
|
-
/** The
|
|
36
|
+
/** The wordings tested on video (Seedance 2.5), word for word. */
|
|
37
37
|
const TESTED_ON_VIDEO: Readonly<Record<string, string>> = {
|
|
38
|
+
"felt-puppet": "Needle-felted stop-motion puppet animation, handmade: every person a felt puppet with soft fuzzy wool-felt skin, simple stitched features, small bead-like eyes and hair of felted wool strands, clothes sewn from real fabric with visible weave. The whole set is a handmade felt miniature: every car a fuzzy felt-covered model with felt seats, every wall, floor, curtain, table, chair and plant made of felt or wool fabric with visible fibres, the street and sky felt too; warm soft studio light on a tabletop miniature set, a slight stop-motion step to the motion.",
|
|
39
|
+
"knitted-yarn": "Knitted yarn stop-motion animation, handmade: every person a knitted doll with yarn skin in visible knit loops, hair of chunky yarn strands, clothes knitted in cable and rib stitch; every car, building, road, tree and sky knitted from wool yarn with visible stitches; soft warm studio light on a tabletop miniature set, a slight stop-motion step to the motion.",
|
|
40
|
+
"toy-figure-3d": "Stylised 3D toy animation, computer-generated: every person a rounded vinyl toy figure with smooth glossy plastic skin, a simplified oversized head, small dot eyes and moulded plastic hair, clothes as moulded painted plastic; every car, room and prop a chunky rounded toy model with soft bevelled edges; bright clean colours, soft studio lighting.",
|
|
41
|
+
"painterly-3d": "Painterly 3D animation, computer-generated with a hand-painted finish: every person a stylised 3D character with soft rounded features and expressive eyes, skin, hair and clothes textured with visible painted brush strokes; every room and landscape a 3D set painted like a storybook illustration; warm glowing light, rich soft colours.",
|
|
42
|
+
"painted-clay": "Sculpted clay animation with a painted finish, handmade: every person sculpted from matte clay with visible thumbprints and tool marks, painted in soft gouache colour; every car, room and prop sculpted and hand-painted the same way; warm painterly light, a slight stop-motion step to the motion.",
|
|
38
43
|
"pop-art": "Flat 2D cel-shaded comic illustration, hand-drawn: thick uniform black ink outlines, flat colour fills, Ben-Day dot shading, bold saturated primary colours, high contrast.",
|
|
39
44
|
claymation: "Stop-motion claymation, handmade: every person, vehicle and set sculpted from matte plasticine clay with visible fingerprints and tool marks, rounded simplified forms, miniature handcrafted diorama sets, soft warm studio lighting.",
|
|
40
45
|
anime: "2D Japanese anime, hand-drawn cel animation: clean thin ink line art, flat cel shading with one hard shadow tone, large expressive eyes, hair drawn as solid shapes, saturated colours, painted backgrounds.",
|
|
@@ -58,9 +63,19 @@ const LOW_POLY_IMAGE =
|
|
|
58
63
|
|
|
59
64
|
const styleNode = (style: string) => ({ id: "s1", type: "style", data: { style } })
|
|
60
65
|
|
|
66
|
+
/** The styles offered for video alone (`media: ["video"]`). */
|
|
67
|
+
const VIDEO_ONLY = ["felt-puppet", "knitted-yarn", "toy-figure-3d", "painterly-3d", "painted-clay"]
|
|
68
|
+
/**
|
|
69
|
+
* A video wording's length cap. A style offered for images too names its medium and its techniques in one short line;
|
|
70
|
+
* a video-only style also names what the whole set is made of (cars, rooms, props), and the felt line that felted
|
|
71
|
+
* the whole set of a live-action clip on Seedance 2.5 (people, car, street and curtains) is 562 characters.
|
|
72
|
+
*/
|
|
73
|
+
const capOf = (id: string) => (VIDEO_ONLY.includes(id) ? 600 : 240)
|
|
74
|
+
|
|
61
75
|
describe("the catalog", () => {
|
|
62
|
-
it("has 49
|
|
63
|
-
expect(STYLES).toHaveLength(
|
|
76
|
+
it("has 54 styles (49 for images and video, 5 for video alone), and every one carries its own video wording, different from its image wording", () => {
|
|
77
|
+
expect(STYLES).toHaveLength(54)
|
|
78
|
+
expect(STYLES.filter((s) => s.media?.length === 1 && s.media[0] === "video").map((s) => s.id)).toEqual(VIDEO_ONLY)
|
|
64
79
|
for (const s of STYLES) {
|
|
65
80
|
expect(s.videoPromptHint, s.id).toEqual(expect.any(String))
|
|
66
81
|
expect(s.videoPromptHint, s.id).not.toBe(s.promptHint)
|
|
@@ -71,11 +86,11 @@ describe("the catalog", () => {
|
|
|
71
86
|
for (const s of STYLES) {
|
|
72
87
|
const v = s.videoPromptHint!
|
|
73
88
|
expect(v, s.id).toMatch(/^[^:]+: \S[^\n]*\.$/)
|
|
74
|
-
expect(v.length, s.id).toBeLessThanOrEqual(
|
|
89
|
+
expect(v.length, s.id).toBeLessThanOrEqual(capOf(s.id))
|
|
75
90
|
}
|
|
76
91
|
})
|
|
77
92
|
|
|
78
|
-
it("the
|
|
93
|
+
it("the wordings tested on video are word for word", () => {
|
|
79
94
|
for (const [id, wording] of Object.entries(TESTED_ON_VIDEO)) expect(getStyleVideoPromptHint(id), id).toBe(wording)
|
|
80
95
|
})
|
|
81
96
|
|
|
@@ -106,7 +121,7 @@ describe("the catalog", () => {
|
|
|
106
121
|
})
|
|
107
122
|
})
|
|
108
123
|
|
|
109
|
-
describe("image surfaces read only promptHint, for
|
|
124
|
+
describe("image surfaces read only promptHint, for every style (a video-only one wired into an image included)", () => {
|
|
110
125
|
it("the direction fold on image", () => {
|
|
111
126
|
for (const s of STYLES) {
|
|
112
127
|
expect(renderDirectionHints({ style: s.id }, { surface: "image" }), s.id).toEqual([s.promptHint])
|
|
@@ -185,7 +200,7 @@ describe("catalog packs", () => {
|
|
|
185
200
|
|
|
186
201
|
it("the composed catalog carries every style's video wording", () => {
|
|
187
202
|
const options = styleCatalog().options!
|
|
188
|
-
expect(options).toHaveLength(
|
|
203
|
+
expect(options).toHaveLength(54)
|
|
189
204
|
for (const o of options) expect(o.videoPromptHint, o.id).toBe(videoOf(o.id))
|
|
190
205
|
})
|
|
191
206
|
|
|
@@ -1117,7 +1117,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1117
1117
|
},
|
|
1118
1118
|
{
|
|
1119
1119
|
id: "generate-image/pixar-3d",
|
|
1120
|
-
name: "3D Animated
|
|
1120
|
+
name: "3D Animated Film",
|
|
1121
1121
|
description: "Polished CG character look.",
|
|
1122
1122
|
group: "Illustration & Art Styles",
|
|
1123
1123
|
data: {
|
|
@@ -591,7 +591,7 @@ export const GENERATE_VIDEO_PRESETS: readonly FactoryPreset[] = [
|
|
|
591
591
|
{
|
|
592
592
|
id: "generate-video/cartoon-3d",
|
|
593
593
|
name: "3D Cartoon",
|
|
594
|
-
description: "Playful
|
|
594
|
+
description: "Playful animated-film motion.",
|
|
595
595
|
group: "Animation & Style",
|
|
596
596
|
data: {
|
|
597
597
|
provider: "kling-3.0",
|
|
@@ -50,11 +50,18 @@ import { VOICE_PACES, VOICE_EMOTIONS, VOICE_ARCHETYPES } from "./voice-delivery.
|
|
|
50
50
|
interface AnalyzerEntry {
|
|
51
51
|
readonly id: string
|
|
52
52
|
readonly label: string
|
|
53
|
+
/** What the analyzers' list calls this entry where it differs from `label` (`Style.promptLabel`). */
|
|
54
|
+
readonly promptLabel?: string
|
|
53
55
|
readonly description: string
|
|
54
56
|
readonly dimension?: string
|
|
55
57
|
readonly category?: string
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/** An entry's row in the system-prompt legend: a list only models read. */
|
|
61
|
+
function legendRow(e: AnalyzerEntry): { id: string; label: string; description: string } {
|
|
62
|
+
return { id: e.id, label: e.promptLabel ?? e.label, description: e.description }
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
export type PickerApplyMode = "override" | "overwrite-detected" | "fill-empty"
|
|
59
66
|
type ApplyCleanup = (patch: Record<string, unknown>, mode: PickerApplyMode) => void
|
|
60
67
|
|
|
@@ -362,7 +369,7 @@ export function buildPickerAnalyzerSpec(pickerType: PickerType): PickerAnalyzerS
|
|
|
362
369
|
label: d.label,
|
|
363
370
|
limit: 1,
|
|
364
371
|
entryIds: d.entries.map((e) => e.id),
|
|
365
|
-
legend: d.entries.map(
|
|
372
|
+
legend: d.entries.map(legendRow),
|
|
366
373
|
},
|
|
367
374
|
],
|
|
368
375
|
}
|
|
@@ -379,7 +386,7 @@ export function buildPickerAnalyzerSpec(pickerType: PickerType): PickerAnalyzerS
|
|
|
379
386
|
label: d.labels[key] ?? key,
|
|
380
387
|
limit: d.limitFn(key),
|
|
381
388
|
entryIds: entries.map((e) => e.id),
|
|
382
|
-
legend: entries.map(
|
|
389
|
+
legend: entries.map(legendRow),
|
|
383
390
|
}
|
|
384
391
|
})
|
|
385
392
|
return { pickerType, toolName: d.toolName, dimensions, cleanup: d.cleanup }
|
package/src/picker-art/images.ts
CHANGED
|
@@ -41,11 +41,12 @@ function originOf(baseUrl: string): string | undefined {
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* A still, 480px-wide picture of a rendered look preview: the image resized by
|
|
44
|
-
* the CDN, or — for a clip (camera motion) — a frame taken 1s in
|
|
45
|
-
* transforms the editor's tiles use. A URL off the
|
|
46
|
-
* as is for an image, and dropped for a clip (no still
|
|
44
|
+
* the CDN, or — for a clip (camera motion) — a frame taken 1s in (or at
|
|
45
|
+
* `clipSeconds`). The same transforms the editor's tiles use. A URL off the
|
|
46
|
+
* transforming CDN is kept as is for an image, and dropped for a clip (no still
|
|
47
|
+
* to offer).
|
|
47
48
|
*/
|
|
48
|
-
export function lookPreviewStillUrl(url: string, width = STILL_WIDTH): string | undefined {
|
|
49
|
+
export function lookPreviewStillUrl(url: string, width = STILL_WIDTH, clipSeconds = 1): string | undefined {
|
|
49
50
|
let parsed: URL
|
|
50
51
|
try {
|
|
51
52
|
parsed = new URL(url)
|
|
@@ -55,7 +56,7 @@ export function lookPreviewStillUrl(url: string, width = STILL_WIDTH): string |
|
|
|
55
56
|
const clip = /\.(mp4|webm|mov)$/i.test(parsed.pathname)
|
|
56
57
|
if (!TRANSFORM_HOSTS.has(parsed.hostname)) return clip ? undefined : url
|
|
57
58
|
return clip
|
|
58
|
-
? `${parsed.origin}/cdn-cgi/media/mode=frame,time
|
|
59
|
+
? `${parsed.origin}/cdn-cgi/media/mode=frame,time=${clipSeconds}s,width=${width}${parsed.pathname}`
|
|
59
60
|
: `${parsed.origin}/cdn-cgi/image/width=${width},format=auto,quality=80${parsed.pathname}`
|
|
60
61
|
}
|
|
61
62
|
|
package/src/picker-art/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { SOUND_ART } from "./sound-art-map.js"
|
|
|
9
9
|
export { SOUND_ART_FILES } from "./sound-art-files.generated.js"
|
|
10
10
|
export { CHARACTER_ART_FILES } from "./character-art-files.generated.js"
|
|
11
11
|
export { LOOK_PREVIEW_SETS, type LookPreviewSet, type LookPreviewSets } from "./look-preview-sets.js"
|
|
12
|
+
export { RESTYLE_PREVIEWS, restylePreviewStillUrl, type RestylePreview } from "./restyle-previews.js"
|
|
12
13
|
export { SOUND_RENDER_SETS } from "./sound-render-sets.js"
|
|
13
14
|
export * from "./paths.js"
|
|
14
15
|
export * from "./images.js"
|
|
@@ -70,6 +70,11 @@ export const LOOK_PREVIEW_SETS = {
|
|
|
70
70
|
"mixed-media": "https://cdn.nodaro.ai/picker-renders/d36ab150-9cd6-47ac-bdd9-3b26990a4fa9.png",
|
|
71
71
|
"early-color-photo": "https://cdn.nodaro.ai/picker-renders/1bf61f3d-c174-466f-9092-5de72dc7abea.png",
|
|
72
72
|
manga: "https://cdn.nodaro.ai/picker-renders/3d6bbfd8-8c62-4769-b62e-11d2a7e43b4b.png",
|
|
73
|
+
"felt-puppet": "https://cdn.nodaro.ai/picker-renders/31ad3b39-d501-4b76-9c1c-2f87fc374333.png",
|
|
74
|
+
"knitted-yarn": "https://cdn.nodaro.ai/picker-renders/b80b6a96-2221-4c68-8e0c-4beaa5a6ce56.png",
|
|
75
|
+
"toy-figure-3d": "https://cdn.nodaro.ai/picker-renders/add81127-a005-4f9a-a002-780768238715.png",
|
|
76
|
+
"painterly-3d": "https://cdn.nodaro.ai/picker-renders/6aec1d80-be9f-4f2e-a1b7-fa38a61131d1.png",
|
|
77
|
+
"painted-clay": "https://cdn.nodaro.ai/picker-renders/daedee18-c034-4b98-80bb-7bef7e05c547.png",
|
|
73
78
|
},
|
|
74
79
|
"color-look": {
|
|
75
80
|
warm: "https://cdn.nodaro.ai/picker-renders/8511a1bc-3d3b-46a8-adc0-26fcc9e93a4b.png",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Restyle previews: for each art style, a short clip of real footage wiping
|
|
2
|
+
// into that style, for a picker that restyles a video. Keyed by the `style`
|
|
3
|
+
// catalog's option id. An entry exists only for an approved clip: a style
|
|
4
|
+
// without one has no preview here.
|
|
5
|
+
//
|
|
6
|
+
// Each approved style keeps the URLs of its source shot and its generated clip
|
|
7
|
+
// (no wipe) in a comment beside its entry, so the clip can be composed again if
|
|
8
|
+
// the switch animation changes.
|
|
9
|
+
//
|
|
10
|
+
// Separate from LOOK_PREVIEW_SETS.style, whose stills every other art style
|
|
11
|
+
// picker keeps showing. Like every render, a clip lives under picker-renders/
|
|
12
|
+
// on the CDN, a key no user owns; a test holds this.
|
|
13
|
+
import { lookPreviewStillUrl } from "./images.js"
|
|
14
|
+
|
|
15
|
+
/** One style's preview clip. */
|
|
16
|
+
export interface RestylePreview {
|
|
17
|
+
readonly url: string
|
|
18
|
+
/** The moment of the clip its still is cut from, for a clip whose usual moment frames it badly. Absent: the usual moment (`lookPreviewStillUrl`). */
|
|
19
|
+
readonly stillAtSeconds?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Art style id → its restyle preview clip. */
|
|
23
|
+
export const RESTYLE_PREVIEWS: Readonly<Record<string, RestylePreview>> = {
|
|
24
|
+
// Source shot: https://cdn.nodaro.ai/picker-renders/4054a0e1-af60-47f9-86bd-a78ab93f3800.mp4
|
|
25
|
+
// Generated, no wipe: https://cdn.nodaro.ai/picker-renders/9dc6b337-58b4-4a63-889f-70561a8f1f00.mp4
|
|
26
|
+
anime: { url: "https://cdn.nodaro.ai/picker-renders/7fbad6e4-023f-4282-ac3d-d186f2cbb37b.mp4", stillAtSeconds: 7 },
|
|
27
|
+
// Source shot: https://cdn.nodaro.ai/picker-renders/4ffff922-e807-42cc-a2a0-c66c3a8d492c.mp4
|
|
28
|
+
// Generated, no wipe: https://cdn.nodaro.ai/picker-renders/fca5d96d-ee9c-430e-ba82-2795c0adb8d6.mp4
|
|
29
|
+
claymation: { url: "https://cdn.nodaro.ai/picker-renders/2916a2f7-114c-4c7e-9be0-5bf229d76133.mp4" },
|
|
30
|
+
// Source shot: https://cdn.nodaro.ai/picker-renders/41faaa51-5e77-4097-aa9b-bef6738328b7.mp4
|
|
31
|
+
// Generated, no wipe: https://cdn.nodaro.ai/picker-renders/5a28da72-f3ee-49a8-b732-f73e79741b62.mp4
|
|
32
|
+
"comic-book": { url: "https://cdn.nodaro.ai/picker-renders/7a2cd17c-b02f-4ae4-9723-ef48d0eda52a.mp4" },
|
|
33
|
+
// Source shot: https://cdn.nodaro.ai/picker-renders/4a70c22d-04f0-4b5a-90d4-e9dd7ddb01c4.mp4
|
|
34
|
+
// Generated, no wipe: https://cdn.nodaro.ai/picker-renders/5ab33529-ef2a-42bc-a625-d6b1abfeef1c.mp4
|
|
35
|
+
"felt-puppet": { url: "https://cdn.nodaro.ai/picker-renders/3aa3c81b-ba19-4776-becc-e0c47c700699.mp4" },
|
|
36
|
+
// Source shot: https://cdn.nodaro.ai/picker-renders/d9e12a4a-677e-4e36-8f44-8503f14dd6b8.mp4
|
|
37
|
+
// Generated, no wipe: https://cdn.nodaro.ai/picker-renders/d4dad451-802a-4795-9cc3-71ff3ed70c69.mp4
|
|
38
|
+
"pixar-3d": { url: "https://cdn.nodaro.ai/picker-renders/c5ea2111-3283-4f80-ab05-8ab4450946ce.mp4" },
|
|
39
|
+
// Source shot: https://cdn.nodaro.ai/picker-renders/789f4330-660b-42bd-8d11-6e638e8c7c88.mp4
|
|
40
|
+
// Generated, no wipe: https://cdn.nodaro.ai/picker-renders/10471107-b26a-42e9-a3a5-068a42436d3c.mp4
|
|
41
|
+
watercolor: { url: "https://cdn.nodaro.ai/picker-renders/61a26588-d09a-4959-b677-ee8b32e3281a.mp4" },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A preview clip's still, a frame the CDN cuts from it at the clip's own moment. */
|
|
45
|
+
export function restylePreviewStillUrl(preview: RestylePreview, width?: number): string | undefined {
|
|
46
|
+
return lookPreviewStillUrl(preview.url, width, preview.stillAtSeconds)
|
|
47
|
+
}
|
package/src/picker-catalogs.ts
CHANGED
|
@@ -140,6 +140,15 @@ export interface PickerOption {
|
|
|
140
140
|
* does not carry it.
|
|
141
141
|
*/
|
|
142
142
|
readonly videoPromptHint?: string
|
|
143
|
+
/**
|
|
144
|
+
* Style only: the outputs the style is offered for. Present only on a style
|
|
145
|
+
* offered for one of them (`["video"]`: a look that only works in motion);
|
|
146
|
+
* absent means both. Carried to the wire (`projectPickerCatalog`). An image style list leaves out an option without
|
|
147
|
+
* "image".
|
|
148
|
+
*/
|
|
149
|
+
readonly media?: ReadonlyArray<"image" | "video">
|
|
150
|
+
/** Style only: `false` on a style restyle lists leave out (`Style.restyle`); absent means offered. Carried to the wire. */
|
|
151
|
+
readonly restyle?: false
|
|
143
152
|
/**
|
|
144
153
|
* Transitions only: present (`true`) on a row whose mechanism is a CUT, so
|
|
145
154
|
* it takes no duration and no intensity — see `Transition.instant` /
|
|
@@ -231,6 +240,10 @@ interface BaseCatalogEntry {
|
|
|
231
240
|
readonly term?: string
|
|
232
241
|
/** Style: see `Style.videoPromptHint`. Propagated verbatim into the flattened option. */
|
|
233
242
|
readonly videoPromptHint?: string
|
|
243
|
+
/** Style: see `Style.media`. Propagated verbatim into the flattened option. */
|
|
244
|
+
readonly media?: ReadonlyArray<"image" | "video">
|
|
245
|
+
/** Style: see `Style.restyle`. Propagated verbatim into the flattened option. */
|
|
246
|
+
readonly restyle?: false
|
|
234
247
|
/** W1-a: see Person.adultOnly. Propagated verbatim into the flattened option. */
|
|
235
248
|
readonly adultOnly?: true
|
|
236
249
|
/** Transitions: see `Transition.instant`. Propagated into the flattened option as `instant: true`. */
|
|
@@ -278,6 +291,8 @@ function toOptions<T extends BaseCatalogEntry>(
|
|
|
278
291
|
}
|
|
279
292
|
if (e.description) opt.description = e.description
|
|
280
293
|
if (e.videoPromptHint) opt.videoPromptHint = e.videoPromptHint
|
|
294
|
+
if (e.media) opt.media = e.media
|
|
295
|
+
if (e.restyle === false) opt.restyle = false
|
|
281
296
|
if (categoryField) opt.category = e[categoryField] as unknown as string
|
|
282
297
|
if (e.adultOnly) opt.adultOnly = true
|
|
283
298
|
if (e.instant) opt.instant = true
|
|
@@ -1077,6 +1092,10 @@ function projectOptionFields(o: PickerOption, detail: PickerCatalogDetail, image
|
|
|
1077
1092
|
// present only when the option has a picture (a srcset only for a CDN render).
|
|
1078
1093
|
const extras = {
|
|
1079
1094
|
...(o.motion ? { motion: o.motion } : {}),
|
|
1095
|
+
// A style's media rides at both detail levels: a thin client filters its image style list by it.
|
|
1096
|
+
...(o.media ? { media: [...o.media] } : {}),
|
|
1097
|
+
// A style restyle lists leave out says so at both detail levels too.
|
|
1098
|
+
...(o.restyle === false ? { restyle: false as const } : {}),
|
|
1080
1099
|
...(image ? { imageUrl: image.url } : {}),
|
|
1081
1100
|
...(image?.srcSet ? { imageSrcSet: image.srcSet } : {}),
|
|
1082
1101
|
}
|
package/src/setting.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* shop, forest clearing, cyberpunk alley, cathedral, etc.). Orthogonal to the
|
|
6
6
|
* other cinematography dimensions:
|
|
7
7
|
*
|
|
8
|
-
* - Style = artistic medium (Oil Painting,
|
|
8
|
+
* - Style = artistic medium (Oil Painting, 3D Animated Film, Photorealistic)
|
|
9
9
|
* - Atmosphere = what's in the air (Fog, Rain, God rays, Dust)
|
|
10
10
|
* - Lighting = direction / quality of light
|
|
11
11
|
* - Setting = *where* (this file)
|