@twick/2d 0.15.7 → 0.15.8
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/index.cjs +128 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +128 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -9944,6 +9944,10 @@ async function getFileInfo(uri) {
|
|
|
9944
9944
|
found = true;
|
|
9945
9945
|
controller.abort();
|
|
9946
9946
|
const track = info.videoTracks[0];
|
|
9947
|
+
if (!track) {
|
|
9948
|
+
rej("No video track found");
|
|
9949
|
+
return;
|
|
9950
|
+
}
|
|
9947
9951
|
const config = {
|
|
9948
9952
|
// Browser doesn't support parsing full vp8 codec (eg: `vp08.00.41.08`),
|
|
9949
9953
|
// they only support `vp8`.
|
|
@@ -9974,25 +9978,35 @@ async function getFileInfo(uri) {
|
|
|
9974
9978
|
file.start();
|
|
9975
9979
|
res({ file, edits, config });
|
|
9976
9980
|
};
|
|
9981
|
+
file.onError = (e) => rej(e);
|
|
9977
9982
|
return fetch(uri, { signal: controller.signal }).then(async (response) => {
|
|
9983
|
+
if (!response.ok) {
|
|
9984
|
+
rej(`HTTP ${response.status}: ${response.statusText}`);
|
|
9985
|
+
return;
|
|
9986
|
+
}
|
|
9978
9987
|
if (!response.body) {
|
|
9979
|
-
|
|
9988
|
+
rej("Response body is null");
|
|
9989
|
+
return;
|
|
9980
9990
|
}
|
|
9981
9991
|
const reader = response.body.getReader();
|
|
9982
9992
|
const sink = new MP4FileSink(file, () => {
|
|
9983
9993
|
});
|
|
9994
|
+
let bytesRead = 0;
|
|
9995
|
+
let chunks = 0;
|
|
9984
9996
|
while (!found) {
|
|
9985
9997
|
await reader.read().then(({ done, value }) => {
|
|
9986
9998
|
if (done) {
|
|
9987
9999
|
file.flush();
|
|
9988
10000
|
controller.abort();
|
|
9989
|
-
rej("Could not find moov");
|
|
10001
|
+
if (!found) rej("Could not find moov box in video file");
|
|
9990
10002
|
return;
|
|
9991
10003
|
}
|
|
10004
|
+
bytesRead += value.byteLength;
|
|
10005
|
+
chunks++;
|
|
9992
10006
|
sink.write(value);
|
|
9993
10007
|
});
|
|
9994
10008
|
}
|
|
9995
|
-
});
|
|
10009
|
+
}).catch((err) => rej(err));
|
|
9996
10010
|
});
|
|
9997
10011
|
}
|
|
9998
10012
|
var Mp4Parser = class {
|
|
@@ -10016,9 +10030,7 @@ var Mp4Parser = class {
|
|
|
10016
10030
|
const segmentDurationInSeconds = this.getSecondDurationOfSegment(
|
|
10017
10031
|
this.edits[this.nextSegment]
|
|
10018
10032
|
);
|
|
10019
|
-
if (startTimeWithinSegment < segmentDurationInSeconds)
|
|
10020
|
-
break;
|
|
10021
|
-
}
|
|
10033
|
+
if (startTimeWithinSegment < segmentDurationInSeconds) break;
|
|
10022
10034
|
startTimeWithinSegment -= segmentDurationInSeconds;
|
|
10023
10035
|
this.nextSegment++;
|
|
10024
10036
|
}
|
|
@@ -10110,8 +10122,94 @@ var Mp4Parser = class {
|
|
|
10110
10122
|
}
|
|
10111
10123
|
};
|
|
10112
10124
|
|
|
10125
|
+
// src/lib/utils/video/html-video-extractor.ts
|
|
10126
|
+
var HTMLVideoFrameExtractor = class {
|
|
10127
|
+
constructor(src) {
|
|
10128
|
+
this.src = src;
|
|
10129
|
+
this.lastTime = -1;
|
|
10130
|
+
this.video = document.createElement("video");
|
|
10131
|
+
this.video.crossOrigin = "anonymous";
|
|
10132
|
+
this.video.preload = "auto";
|
|
10133
|
+
this.video.src = src;
|
|
10134
|
+
this.video.muted = true;
|
|
10135
|
+
this.canvas = document.createElement("canvas");
|
|
10136
|
+
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
|
|
10137
|
+
}
|
|
10138
|
+
async start() {
|
|
10139
|
+
return new Promise((resolve, reject) => {
|
|
10140
|
+
const timeout = setTimeout(() => {
|
|
10141
|
+
reject(new Error("Timeout loading video metadata"));
|
|
10142
|
+
}, 3e4);
|
|
10143
|
+
this.video.addEventListener("loadedmetadata", () => {
|
|
10144
|
+
clearTimeout(timeout);
|
|
10145
|
+
this.canvas.width = this.video.videoWidth;
|
|
10146
|
+
this.canvas.height = this.video.videoHeight;
|
|
10147
|
+
resolve();
|
|
10148
|
+
}, { once: true });
|
|
10149
|
+
this.video.addEventListener("error", () => {
|
|
10150
|
+
clearTimeout(timeout);
|
|
10151
|
+
reject(new Error("Failed to load video"));
|
|
10152
|
+
}, { once: true });
|
|
10153
|
+
});
|
|
10154
|
+
}
|
|
10155
|
+
async getFrameAt(time) {
|
|
10156
|
+
if (Math.abs(this.video.currentTime - time) > 0.016) {
|
|
10157
|
+
await this.seekTo(time);
|
|
10158
|
+
}
|
|
10159
|
+
this.ctx.drawImage(this.video, 0, 0);
|
|
10160
|
+
return createImageBitmap(this.canvas);
|
|
10161
|
+
}
|
|
10162
|
+
async seekTo(time) {
|
|
10163
|
+
return new Promise((resolve, reject) => {
|
|
10164
|
+
const timeout = setTimeout(() => {
|
|
10165
|
+
reject(new Error(`Seek timeout at ${time}s`));
|
|
10166
|
+
}, 5e3);
|
|
10167
|
+
const onSeeked = () => {
|
|
10168
|
+
clearTimeout(timeout);
|
|
10169
|
+
this.video.removeEventListener("seeked", onSeeked);
|
|
10170
|
+
resolve();
|
|
10171
|
+
};
|
|
10172
|
+
this.video.addEventListener("seeked", onSeeked, { once: true });
|
|
10173
|
+
this.video.currentTime = time;
|
|
10174
|
+
});
|
|
10175
|
+
}
|
|
10176
|
+
getDuration() {
|
|
10177
|
+
return this.video.duration || 0;
|
|
10178
|
+
}
|
|
10179
|
+
getTime() {
|
|
10180
|
+
return this.video.currentTime;
|
|
10181
|
+
}
|
|
10182
|
+
getLastTime() {
|
|
10183
|
+
return this.lastTime;
|
|
10184
|
+
}
|
|
10185
|
+
close() {
|
|
10186
|
+
this.video.src = "";
|
|
10187
|
+
this.video.load();
|
|
10188
|
+
}
|
|
10189
|
+
};
|
|
10190
|
+
var htmlVideoExtractors = /* @__PURE__ */ new Map();
|
|
10191
|
+
async function getFrameHTML(id, filePath, time, fps) {
|
|
10192
|
+
const extractorId = filePath + "-" + id;
|
|
10193
|
+
let extractor = htmlVideoExtractors.get(extractorId);
|
|
10194
|
+
if (!extractor) {
|
|
10195
|
+
extractor = new HTMLVideoFrameExtractor(filePath);
|
|
10196
|
+
await extractor.start();
|
|
10197
|
+
htmlVideoExtractors.set(extractorId, extractor);
|
|
10198
|
+
}
|
|
10199
|
+
return extractor.getFrameAt(time);
|
|
10200
|
+
}
|
|
10201
|
+
function dropHTMLExtractor(id, filePath) {
|
|
10202
|
+
const extractorId = filePath + "-" + id;
|
|
10203
|
+
const extractor = htmlVideoExtractors.get(extractorId);
|
|
10204
|
+
if (extractor) {
|
|
10205
|
+
extractor.close();
|
|
10206
|
+
htmlVideoExtractors.delete(extractorId);
|
|
10207
|
+
}
|
|
10208
|
+
}
|
|
10209
|
+
|
|
10113
10210
|
// src/lib/utils/video/mp4-parser-manager.ts
|
|
10114
10211
|
var videoFrameExtractors = /* @__PURE__ */ new Map();
|
|
10212
|
+
var htmlFallbackVideos = /* @__PURE__ */ new Set();
|
|
10115
10213
|
async function dropExtractor(id, filePath) {
|
|
10116
10214
|
const extractorId = filePath + "-" + id;
|
|
10117
10215
|
const extractor = videoFrameExtractors.get(extractorId);
|
|
@@ -10119,9 +10217,16 @@ async function dropExtractor(id, filePath) {
|
|
|
10119
10217
|
extractor.close();
|
|
10120
10218
|
videoFrameExtractors.delete(extractorId);
|
|
10121
10219
|
}
|
|
10220
|
+
if (htmlFallbackVideos.has(extractorId)) {
|
|
10221
|
+
dropHTMLExtractor(id, filePath);
|
|
10222
|
+
htmlFallbackVideos.delete(extractorId);
|
|
10223
|
+
}
|
|
10122
10224
|
}
|
|
10123
10225
|
async function getFrame(id, filePath, time, fps) {
|
|
10124
10226
|
const extractorId = filePath + "-" + id;
|
|
10227
|
+
if (htmlFallbackVideos.has(extractorId)) {
|
|
10228
|
+
return getFrameHTML(id, filePath, time, fps);
|
|
10229
|
+
}
|
|
10125
10230
|
let extractor = videoFrameExtractors.get(extractorId);
|
|
10126
10231
|
const frameDuration = 1 / fps;
|
|
10127
10232
|
const duration = extractor?.getDuration();
|
|
@@ -10131,9 +10236,7 @@ async function getFrame(id, filePath, time, fps) {
|
|
|
10131
10236
|
const isOldFrame = extractor && Math.abs(time - extractor.getLastTime()) < frameDuration / 2;
|
|
10132
10237
|
if (extractor && isOldFrame) {
|
|
10133
10238
|
const lastFrame = extractor.getLastFrame();
|
|
10134
|
-
if (!lastFrame)
|
|
10135
|
-
throw new Error("No last frame");
|
|
10136
|
-
}
|
|
10239
|
+
if (!lastFrame) throw new Error("No last frame");
|
|
10137
10240
|
return lastFrame;
|
|
10138
10241
|
}
|
|
10139
10242
|
if (extractor && time + frameDuration < extractor.getTime()) {
|
|
@@ -10148,7 +10251,22 @@ async function getFrame(id, filePath, time, fps) {
|
|
|
10148
10251
|
}
|
|
10149
10252
|
if (!extractor) {
|
|
10150
10253
|
extractor = new Mp4Parser(filePath, fps, time);
|
|
10151
|
-
|
|
10254
|
+
try {
|
|
10255
|
+
await extractor.start();
|
|
10256
|
+
} catch {
|
|
10257
|
+
htmlFallbackVideos.add(extractorId);
|
|
10258
|
+
return getFrameHTML(id, filePath, time, fps);
|
|
10259
|
+
}
|
|
10260
|
+
const duration2 = extractor.getDuration();
|
|
10261
|
+
if (duration2 === 0) {
|
|
10262
|
+
extractor.close();
|
|
10263
|
+
videoFrameExtractors.delete(extractorId);
|
|
10264
|
+
htmlFallbackVideos.add(extractorId);
|
|
10265
|
+
return getFrameHTML(id, filePath, time, fps);
|
|
10266
|
+
}
|
|
10267
|
+
if (duration2 > 0 && time > duration2) {
|
|
10268
|
+
throw new Error(`Requested time ${time}s exceeds video duration ${duration2}s`);
|
|
10269
|
+
}
|
|
10152
10270
|
videoFrameExtractors.set(extractorId, extractor);
|
|
10153
10271
|
}
|
|
10154
10272
|
return extractor.getNextFrame();
|