Published
Edited
Nov 4, 2021
108 forks
32 stars
Insert cell
Insert cell
viewof videoFile = html`<input type="file" accept=".mp4,.mov,.webm" />`
Insert cell
viewof inputFPS = html`<input type=number value=30 />`
Insert cell
maxFrameCountGuess = Math.round(duration * inputFPS)
Insert cell
outputWidth = Math.floor(options.outputScale * videoWidth)
Insert cell
outputHeight = Math.floor(options.outputScale * videoHeight)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
previewImage = html`<div>${previewScrub}</div><img src="${frames[previewScrub].data}">`
Insert cell
Insert cell
videoEl = {
const videoEl = html`<video src="${URL.createObjectURL(
videoFile
)}" controls style="max-width: 100%;" />`;

videoEl.addEventListener('loadedmetadata', function(event) {
mutable videoWidth = videoEl.videoWidth;
mutable videoHeight = videoEl.videoHeight;
});

videoEl.addEventListener('durationchange', function(event) {
mutable duration = videoEl.duration;
});

function setCurrentTime() {
mutable currentTime = videoEl.currentTime;
}
videoEl.addEventListener('seeked', setCurrentTime);
videoEl.addEventListener('timeupdate', setCurrentTime);

return videoEl;
}
Insert cell
mutable videoWidth = 0
Insert cell
mutable videoHeight = 0
Insert cell
mutable duration = 0
Insert cell
mutable currentTime = 0
Insert cell
mutable busy = false
Insert cell
mutable progress = 0
Insert cell
outputFrameRate = Math.max(1, Math.floor(options.numFrames / duration))
Insert cell
async function seekTo(videoEl, time) {
return new Promise((resolve, reject) => {
const handleSeeked = function() {
videoEl.removeEventListener('seeked', handleSeeked);
resolve(videoEl.currentTime);
};
videoEl.addEventListener('seeked', handleSeeked);
videoEl.currentTime = time;
});
}
Insert cell
async function extractFrames({
videoEl,
clipStart,
clipEnd,
numFrames,
quality = 0.8,
outputScale = 1
}) {
// if (busy) {
// throw new Error('Already encoding, wait a moment...');
// }
// mutable busy = true;
videoEl.pause();

const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const outputWidth = outputScale * videoEl.videoWidth;
const outputHeight = outputScale * videoEl.videoHeight;
canvas.width = outputWidth;
canvas.height = outputHeight;

const frames = [];

for (let i = 0; i < numFrames; i++) {
const duration = clipEnd - clipStart;
if (duration <= 0) {
throw new Error('clipEnd is before clipStart');
}
const time = i * (duration / (Math.max(2, numFrames) - 1)) + clipStart;
// In threory, waits until seeked time is in view
const seekedTime = await seekTo(videoEl, time);
context.drawImage(videoEl, 0, 0, outputWidth, outputHeight);
frames.push({
time,
seekedTime,
data: canvas.toDataURL('image/jpeg', quality)
});
mutable progress = (i + 1) / numFrames;
}

// mutable busy = false;

return frames;
}
Insert cell
frames = extractFrames({
videoEl,
...options
})
Insert cell
preview = {
const previewImages = frames.map(
({ data }) => html`<img src="${data}" style="max-width: 128px">`
);
return html`<div>${previewImages}</div>`;
}
Insert cell
lottieJson = frames.reduce(
function(lottie, frame, index) {
const id = "fr_" + index;
const w = outputWidth;
const w2 = Math.floor(w / 2);
const h = outputHeight;
const h2 = Math.floor(h / 2);

lottie.assets.push({
id,
w,
h,
u: "",
p: frame.data,
e: 1
});

lottie.layers.push({
ddd: 0,
ind: index + 1,
ty: 2,
nm: id + ".jpg",
cl: "jpg",
refId: id,
sr: 1,
ks: {
o: { a: 0, k: 100, ix: 11 },
r: { a: 0, k: 0, ix: 10 },
p: { a: 0, k: [w2, h2, 0], ix: 2 },
a: { a: 0, k: [w2, h2, 0], ix: 1 },
s: { a: 0, k: [100, 100, 100], ix: 6 }
},
ao: 0,
ip: index,
op: index + 1,
st: index,
bm: 0
});

return lottie;
},
{
v: "5.5.2",
fr: outputFrameRate,
ip: 0,
op: options.numFrames,
w: outputWidth,
h: outputHeight,
nm: "@forresto/movie-to-lottie",
ddd: 0,
assets: [],
layers: [],
markers: []
}
)
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more