Skip to content

Instantly share code, notes, and snippets.

@connordavenport
Forked from kubarskii/video.html
Created November 8, 2022 21:20
Show Gist options
  • Save connordavenport/896a4c61234bf1166ba8c632917333dd to your computer and use it in GitHub Desktop.
Save connordavenport/896a4c61234bf1166ba8c632917333dd to your computer and use it in GitHub Desktop.
Render video as ascii — use a variable font instead of ascii characters
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<style>
body {
background: #ffffff
}
video, canvas {
display: none;
}
@font-face {
font-family: 'SourceCode';
src: url('SourceCodeVariable-Roman.ttf') format('truetype-variations');
/*font-weight: 0 1000;*/
}
#text-video {
font-family: "SourceCode", sans-serif;
font-weight: 0;
font-size: 10px;
line-height: 8px;
color: black;
}
</style>
<body>
<div>
<div>
<video id="video">Video stream not available.</video>
<canvas id="canvas-video"></canvas>
</div>
<div id="text-video"></div>
<button id="stop">Stop</button>
</div>
</body>
<script>
(function () {
const video = document.getElementById('video')
const textVideo = document.getElementById('text-video')
const canvas = document.getElementById('canvas-video')
const ctx = canvas.getContext('2d');
const width = 320 / 2, height = 240 / 2;
const init = () => {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(function (stream) {
video.srcObject = stream;
video.play();
})
.catch(function (err) {
console.log("An error occurred: " + err);
});
}
const clearphoto = () => {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
}
const render = (ctx) => {
if (width && height) {
canvas.width = width;
canvas.height = height;
ctx.drawImage(video, 0, 0, width, height);
} else {
clearphoto();
}
}
const getPixelsGreyScale = (ctx) => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
let row = 0
const res = new Array(height).fill(0).map(() => []);
for (let i = 0, c = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
let curr = res[row]
curr.push(avg)
if (c < width) {
c++
}
if (c === width) {
c = 0
row += 1
}
}
return res
}
const renderText = (node, textDarkScale) => {
let txt = `<div>`
for (let i = 0; i < textDarkScale.length; i++) {
for (let k = 0; k < textDarkScale[i].length; k++) {
txt = `${txt}<span style=\"font-variation-settings: \'wght\' ${Math.floor((1 - (textDarkScale[i][k]/255)) * 1000)};\">a</span>`
}
txt += `<br>`
}
txt += `</div>`
node.innerHTML = txt
}
init()
const interval = setInterval(() => {
requestAnimationFrame(() => {
render(ctx)
const chars = getPixelsGreyScale(ctx)
renderText(textVideo, chars)
})
})
document.getElementById('stop').addEventListener('click', (e) => {
clearInterval(interval)
})
})()
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment