@gentbajko/slopify 3.1.0 → 3.1.1

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.
@@ -0,0 +1,18 @@
1
+ # Slopify 3.1.1
2
+
3
+ Released 30 September 2026.
4
+
5
+ 3.1.1 lets the shorts draw figures the way the video does.
6
+
7
+ ## Shorts
8
+
9
+ - The shorts' image prompt (**Shorts → Image prompt**) takes `{{Appearance}}`, like the video's image and thumbnail prompts. Each short gets the subject's researched look and those of the figures its clip names, so a figure looks the same in the shorts as in the video.
10
+ - A `{{Scene}}` line in the shorts' image prompt is left out: each short's images already show what is said at that moment.
11
+
12
+ ## Help
13
+
14
+ - The **Prompts** page of the wiki explains the two keywords Slopify fills itself, `{{Scene}}` and `{{Appearance}}`: what each becomes, where it works, and an example.
15
+
16
+ ## Upgrading from 3.1.0
17
+
18
+ - **Nothing becomes outdated.** Only a shorts image prompt that names `{{Appearance}}` changes, the next time its shorts are made.
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "id": "3.1.1",
4
+ "title": "Slopify 3.1.1",
5
+ "version": "3.1.1",
6
+ "date": "2026-09-30"
7
+ },
2
8
  {
3
9
  "id": "3.1.0",
4
10
  "title": "Slopify 3.1.0",
@@ -4,7 +4,7 @@ import { recipe, selectedReference, } from "./recipe-model.js";
4
4
  import { llmInput, llmInputFingerprint } from "./recipe-text.js";
5
5
  export const imageAppearanceKey = "images:appearance";
6
6
  // The prompt bodies a run draws pictures from: the generated images', the thumbnail's when it
7
- // is drawn from a Library prompt, and the establishing image's.
7
+ // is drawn from a Library prompt, the establishing image's and the shorts' image prompt.
8
8
  function pictureBodies(context) {
9
9
  const { config, content } = context;
10
10
  const images = config.sources.images === "generate"
@@ -27,6 +27,9 @@ function pictureBodies(context) {
27
27
  ...(config.sources.images === "generate" && config.reference?.source === "prompt"
28
28
  ? [content.promptTemplates.referencePrompt ?? config.rendered.referencePrompt]
29
29
  : []),
30
+ ...(config.shorts?.enabled === true && config.shorts.imagePrompt?.trim()
31
+ ? [content.promptTemplates.shortsImage ?? config.rendered.shortsImage]
32
+ : []),
30
33
  ];
31
34
  }
32
35
  export function imageAppearance(context, text) {
@@ -43,7 +43,7 @@ export function buildRecipes(context) {
43
43
  ...exports,
44
44
  ...youtube,
45
45
  ...(reference === undefined ? [] : [reference]),
46
- ...shortsRecipes(context, exports, drawnFrom, audio.cards ?? [], masterPlan(context, audio.levels, "video")),
46
+ ...shortsRecipes(context, exports, drawnFrom, audio.cards ?? [], masterPlan(context, audio.levels, "video"), appearance),
47
47
  ...thumbnail,
48
48
  ...documentRecipes(context, text, thumbnail),
49
49
  ...visualAssets(context, visualRecipes(context.config, context.content, audio.mediaFingerprint, captions?.fingerprint ?? null, (images) => editPlan(context, exports, youtube, images, audio.cards ?? []), drawnFrom, timing === undefined ? null : resourceIdentity(context, timing), withLines(masterPlan(context, audio.levels, "video"), linePlan(context, timing)), scenes, appearance)),
@@ -1,9 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { fingerprint } from "../../kernel/runner/work.js";
3
3
  import { usesShorts } from "../admission/rules.js";
4
+ import { withoutScene } from "../images/scenes.js";
4
5
  import { effectiveClips, pickedShortsOf } from "../shorts/clips.js";
5
6
  import { defaultShortsImagePrompt, musicVolumeOf, shortImageCount, shortsSpeedOf, } from "../shorts/model.js";
6
7
  import { imagePromptMessages } from "../shorts/prompts.js";
8
+ import { lookWait, withLooks } from "./recipe-appearance.js";
7
9
  import { castFor } from "./recipe-cast.js";
8
10
  import { noMaster } from "./recipe-loudness.js";
9
11
  import { recipe, resourceIdentity, selectedReference, } from "./recipe-model.js";
@@ -28,7 +30,9 @@ reference,
28
30
  // "Show tables and figures on screen": the cards a short shows where it describes one.
29
31
  cards = [],
30
32
  // Level the volume: the levelled narration and the master (`recipe-loudness.ts`).
31
- master = noMaster) {
33
+ master = noMaster,
34
+ // How the figures look, for a shorts image prompt with `{{Appearance}}` (`recipe-appearance.ts`).
35
+ appearance) {
32
36
  const { config } = context;
33
37
  const shorts = config.shorts;
34
38
  const timing = exports.find((value) => value.key === "subtitles:timing");
@@ -58,8 +62,10 @@ master = noMaster) {
58
62
  { kind: "provider" });
59
63
  const picked = savedPicked(context, pick);
60
64
  const images = config.images ?? { provider: "", model: "" };
65
+ // A shorts image prompt may name `{{Scene}}` like the video's: the prompts call already writes
66
+ // each image's moment from what the clip says, so that line is left out here.
61
67
  const style = shorts.imagePrompt?.trim()
62
- ? renderedPrompt(context, "shortsImage")
68
+ ? withoutScene(renderedPrompt(context, "shortsImage"))
63
69
  : defaultShortsImagePrompt;
64
70
  if (picked === undefined)
65
71
  return [
@@ -91,9 +97,22 @@ master = noMaster) {
91
97
  const prefix = `shorts:${String(clip.number)}`;
92
98
  const token = clipToken(context, clip);
93
99
  const count = shortImageCount(clip.end - clip.start, config.imageSeconds);
100
+ // The looks of the figures the clip names, and always the subject's: a short stands for
101
+ // the video. Until they are looked up the clip's prompts wait for them.
102
+ const looked = withLooks(style, appearance, clip.text, { subjectAlways: true });
103
+ if (looked === null) {
104
+ const wait = lookWait(style, appearance);
105
+ recipes.push(recipe(context, `${prefix}:prompts`, "video", {
106
+ kind: "deferred",
107
+ version: 1,
108
+ operation: "shorts",
109
+ template: [...wait.template, style, clip.text, count],
110
+ }, [pick.key, ...wait.dependsOn], { token }));
111
+ continue;
112
+ }
94
113
  const prompts = {
95
114
  ...recipe(context, `${prefix}:prompts`, "video", llmInput(context, imagePromptMessages({
96
- style,
115
+ style: looked,
97
116
  videoTitle: config.title,
98
117
  shortTitle: clip.title,
99
118
  text: clip.text,
@@ -124,7 +124,7 @@ The style, composition and everything else in the prompt stay as written; only t
124
124
 
125
125
  ## Looks looked up: {{Appearance}}
126
126
 
127
- An image model draws a named figure from whatever it half remembers, so the same character can look different in every image. Put `{{Appearance}}` in an image, thumbnail or establishing prompt, for example a line `Looks: {{Appearance}}`, and Slopify looks the looks up instead.
127
+ An image model draws a named figure from whatever it half remembers, so the same character can look different in every image. Put `{{Appearance}}` in an image, thumbnail, establishing or shorts image prompt, for example a line `Looks: {{Appearance}}`, and Slopify looks the looks up instead.
128
128
 
129
129
  - Once the article exists, one call to the project's AI model **searches the web** for how the video's subject and every named character in the article look: build, face, clothing, colours and the marks that make them recognisable. Where depictions disagree, it describes the most iconic one.
130
130
  - With **Scenes from the article** on, each picture gets the looks of the figures its scene names (up to three besides the subject), so a scene of a place or of someone else isn't redrawn with the subject in it; a scene that names no one leaves the looks line out. A thumbnail always gets the subject's look. Without scenes, every picture gets the subject's look.
@@ -50,7 +50,7 @@ When the body already has text, Slopify asks "Replace this prompt body?" first.
50
50
 
51
51
  ## Keywords (slots)
52
52
 
53
- Every `{{keyword}}` in the body becomes one field on Play.
53
+ Every `{{keyword}}` in the body becomes one field on Play, except the two Slopify fills itself (below).
54
54
 
55
55
  - You type a keyword's value once per video, and it is filled into every prompt, intro and outro that names it.
56
56
  - Names are case-sensitive: `{{Topic}}` and `{{topic}}` are two fields.
@@ -63,6 +63,30 @@ Mistakes the editor refuses, with the line and column:
63
63
  - a keyword with no name,
64
64
  - a keyword placed inside another keyword.
65
65
 
66
+ ### Keywords Slopify fills: {{Scene}} and {{Appearance}}
67
+
68
+ Two keywords never become fields. The project's AI model writes them during the run, once the article exists, and the picture waits for them:
69
+
70
+ | Keyword | Filled with | Works in | Needs |
71
+ | --- | --- | --- | --- |
72
+ | `{{Scene}}` | This picture's moment from the article: for images, in order from the article's opening to its ending; for thumbnails, its most striking moment, a different one for each of three. | Image and thumbnail prompts | Images → **Scenes from the article** on |
73
+ | `{{Appearance}}` | How the figures look, looked up on the web (most iconic depiction): the ones the picture's scene names, or the video's subject; a thumbnail or a short always gets the subject. | Image, thumbnail, establishing and shorts image prompts | Nothing: the keyword turns it on |
74
+
75
+ Put each on a line of its own with a label, for example:
76
+
77
+ ```
78
+ Scene: {{Scene}}
79
+
80
+ Looks: {{Appearance}}
81
+ ```
82
+
83
+ - An image prompt without `{{Scene}}` still gets its scene, after its first paragraph, while Scenes from the article is on. A thumbnail prompt without it gets none.
84
+ - With Scenes from the article off, a line holding `{{Scene}}` is left out. A `{{Appearance}}` line whose scene names no one is left out too.
85
+ - Each costs one more AI call per video (the looks call searches the web).
86
+ - In the shorts' image prompt (**Shorts → Image prompt**), `{{Appearance}}` gives each short the subject's look and those of the figures its clip names. A `{{Scene}}` line is left out there: each short's images already show what is said at that moment.
87
+
88
+ See [Play Images](Play-Images#scenes-from-the-article) for how the scenes and looks are chosen.
89
+
66
90
  ## Mark an image prompt as photorealistic
67
91
 
68
92
  YouTube asks whether a video contains realistic-looking AI scenes. Slopify answers for you from your marks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gentbajko/slopify",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Turn a prompt into a finished video, locally, with your own provider keys",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Gent Bajko",