@gentbajko/slopify 3.8.1 → 3.8.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/edge/http/studio.js +6 -4
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/patch-notes/3.8.2.md +6 -0
- package/dist/patch-notes/index.json +6 -0
- package/dist/slices/rebuild/narration-retry.js +19 -2
- package/dist/slices/studio/releases.js +12 -2
- package/package.json +1 -1
package/dist/edge/http/studio.js
CHANGED
|
@@ -162,17 +162,19 @@ export function studioRoutes(deps) {
|
|
|
162
162
|
};
|
|
163
163
|
};
|
|
164
164
|
// A project ready to upload gets its release times from the posting plan when its upload is
|
|
165
|
-
// prepared (`releases.ts`)
|
|
165
|
+
// prepared (`releases.ts`).
|
|
166
|
+
// A long video already on YouTube keeps when it went up; its shorts still get times.
|
|
166
167
|
const planned = (projectId) => {
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
const long = videoOf(deps.db, projectId, null);
|
|
169
|
+
const out = long?.uploadState === "done" ? long.recordedAt : undefined;
|
|
169
170
|
const result = uploadPack(deps, projectId);
|
|
170
|
-
if (!result.ok || result.pack.items[0]?.video == null)
|
|
171
|
+
if (!result.ok || (out === undefined && result.pack.items[0]?.video == null))
|
|
171
172
|
return;
|
|
172
173
|
planReleases(deps.db, readPlan(deps.db), {
|
|
173
174
|
id: projectId,
|
|
174
175
|
...whoOf(projectId, result.pack.series),
|
|
175
176
|
shorts: result.pack.items.filter((item) => item.kind === "short").length,
|
|
177
|
+
...(out === undefined ? {} : { longOut: out }),
|
|
176
178
|
}, deps.clock.now());
|
|
177
179
|
};
|
|
178
180
|
// Settings shows only the lines saved there; each schedule shows its own.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Slopify 3.8.2
|
|
2
|
+
|
|
3
|
+
Released 4 October 2026.
|
|
4
|
+
|
|
5
|
+
- **Shorts of a video already on YouTube get release times.** A project whose long video went up before it had a release time left its shorts with none, so the Studio extension had no schedule to type in. Now the long video keeps the time it went up as its release, and its shorts take the release line's short times from the upload lead on (never due sooner than you could upload them).
|
|
6
|
+
- **A narration chunk recorded again by itself no longer gets stuck.** When the voice garbled words, Slopify promised to record the chunk again and carry on, but if the project's last other step was just finishing, the retry waited for a step that never came, and the video stayed failed. It now looks again every 15 seconds until the project is free.
|
|
@@ -61,6 +61,13 @@ export function createNarrationRetries() {
|
|
|
61
61
|
continue;
|
|
62
62
|
running.add(id);
|
|
63
63
|
void recordAgain(deps, retry)
|
|
64
|
+
.then((waiting) => {
|
|
65
|
+
// The step that just finished may still count as running when it kicks this (the
|
|
66
|
+
// runner lets go of it after its `onFinished`), and nothing else finishes to kick
|
|
67
|
+
// again: look again shortly until the project is free.
|
|
68
|
+
if (waiting)
|
|
69
|
+
setTimeout(() => kick(retry.projectId), retryWaitMs).unref?.();
|
|
70
|
+
})
|
|
64
71
|
.catch((error) => {
|
|
65
72
|
deps.log.write("error", "narration.retry", {
|
|
66
73
|
projectId: retry.projectId,
|
|
@@ -86,11 +93,16 @@ export function createNarrationRetries() {
|
|
|
86
93
|
kick,
|
|
87
94
|
};
|
|
88
95
|
}
|
|
96
|
+
// How long a retry waiting for the project's other steps waits before it looks again.
|
|
97
|
+
export const retryWaitMs = 15_000;
|
|
98
|
+
// The waits already logged, so looking again doesn't log the same line every 15 seconds.
|
|
99
|
+
const told = new Set();
|
|
100
|
+
// True when the project's other steps are still going and the retry waits for them.
|
|
89
101
|
async function recordAgain(deps, retry) {
|
|
90
102
|
const base = currentRevisionId(deps.db, retry.projectId);
|
|
91
103
|
if (base === undefined) {
|
|
92
104
|
settle(deps.db, retry, "failed", "no project", deps.clock.now().toISOString());
|
|
93
|
-
return;
|
|
105
|
+
return false;
|
|
94
106
|
}
|
|
95
107
|
const result = await recoverProject(deps, retry.projectId, {
|
|
96
108
|
baseRevisionId: base,
|
|
@@ -106,6 +118,10 @@ async function recordAgain(deps, retry) {
|
|
|
106
118
|
});
|
|
107
119
|
}
|
|
108
120
|
else if (result.reason === "running") {
|
|
121
|
+
const key = retryKey(retry);
|
|
122
|
+
if (told.has(key))
|
|
123
|
+
return true;
|
|
124
|
+
told.add(key);
|
|
109
125
|
// Another step of the project is still going (its PDF, say): the retry waits for it and
|
|
110
126
|
// starts when that step finishes, which kicks this again (`onFinished`). Giving up here
|
|
111
127
|
// left the video failed with a message promising a retry that never came.
|
|
@@ -113,7 +129,7 @@ async function recordAgain(deps, retry) {
|
|
|
113
129
|
projectId: retry.projectId,
|
|
114
130
|
detail: `Recording ${retry.chunkKey} again once the project's running steps finish.`,
|
|
115
131
|
});
|
|
116
|
-
return;
|
|
132
|
+
return true;
|
|
117
133
|
}
|
|
118
134
|
else {
|
|
119
135
|
settle(deps.db, retry, "failed", refusal(result), now);
|
|
@@ -123,6 +139,7 @@ async function recordAgain(deps, retry) {
|
|
|
123
139
|
});
|
|
124
140
|
}
|
|
125
141
|
deps.emit(retry.projectId, { type: "project.updated", projectId: retry.projectId });
|
|
142
|
+
return false;
|
|
126
143
|
}
|
|
127
144
|
function refusal(result) {
|
|
128
145
|
const detail = result.fields?.map((field) => field.message).join(" ");
|
|
@@ -84,6 +84,14 @@ export function freeSlots(db, plan, who, now, count, exceptProject) {
|
|
|
84
84
|
export function planReleases(db, plan, project, now) {
|
|
85
85
|
let releases = releasesOf(db, project.id);
|
|
86
86
|
let long = releases.find((release) => release.short === 0);
|
|
87
|
+
if (long === undefined && project.longOut !== undefined) {
|
|
88
|
+
const line = plan.rows.find((one) => one.schedule !== undefined && fits(one, project) && one.runDay === project.runDay) ?? plan.rows.find((one) => fits(one, project));
|
|
89
|
+
if (line === undefined)
|
|
90
|
+
return;
|
|
91
|
+
long = { short: 0, at: project.longOut, line: line.name, by: "plan" };
|
|
92
|
+
write(db, project.id, long, now);
|
|
93
|
+
releases = releasesOf(db, project.id);
|
|
94
|
+
}
|
|
87
95
|
if (long === undefined) {
|
|
88
96
|
// The line of the day its schedule ran comes first, then any line that takes it.
|
|
89
97
|
const slots = freeSlots(db, plan, project, now, 30, project.id);
|
|
@@ -108,10 +116,12 @@ export function planReleases(db, plan, project, now) {
|
|
|
108
116
|
if (release.at !== "")
|
|
109
117
|
taken.add(Math.floor(Date.parse(release.at) / hourMs));
|
|
110
118
|
const longAt = new Date(long.at);
|
|
119
|
+
// A short is never due before it could be uploaded in time.
|
|
120
|
+
const start = Math.max(longAt.getTime(), now.getTime() + readLeadHours(db) * hourMs);
|
|
111
121
|
const times = line.shorts
|
|
112
|
-
.flatMap((slot) => zonedTimes(zoneOf(plan, line), slot,
|
|
122
|
+
.flatMap((slot) => zonedTimes(zoneOf(plan, line), slot, new Date(start), new Date(start + 5 * 7 * 24 * hourMs)))
|
|
113
123
|
.toSorted((left, right) => left.getTime() - right.getTime());
|
|
114
|
-
let after =
|
|
124
|
+
let after = start;
|
|
115
125
|
for (let short = 1; short <= project.shorts; short += 1) {
|
|
116
126
|
const kept = releases.find((release) => release.short === short);
|
|
117
127
|
if (kept !== undefined) {
|