@gentbajko/slopify 3.25.5 → 3.25.7
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/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/patch-notes/3.25.6.md +5 -0
- package/dist/patch-notes/3.25.7.md +5 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/rebuild/recipe-save.js +6 -1
- package/dist/slices/rebuild/recipe-shorts.js +18 -6
- package/dist/slices/rebuild/recipe-softening.js +2 -2
- package/dist/slices/rebuild/runtime-plan.js +2 -2
- package/dist/slices/revisions/manifest-repo.js +15 -0
- package/dist/slices/revisions/mutations.js +3 -2
- package/dist/slices/revisions/restore.js +2 -2
- package/dist/slices/revisions/view.js +11 -0
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Slopify 3.25.6
|
|
2
|
+
|
|
3
|
+
Released 17 October 2026.
|
|
4
|
+
|
|
5
|
+
- **Saving a softened project again no longer times the words again.** With Soften breaths or Soften S sounds on, a second save or a restore could plan the word timing, the description and the shorts' pick again, which are paid calls. Slopify now finds the narration the timing was made from in the project's earlier versions and keeps the timing.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Slopify 3.25.7
|
|
2
|
+
|
|
3
|
+
Released 17 October 2026.
|
|
4
|
+
|
|
5
|
+
- **An older project's shorts can be remade again.** On a project made before an earlier change to how Slopify recognises finished work, Choose what to remake said a short "has an unavailable prerequisite" and a run left the shorts out. Slopify now recognises the shorts it already picked, so they render again without picking new moments.
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"id": "3.25.7",
|
|
4
|
+
"title": "Slopify 3.25.7",
|
|
5
|
+
"version": "3.25.7",
|
|
6
|
+
"date": "2026-10-17"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "3.25.6",
|
|
10
|
+
"title": "Slopify 3.25.6",
|
|
11
|
+
"version": "3.25.6",
|
|
12
|
+
"date": "2026-10-17"
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"id": "3.25.5",
|
|
4
16
|
"title": "Slopify 3.25.5",
|
|
@@ -156,7 +156,12 @@ function planEdit(base, edit, prepared, inspected, keeping) {
|
|
|
156
156
|
config: base.revision.config,
|
|
157
157
|
content: base.revision.content,
|
|
158
158
|
manifest,
|
|
159
|
-
history: {
|
|
159
|
+
history: {
|
|
160
|
+
outputs: base.outputs,
|
|
161
|
+
pieces: base.pieces,
|
|
162
|
+
earlierJoins: base.earlierJoins,
|
|
163
|
+
earlierJoinPieces: base.earlierJoinPieces,
|
|
164
|
+
},
|
|
160
165
|
resolved: {
|
|
161
166
|
articleMarkdown: base.articleMarkdown,
|
|
162
167
|
researchNotes: base.revision.config.provided.research ?? null,
|
|
@@ -9,7 +9,7 @@ import { imagePromptMessages } from "../shorts/prompts.js";
|
|
|
9
9
|
import { lookWait, withLooks } from "./recipe-appearance.js";
|
|
10
10
|
import { castFor } from "./recipe-cast.js";
|
|
11
11
|
import { noMaster } from "./recipe-loudness.js";
|
|
12
|
-
import { recipe, resourceIdentity, selectedReference, } from "./recipe-model.js";
|
|
12
|
+
import { recipe, resourceIdentity, selectedAsset, selectedReference, } from "./recipe-model.js";
|
|
13
13
|
import { castField, imageChoice } from "./recipe-reference.js";
|
|
14
14
|
import { llmInput, renderedPrompt } from "./recipe-text.js";
|
|
15
15
|
// The Shorts step unfolds as its answers land, the way research's chapters unfold from the
|
|
@@ -39,12 +39,12 @@ appearance) {
|
|
|
39
39
|
const timing = exports.find((value) => value.key === "subtitles:timing");
|
|
40
40
|
if (!usesShorts(config) || shorts === undefined || timing === undefined)
|
|
41
41
|
return [];
|
|
42
|
-
const
|
|
42
|
+
const pickFor = (timingIdentity) => recipe(context, shortsPickKey, "video", {
|
|
43
43
|
kind: "local",
|
|
44
44
|
version: 1,
|
|
45
45
|
operation: "shorts-pick-v1",
|
|
46
46
|
values: [
|
|
47
|
-
|
|
47
|
+
timingIdentity,
|
|
48
48
|
config.llm?.provider ?? "",
|
|
49
49
|
config.llm?.model ?? "",
|
|
50
50
|
config.llm?.thinking ?? null,
|
|
@@ -81,7 +81,17 @@ appearance) {
|
|
|
81
81
|
}, [timing.key],
|
|
82
82
|
// A paid LLM call: readiness, the charge warning and the estimate treat it like one.
|
|
83
83
|
{ kind: "provider" });
|
|
84
|
-
const
|
|
84
|
+
const pick = pickFor(resourceIdentity(context, timing));
|
|
85
|
+
// The same pick asked of the finished timing as it was recorded: a run's plan reads the
|
|
86
|
+
// catalogue, which gives the timing another fingerprint than the one the version was saved
|
|
87
|
+
// with, while the timing file is the same.
|
|
88
|
+
const asset = selectedAsset(context, timing.key, timing.fingerprint);
|
|
89
|
+
const recorded = context.manifest.outputs.find((row) => row.workKey === timing.key &&
|
|
90
|
+
row.assetId === asset &&
|
|
91
|
+
asset !== null &&
|
|
92
|
+
row.fingerprint !== timing.fingerprint &&
|
|
93
|
+
selectedReference(row));
|
|
94
|
+
const picked = savedPicked(context, pick, recorded === undefined ? [] : [pickFor([recorded.fingerprint, asset]).fingerprint]);
|
|
85
95
|
const images = config.images ?? { provider: "", model: "" };
|
|
86
96
|
// A shorts image prompt may name `{{Scene}}` like the video's: the prompts call already writes
|
|
87
97
|
// each image's moment from what the clip says, so that line is left out here.
|
|
@@ -219,7 +229,9 @@ export function shortRemakeKey(number) {
|
|
|
219
229
|
return `shorts:${String(number)}`;
|
|
220
230
|
}
|
|
221
231
|
// What the pick saved, when its saved answer is the one this plan asks for.
|
|
222
|
-
export function savedPicked(context, pick
|
|
232
|
+
export function savedPicked(context, pick,
|
|
233
|
+
// Other fingerprints the same pick has (the one recorded with the finished timing).
|
|
234
|
+
also = []) {
|
|
223
235
|
const done = context.manifest.pieces.filter((row) => row.key === pick.key && selectedReference(row) && row.piece.state === "done");
|
|
224
236
|
const piece = done.find((row) => row.fingerprint === pick.fingerprint) ??
|
|
225
237
|
// A project from before an older fingerprint scheme: its output carries the plan's
|
|
@@ -228,7 +240,7 @@ export function savedPicked(context, pick) {
|
|
|
228
240
|
(done.length === 1 &&
|
|
229
241
|
context.manifest.outputs.some((row) => row.workKey === pick.key &&
|
|
230
242
|
selectedReference(row) &&
|
|
231
|
-
row.fingerprint === pick.fingerprint &&
|
|
243
|
+
(row.fingerprint === pick.fingerprint || also.includes(row.fingerprint)) &&
|
|
232
244
|
(row.state === "ready" || row.state === "outdated"))
|
|
233
245
|
? done[0]
|
|
234
246
|
: undefined);
|
|
@@ -28,10 +28,10 @@ export function unsoftenedTimelines(context, text) {
|
|
|
28
28
|
// project from before an older fingerprint scheme its outputs carry another fingerprint
|
|
29
29
|
// than its pieces.
|
|
30
30
|
const fingerprint = entry.audio[0];
|
|
31
|
-
const output = past.outputs.find((row) => row.workKey === key &&
|
|
31
|
+
const output = [...past.outputs, ...(past.earlierJoins ?? [])].find((row) => row.workKey === key &&
|
|
32
32
|
row.fingerprint === fingerprint &&
|
|
33
33
|
(row.state === "ready" || row.state === "outdated"));
|
|
34
|
-
const piece = past.pieces.find((row) => row.key === key &&
|
|
34
|
+
const piece = [...past.pieces, ...(past.earlierJoinPieces ?? [])].find((row) => row.key === key &&
|
|
35
35
|
row.fingerprint === fingerprint &&
|
|
36
36
|
row.piece.state === "done" &&
|
|
37
37
|
row.assetId !== null);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { catalogueSchema } from "../../catalog/schema.js";
|
|
4
|
-
import { getRevisionView } from "../revisions/view.js";
|
|
4
|
+
import { getRevisionView, withEarlierJoins } from "../revisions/view.js";
|
|
5
5
|
import { outputPath } from "../storage/layout.js";
|
|
6
6
|
import { referencedAssetsAvailable, retainedNarrationPieces } from "./narration-history.js";
|
|
7
7
|
import { applyProvidedReviews } from "./provided-review.js";
|
|
@@ -58,7 +58,7 @@ export function executionPlan(deps, view, catalogue) {
|
|
|
58
58
|
.filter((row) => row.available && row.assetId !== null)
|
|
59
59
|
.flatMap((row) => (row.assetId === null ? [] : [row.assetId])),
|
|
60
60
|
]);
|
|
61
|
-
return applyProvidedReviews(deps.db, view.revision.id, planRevisionWork(view.revision, view, catalogue, available, {
|
|
61
|
+
return applyProvidedReviews(deps.db, view.revision.id, planRevisionWork(view.revision, withEarlierJoins(deps, view), catalogue, available, {
|
|
62
62
|
...resolved,
|
|
63
63
|
...(research === undefined ? {} : { research }),
|
|
64
64
|
}, history));
|
|
@@ -64,6 +64,21 @@ export function insertManifestPiece(db, revision, piece, recordId, publicationId
|
|
|
64
64
|
(id,project_id,revision_id,piece_key,stage_kind,asset_id,fingerprint,descriptor,publication_id,selected,created_at)
|
|
65
65
|
VALUES (?,?,?,?,?,?,?,?,?,0,?)`).run(recordId, revision.projectId, revision.id, piece.key, piece.stageKind, piece.assetId, piece.fingerprint, JSON.stringify(piece.piece), publicationId, revision.createdAt);
|
|
66
66
|
}
|
|
67
|
+
// Every narration join (intro, body, outro) any version of the project recorded.
|
|
68
|
+
export function narrationJoinsOfProject(db, projectId) {
|
|
69
|
+
return db
|
|
70
|
+
.prepare("SELECT * FROM revision_outputs WHERE project_id=? AND work_key IN ('audio:intro','audio:body:concat','audio:outro') ORDER BY created_at,id")
|
|
71
|
+
.all(projectId)
|
|
72
|
+
.map(toOutput);
|
|
73
|
+
}
|
|
74
|
+
// The finished pieces of those joins, for a project whose outputs and pieces carry different
|
|
75
|
+
// fingerprints (made before a fingerprint change).
|
|
76
|
+
export function narrationJoinPiecesOfProject(db, projectId) {
|
|
77
|
+
return db
|
|
78
|
+
.prepare("SELECT * FROM revision_pieces WHERE project_id=? AND piece_key IN ('audio:intro','audio:body:concat','audio:outro') ORDER BY created_at,id")
|
|
79
|
+
.all(projectId)
|
|
80
|
+
.map(toPiece);
|
|
81
|
+
}
|
|
67
82
|
export function outputsForRevision(db, projectId, revisionId) {
|
|
68
83
|
return db
|
|
69
84
|
.prepare("SELECT * FROM revision_outputs WHERE project_id=? AND revision_id=? ORDER BY created_at,id")
|
|
@@ -25,6 +25,7 @@ import { insertAsset, insertRevision } from "./repo.js";
|
|
|
25
25
|
import { bindNarrationSources, validateNarrationIntent, validateTemplateIntent } from "./rules.js";
|
|
26
26
|
import { revisionEditSchema } from "./schema.js";
|
|
27
27
|
import { keptSubject } from "./subject.js";
|
|
28
|
+
import { withEarlierJoins } from "./view.js";
|
|
28
29
|
export { bindUpload } from "./mutation-assets.js";
|
|
29
30
|
export { restoreRevision } from "./restore.js";
|
|
30
31
|
export async function saveRevision(deps, input) {
|
|
@@ -111,7 +112,7 @@ export async function saveRevision(deps, input) {
|
|
|
111
112
|
],
|
|
112
113
|
pieces: [],
|
|
113
114
|
};
|
|
114
|
-
let plan = planRevision(fresh, finalEdit, preparedManifest, inspected);
|
|
115
|
+
let plan = planRevision(withEarlierJoins(deps, fresh), finalEdit, preparedManifest, inspected);
|
|
115
116
|
if (!plan.ok)
|
|
116
117
|
return {
|
|
117
118
|
ok: false,
|
|
@@ -129,7 +130,7 @@ export async function saveRevision(deps, input) {
|
|
|
129
130
|
if (cuesChanged(fresh, finalEdit) && plan.content.subtitleCues !== undefined) {
|
|
130
131
|
const timing = plan.recipes.find((row) => row.key === "subtitles:timing");
|
|
131
132
|
if (timing !== undefined) {
|
|
132
|
-
plan = planRevision(fresh, {
|
|
133
|
+
plan = planRevision(withEarlierJoins(deps, fresh), {
|
|
133
134
|
...finalEdit,
|
|
134
135
|
// From the edit as sent, so the plan decides the kept shorts the same way again.
|
|
135
136
|
content: {
|
|
@@ -7,7 +7,7 @@ import { checkMutation, insertReceipt, refusal, requestHash, requiredView, } fro
|
|
|
7
7
|
import { advanceHead, logicalKeys } from "./mutations.js";
|
|
8
8
|
import { cloneManifest, ensureRevisionStages, projectSelected } from "./projection.js";
|
|
9
9
|
import { insertRevision } from "./repo.js";
|
|
10
|
-
import { getRevisionView } from "./view.js";
|
|
10
|
+
import { getRevisionView, withEarlierJoins } from "./view.js";
|
|
11
11
|
export async function restoreRevision(deps, input) {
|
|
12
12
|
const identity = {
|
|
13
13
|
...input,
|
|
@@ -24,7 +24,7 @@ export async function restoreRevision(deps, input) {
|
|
|
24
24
|
if (target === undefined)
|
|
25
25
|
return refusal(deps, input.projectId, "no-revision");
|
|
26
26
|
const base = requiredView(deps, input.projectId, input.baseRevisionId);
|
|
27
|
-
const comparison = planRevision(base, {
|
|
27
|
+
const comparison = planRevision(withEarlierJoins(deps, base), {
|
|
28
28
|
config: target.revision.config,
|
|
29
29
|
content: target.revision.content,
|
|
30
30
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { outputPath } from "../storage/layout.js";
|
|
4
|
+
import { narrationJoinPiecesOfProject, narrationJoinsOfProject } from "./manifest-repo.js";
|
|
4
5
|
import { currentRevisionId, outputsForRevision, piecesForRevision, revisionById } from "./repo.js";
|
|
5
6
|
export function getRevisionView(deps, projectId, revisionId) {
|
|
6
7
|
const revision = revisionById(deps.db, projectId, revisionId);
|
|
@@ -44,3 +45,13 @@ export function resolvedArticleMarkdown(deps, revision, outputs) {
|
|
|
44
45
|
? ""
|
|
45
46
|
: null;
|
|
46
47
|
}
|
|
48
|
+
// The view with the narration joins of every version of the project, for planning: turning
|
|
49
|
+
// softening on looks up the joins a finished word timing was made from
|
|
50
|
+
// (`rebuild/recipe-softening.ts`), which a later version no longer holds.
|
|
51
|
+
export function withEarlierJoins(deps, view) {
|
|
52
|
+
return {
|
|
53
|
+
...view,
|
|
54
|
+
earlierJoins: narrationJoinsOfProject(deps.db, view.revision.projectId),
|
|
55
|
+
earlierJoinPieces: narrationJoinPiecesOfProject(deps.db, view.revision.projectId),
|
|
56
|
+
};
|
|
57
|
+
}
|