@gentbajko/slopify 3.10.0 → 3.10.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/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/patch-notes/3.10.1.md +18 -0
- package/dist/patch-notes/3.10.2.md +5 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/rebuild/recipe-build.js +29 -0
- package/dist/slices/rebuild/recipe-edit.js +2 -1
- package/dist/slices/rebuild/recipe-save.js +1 -0
- package/dist/slices/rebuild/recipe-work.js +1 -0
- package/dist/slices/studio/backfill.js +8 -1
- package/dist/web/assets/{index-CW_6Ho-M.js → index-CGwlNEwD.js} +115 -115
- package/dist/web/assets/{pdf-C4K73cRQ.js → pdf-3nEY4HYq.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Slopify 3.10.1
|
|
2
|
+
|
|
3
|
+
Released 4 October 2026.
|
|
4
|
+
|
|
5
|
+
## YouTube Studio extension (1.2.2)
|
|
6
|
+
|
|
7
|
+
- **The schedule date sticks.** Studio stopped taking the date the extension typed on the Visibility step ("Studio didn't keep the date Slopify typed"). The extension now commits the date the way pressing Enter does, so the date and time are both set and you only press Schedule.
|
|
8
|
+
- **Shorts playlists.** Settings → Publishing → YouTube Studio warns when a playlist named for shorts is set to Videos and shorts: with that setting every short also went into your long-video playlists. Set it to Shorts only, and the long-video ones to Long videos only.
|
|
9
|
+
|
|
10
|
+
## Opening a long project
|
|
11
|
+
|
|
12
|
+
- **No more freeze when opening a finished long project.** Working out which narration chunk each transcript line came from (for the pencil beside each line) re-read every chunk for every line: a two-hour project froze the page for about 7 seconds. It now takes a few milliseconds.
|
|
13
|
+
|
|
14
|
+
## Upload status
|
|
15
|
+
|
|
16
|
+
- **One Studio video belongs to one upload.** When two projects have a short with the same title, the video Studio lists under that title was ticked as uploaded on both. Now a video already linked to a project's upload is never given to another.
|
|
17
|
+
|
|
18
|
+
Download the extension again from Settings → YouTube Studio and reload it.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Slopify 3.10.2
|
|
2
|
+
|
|
3
|
+
Released 6 October 2026.
|
|
4
|
+
|
|
5
|
+
- **Writing the YouTube description again no longer remakes the video.** A video with chapter cards draws them from the description, so writing the description again (YouTube → Write again) used to render the whole video again. A finished video now keeps the description it was drawn from: only the description, tags, pinned comment and titles are written again. The video is still remade when something it shows changes (its images, narration, captions or edit settings), and then its chapter cards follow the current description.
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"id": "3.10.2",
|
|
4
|
+
"title": "Slopify 3.10.2",
|
|
5
|
+
"version": "3.10.2",
|
|
6
|
+
"date": "2026-10-06"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "3.10.1",
|
|
10
|
+
"title": "Slopify 3.10.1",
|
|
11
|
+
"version": "3.10.1",
|
|
12
|
+
"date": "2026-10-04"
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"id": "3.10.0",
|
|
4
16
|
"title": "Slopify 3.10.0",
|
|
@@ -20,7 +20,36 @@ function withLines(master, lines) {
|
|
|
20
20
|
? master
|
|
21
21
|
: { values: [...master.values, ...lines.values], keys: [...master.keys, ...lines.keys] };
|
|
22
22
|
}
|
|
23
|
+
// Every recipe of a version. A finished video whose only change is a description written again
|
|
24
|
+
// keeps the description its chapter cards were drawn from: the video is never remade for a new
|
|
25
|
+
// description, only when what it shows changes.
|
|
23
26
|
export function buildRecipes(context) {
|
|
27
|
+
const built = buildEach(context);
|
|
28
|
+
const video = built.find((value) => value.key === "export:video");
|
|
29
|
+
const rendered = new Set(context.manifest.pieces
|
|
30
|
+
.filter((one) => one.key === "export:video" && one.piece.state === "done")
|
|
31
|
+
.map((one) => one.fingerprint));
|
|
32
|
+
if (video === undefined || rendered.size === 0 || rendered.has(video.fingerprint))
|
|
33
|
+
return built;
|
|
34
|
+
if (!video.dependsOn.includes(descriptionKey))
|
|
35
|
+
return built;
|
|
36
|
+
const past = context.history ?? context.manifest;
|
|
37
|
+
const fingerprints = new Set(past.pieces.filter((one) => one.key === descriptionKey).map((one) => one.fingerprint));
|
|
38
|
+
const assets = new Set([
|
|
39
|
+
null,
|
|
40
|
+
...past.outputs.filter((one) => one.workKey === descriptionKey).map((one) => one.assetId),
|
|
41
|
+
...past.pieces.filter((one) => one.key === descriptionKey).map((one) => one.assetId),
|
|
42
|
+
]);
|
|
43
|
+
for (const described of fingerprints)
|
|
44
|
+
for (const asset of assets) {
|
|
45
|
+
const kept = buildEach({ ...context, pinnedChapters: [described, asset] }).find((value) => value.key === "export:video");
|
|
46
|
+
if (kept !== undefined && rendered.has(kept.fingerprint))
|
|
47
|
+
return built.map((value) => (value.key === "export:video" ? kept : value));
|
|
48
|
+
}
|
|
49
|
+
return built;
|
|
50
|
+
}
|
|
51
|
+
const descriptionKey = "youtube:description";
|
|
52
|
+
function buildEach(context) {
|
|
24
53
|
const text = textRecipes(context);
|
|
25
54
|
const audio = audioRecipes(context, text);
|
|
26
55
|
const exports = exportRecipes(context, audio);
|
|
@@ -49,7 +49,8 @@ export function editPlan(context, exports, youtube, images, cards = []) {
|
|
|
49
49
|
values.push(["timing", resourceIdentity(context, timing)]);
|
|
50
50
|
dependsOn.push(timing.key);
|
|
51
51
|
if (fromDescription && description !== undefined) {
|
|
52
|
-
|
|
52
|
+
// A finished video keeps the description it was drawn from (`recipe-build.ts`).
|
|
53
|
+
values.push(["chapters", context.pinnedChapters ?? resourceIdentity(context, description)]);
|
|
53
54
|
dependsOn.push(description.key);
|
|
54
55
|
}
|
|
55
56
|
else
|
|
@@ -83,6 +83,7 @@ export function planRevision(base, edit, prepared = { outputs: [], pieces: [] },
|
|
|
83
83
|
config: base.revision.config,
|
|
84
84
|
content: base.revision.content,
|
|
85
85
|
manifest,
|
|
86
|
+
history: { outputs: base.outputs, pieces: base.pieces },
|
|
86
87
|
resolved: {
|
|
87
88
|
articleMarkdown: base.articleMarkdown,
|
|
88
89
|
researchNotes: base.revision.config.provided.research ?? null,
|
|
@@ -30,6 +30,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
|
|
|
30
30
|
config: revision.config,
|
|
31
31
|
content: revision.content,
|
|
32
32
|
manifest: available,
|
|
33
|
+
history: manifest,
|
|
33
34
|
resolved: { ...resolved, articleMarkdown: usableArticle ? resolved.articleMarkdown : null },
|
|
34
35
|
};
|
|
35
36
|
const logical = buildRecipes(logicalContext);
|
|
@@ -8,6 +8,12 @@ export function backfillVideos(deps, rows, at) {
|
|
|
8
8
|
byTitle.set(norm(row.title), row.videoId);
|
|
9
9
|
if (byTitle.size === 0)
|
|
10
10
|
return 0;
|
|
11
|
+
// A video already linked to an upload is never claimed by another: two projects can have a
|
|
12
|
+
// short with the same title (a remake of a topic), and only one of them made that video.
|
|
13
|
+
const taken = new Set(deps.db
|
|
14
|
+
.prepare("SELECT video_id FROM youtube_videos")
|
|
15
|
+
.all()
|
|
16
|
+
.map((row) => String(row.video_id)));
|
|
11
17
|
let found = 0;
|
|
12
18
|
for (const project of listProjects(deps.db)) {
|
|
13
19
|
const known = projectVideos(deps.db, project.id).filter((one) => one.uploadState === "done");
|
|
@@ -20,9 +26,10 @@ export function backfillVideos(deps, rows, at) {
|
|
|
20
26
|
continue;
|
|
21
27
|
const titles = item.pickable?.titles ?? [item.title, ...item.titles];
|
|
22
28
|
const videoId = titles.map((one) => byTitle.get(norm(one))).find((one) => one !== undefined);
|
|
23
|
-
if (videoId === undefined)
|
|
29
|
+
if (videoId === undefined || taken.has(videoId))
|
|
24
30
|
continue;
|
|
25
31
|
recordVideo(deps.db, project.id, short, videoId, at);
|
|
32
|
+
taken.add(videoId);
|
|
26
33
|
found += 1;
|
|
27
34
|
}
|
|
28
35
|
}
|