@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.js
CHANGED
|
@@ -9903,6 +9903,10 @@ async function getFileInfo(uri) {
|
|
|
9903
9903
|
found = true;
|
|
9904
9904
|
controller.abort();
|
|
9905
9905
|
const track = info.videoTracks[0];
|
|
9906
|
+
if (!track) {
|
|
9907
|
+
rej("No video track found");
|
|
9908
|
+
return;
|
|
9909
|
+
}
|
|
9906
9910
|
const config = {
|
|
9907
9911
|
// Browser doesn't support parsing full vp8 codec (eg: `vp08.00.41.08`),
|
|
9908
9912
|
// they only support `vp8`.
|
|
@@ -9933,25 +9937,35 @@ async function getFileInfo(uri) {
|
|
|
9933
9937
|
file.start();
|
|
9934
9938
|
res({ file, edits, config });
|
|
9935
9939
|
};
|
|
9940
|
+
file.onError = (e) => rej(e);
|
|
9936
9941
|
return fetch(uri, { signal: controller.signal }).then(async (response) => {
|
|
9942
|
+
if (!response.ok) {
|
|
9943
|
+
rej(`HTTP ${response.status}: ${response.statusText}`);
|
|
9944
|
+
return;
|
|
9945
|
+
}
|
|
9937
9946
|
if (!response.body) {
|
|
9938
|
-
|
|
9947
|
+
rej("Response body is null");
|
|
9948
|
+
return;
|
|
9939
9949
|
}
|
|
9940
9950
|
const reader = response.body.getReader();
|
|
9941
9951
|
const sink = new MP4FileSink(file, () => {
|
|
9942
9952
|
});
|
|
9953
|
+
let bytesRead = 0;
|
|
9954
|
+
let chunks = 0;
|
|
9943
9955
|
while (!found) {
|
|
9944
9956
|
await reader.read().then(({ done, value }) => {
|
|
9945
9957
|
if (done) {
|
|
9946
9958
|
file.flush();
|
|
9947
9959
|
controller.abort();
|
|
9948
|
-
rej("Could not find moov");
|
|
9960
|
+
if (!found) rej("Could not find moov box in video file");
|
|
9949
9961
|
return;
|
|
9950
9962
|
}
|
|
9963
|
+
bytesRead += value.byteLength;
|
|
9964
|
+
chunks++;
|
|
9951
9965
|
sink.write(value);
|
|
9952
9966
|
});
|
|
9953
9967
|
}
|
|
9954
|
-
});
|
|
9968
|
+
}).catch((err) => rej(err));
|
|
9955
9969
|
});
|
|
9956
9970
|
}
|
|
9957
9971
|
var Mp4Parser = class {
|
|
@@ -9975,9 +9989,7 @@ var Mp4Parser = class {
|
|
|
9975
9989
|
const segmentDurationInSeconds = this.getSecondDurationOfSegment(
|
|
9976
9990
|
this.edits[this.nextSegment]
|
|
9977
9991
|
);
|
|
9978
|
-
if (startTimeWithinSegment < segmentDurationInSeconds)
|
|
9979
|
-
break;
|
|
9980
|
-
}
|
|
9992
|
+
if (startTimeWithinSegment < segmentDurationInSeconds) break;
|
|
9981
9993
|
startTimeWithinSegment -= segmentDurationInSeconds;
|
|
9982
9994
|
this.nextSegment++;
|
|
9983
9995
|
}
|
|
@@ -10069,8 +10081,94 @@ var Mp4Parser = class {
|
|
|
10069
10081
|
}
|
|
10070
10082
|
};
|
|
10071
10083
|
|
|
10084
|
+
// src/lib/utils/video/html-video-extractor.ts
|
|
10085
|
+
var HTMLVideoFrameExtractor = class {
|
|
10086
|
+
constructor(src) {
|
|
10087
|
+
this.src = src;
|
|
10088
|
+
this.lastTime = -1;
|
|
10089
|
+
this.video = document.createElement("video");
|
|
10090
|
+
this.video.crossOrigin = "anonymous";
|
|
10091
|
+
this.video.preload = "auto";
|
|
10092
|
+
this.video.src = src;
|
|
10093
|
+
this.video.muted = true;
|
|
10094
|
+
this.canvas = document.createElement("canvas");
|
|
10095
|
+
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
|
|
10096
|
+
}
|
|
10097
|
+
async start() {
|
|
10098
|
+
return new Promise((resolve, reject) => {
|
|
10099
|
+
const timeout = setTimeout(() => {
|
|
10100
|
+
reject(new Error("Timeout loading video metadata"));
|
|
10101
|
+
}, 3e4);
|
|
10102
|
+
this.video.addEventListener("loadedmetadata", () => {
|
|
10103
|
+
clearTimeout(timeout);
|
|
10104
|
+
this.canvas.width = this.video.videoWidth;
|
|
10105
|
+
this.canvas.height = this.video.videoHeight;
|
|
10106
|
+
resolve();
|
|
10107
|
+
}, { once: true });
|
|
10108
|
+
this.video.addEventListener("error", () => {
|
|
10109
|
+
clearTimeout(timeout);
|
|
10110
|
+
reject(new Error("Failed to load video"));
|
|
10111
|
+
}, { once: true });
|
|
10112
|
+
});
|
|
10113
|
+
}
|
|
10114
|
+
async getFrameAt(time) {
|
|
10115
|
+
if (Math.abs(this.video.currentTime - time) > 0.016) {
|
|
10116
|
+
await this.seekTo(time);
|
|
10117
|
+
}
|
|
10118
|
+
this.ctx.drawImage(this.video, 0, 0);
|
|
10119
|
+
return createImageBitmap(this.canvas);
|
|
10120
|
+
}
|
|
10121
|
+
async seekTo(time) {
|
|
10122
|
+
return new Promise((resolve, reject) => {
|
|
10123
|
+
const timeout = setTimeout(() => {
|
|
10124
|
+
reject(new Error(`Seek timeout at ${time}s`));
|
|
10125
|
+
}, 5e3);
|
|
10126
|
+
const onSeeked = () => {
|
|
10127
|
+
clearTimeout(timeout);
|
|
10128
|
+
this.video.removeEventListener("seeked", onSeeked);
|
|
10129
|
+
resolve();
|
|
10130
|
+
};
|
|
10131
|
+
this.video.addEventListener("seeked", onSeeked, { once: true });
|
|
10132
|
+
this.video.currentTime = time;
|
|
10133
|
+
});
|
|
10134
|
+
}
|
|
10135
|
+
getDuration() {
|
|
10136
|
+
return this.video.duration || 0;
|
|
10137
|
+
}
|
|
10138
|
+
getTime() {
|
|
10139
|
+
return this.video.currentTime;
|
|
10140
|
+
}
|
|
10141
|
+
getLastTime() {
|
|
10142
|
+
return this.lastTime;
|
|
10143
|
+
}
|
|
10144
|
+
close() {
|
|
10145
|
+
this.video.src = "";
|
|
10146
|
+
this.video.load();
|
|
10147
|
+
}
|
|
10148
|
+
};
|
|
10149
|
+
var htmlVideoExtractors = /* @__PURE__ */ new Map();
|
|
10150
|
+
async function getFrameHTML(id, filePath, time, fps) {
|
|
10151
|
+
const extractorId = filePath + "-" + id;
|
|
10152
|
+
let extractor = htmlVideoExtractors.get(extractorId);
|
|
10153
|
+
if (!extractor) {
|
|
10154
|
+
extractor = new HTMLVideoFrameExtractor(filePath);
|
|
10155
|
+
await extractor.start();
|
|
10156
|
+
htmlVideoExtractors.set(extractorId, extractor);
|
|
10157
|
+
}
|
|
10158
|
+
return extractor.getFrameAt(time);
|
|
10159
|
+
}
|
|
10160
|
+
function dropHTMLExtractor(id, filePath) {
|
|
10161
|
+
const extractorId = filePath + "-" + id;
|
|
10162
|
+
const extractor = htmlVideoExtractors.get(extractorId);
|
|
10163
|
+
if (extractor) {
|
|
10164
|
+
extractor.close();
|
|
10165
|
+
htmlVideoExtractors.delete(extractorId);
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
|
|
10072
10169
|
// src/lib/utils/video/mp4-parser-manager.ts
|
|
10073
10170
|
var videoFrameExtractors = /* @__PURE__ */ new Map();
|
|
10171
|
+
var htmlFallbackVideos = /* @__PURE__ */ new Set();
|
|
10074
10172
|
async function dropExtractor(id, filePath) {
|
|
10075
10173
|
const extractorId = filePath + "-" + id;
|
|
10076
10174
|
const extractor = videoFrameExtractors.get(extractorId);
|
|
@@ -10078,9 +10176,16 @@ async function dropExtractor(id, filePath) {
|
|
|
10078
10176
|
extractor.close();
|
|
10079
10177
|
videoFrameExtractors.delete(extractorId);
|
|
10080
10178
|
}
|
|
10179
|
+
if (htmlFallbackVideos.has(extractorId)) {
|
|
10180
|
+
dropHTMLExtractor(id, filePath);
|
|
10181
|
+
htmlFallbackVideos.delete(extractorId);
|
|
10182
|
+
}
|
|
10081
10183
|
}
|
|
10082
10184
|
async function getFrame(id, filePath, time, fps) {
|
|
10083
10185
|
const extractorId = filePath + "-" + id;
|
|
10186
|
+
if (htmlFallbackVideos.has(extractorId)) {
|
|
10187
|
+
return getFrameHTML(id, filePath, time, fps);
|
|
10188
|
+
}
|
|
10084
10189
|
let extractor = videoFrameExtractors.get(extractorId);
|
|
10085
10190
|
const frameDuration = 1 / fps;
|
|
10086
10191
|
const duration = extractor?.getDuration();
|
|
@@ -10090,9 +10195,7 @@ async function getFrame(id, filePath, time, fps) {
|
|
|
10090
10195
|
const isOldFrame = extractor && Math.abs(time - extractor.getLastTime()) < frameDuration / 2;
|
|
10091
10196
|
if (extractor && isOldFrame) {
|
|
10092
10197
|
const lastFrame = extractor.getLastFrame();
|
|
10093
|
-
if (!lastFrame)
|
|
10094
|
-
throw new Error("No last frame");
|
|
10095
|
-
}
|
|
10198
|
+
if (!lastFrame) throw new Error("No last frame");
|
|
10096
10199
|
return lastFrame;
|
|
10097
10200
|
}
|
|
10098
10201
|
if (extractor && time + frameDuration < extractor.getTime()) {
|
|
@@ -10107,7 +10210,22 @@ async function getFrame(id, filePath, time, fps) {
|
|
|
10107
10210
|
}
|
|
10108
10211
|
if (!extractor) {
|
|
10109
10212
|
extractor = new Mp4Parser(filePath, fps, time);
|
|
10110
|
-
|
|
10213
|
+
try {
|
|
10214
|
+
await extractor.start();
|
|
10215
|
+
} catch {
|
|
10216
|
+
htmlFallbackVideos.add(extractorId);
|
|
10217
|
+
return getFrameHTML(id, filePath, time, fps);
|
|
10218
|
+
}
|
|
10219
|
+
const duration2 = extractor.getDuration();
|
|
10220
|
+
if (duration2 === 0) {
|
|
10221
|
+
extractor.close();
|
|
10222
|
+
videoFrameExtractors.delete(extractorId);
|
|
10223
|
+
htmlFallbackVideos.add(extractorId);
|
|
10224
|
+
return getFrameHTML(id, filePath, time, fps);
|
|
10225
|
+
}
|
|
10226
|
+
if (duration2 > 0 && time > duration2) {
|
|
10227
|
+
throw new Error(`Requested time ${time}s exceeds video duration ${duration2}s`);
|
|
10228
|
+
}
|
|
10111
10229
|
videoFrameExtractors.set(extractorId, extractor);
|
|
10112
10230
|
}
|
|
10113
10231
|
return extractor.getNextFrame();
|