ascii-cam 1.0.0
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/README.md +27 -0
- package/index.js +122 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ascii-cam
|
|
2
|
+
|
|
3
|
+
Render your webcam feed as real-time ASCII art directly in the terminal!
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install globally via npm:
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g ascii-cam
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or run it instantly without installing:
|
|
13
|
+
```bash
|
|
14
|
+
npx ascii-cam
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
Simply type the command in your terminal:
|
|
19
|
+
```bash
|
|
20
|
+
ascii-cam
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- Make sure you are in a terminal that supports ANSI escape codes.
|
|
24
|
+
- To exit the camera feed, press **`Ctrl + C`**.
|
|
25
|
+
|
|
26
|
+
## How it works
|
|
27
|
+
This package uses `puppeteer` to access the webcam via headless Chrome's WebRTC implementation and processes the frames locally in the browser context before passing the string buffer to your Node terminal!
|
package/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const puppeteer = require('puppeteer');
|
|
4
|
+
|
|
5
|
+
// A string of characters representing different shades from darkest to lightest
|
|
6
|
+
const density = "Ñ@#W$9876543210?!abc;:+=-,._ ".split("").reverse().join("");
|
|
7
|
+
|
|
8
|
+
(async () => {
|
|
9
|
+
// Clear the console and hide the cursor
|
|
10
|
+
console.clear();
|
|
11
|
+
process.stdout.write('\x1B[?25l');
|
|
12
|
+
|
|
13
|
+
let browser;
|
|
14
|
+
|
|
15
|
+
// Handle Ctrl+C gracefully
|
|
16
|
+
process.on('SIGINT', async () => {
|
|
17
|
+
process.stdout.write('\x1B[?25h'); // Show cursor again
|
|
18
|
+
console.clear();
|
|
19
|
+
if (browser) await browser.close();
|
|
20
|
+
process.exit();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
browser = await puppeteer.launch({
|
|
25
|
+
headless: false,
|
|
26
|
+
defaultViewport: null,
|
|
27
|
+
args: [
|
|
28
|
+
'--use-fake-ui-for-media-stream',
|
|
29
|
+
'--disable-gpu',
|
|
30
|
+
'--autoplay-policy=no-user-gesture-required',
|
|
31
|
+
'--mute-audio',
|
|
32
|
+
'--window-position=-32000,-32000',
|
|
33
|
+
'--window-size=400,400'
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const page = await browser.newPage();
|
|
38
|
+
|
|
39
|
+
await page.exposeFunction('renderFrame', (frameData) => {
|
|
40
|
+
process.stdout.write('\x1B[H' + frameData);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const cols = process.stdout.columns || 80;
|
|
44
|
+
const rows = (process.stdout.rows || 24) - 1;
|
|
45
|
+
|
|
46
|
+
// Modern browsers block webcam access on insecure contexts like about:blank
|
|
47
|
+
// Navigating to a safe, secure HTTPS page guarantees getUserMedia is available
|
|
48
|
+
await page.goto('https://example.com');
|
|
49
|
+
|
|
50
|
+
await page.evaluate(async (cols, rows, densityStr) => {
|
|
51
|
+
const video = document.createElement('video');
|
|
52
|
+
video.autoplay = true;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
56
|
+
throw new Error("getUserMedia is undefined (Security Context Issue)");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
60
|
+
video: {
|
|
61
|
+
width: { ideal: 640 },
|
|
62
|
+
height: { ideal: 480 }
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
video.srcObject = stream;
|
|
66
|
+
await new Promise(resolve => video.onloadedmetadata = resolve);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error("Camera Error:", e.name, e.message);
|
|
69
|
+
window.renderFrame("Error Details: " + e.name + " - " + e.message + "\n\n1. If it says NotFoundError, no camera hardware was found.\n2. If it says NotAllowedError, Windows Privacy or the Browser blocked it.\n3. Ensure no other app (like Zoom or OBS) is actively using the camera.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const canvas = document.createElement('canvas');
|
|
74
|
+
canvas.width = cols;
|
|
75
|
+
canvas.height = rows;
|
|
76
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
77
|
+
|
|
78
|
+
let lastTime = 0;
|
|
79
|
+
const fpsLimit = 1000 / 30;
|
|
80
|
+
|
|
81
|
+
const draw = (time) => {
|
|
82
|
+
requestAnimationFrame(draw);
|
|
83
|
+
|
|
84
|
+
if (time - lastTime < fpsLimit) return;
|
|
85
|
+
lastTime = time;
|
|
86
|
+
|
|
87
|
+
ctx.save();
|
|
88
|
+
ctx.scale(-1, 1);
|
|
89
|
+
ctx.drawImage(video, -cols, 0, cols, rows);
|
|
90
|
+
ctx.restore();
|
|
91
|
+
|
|
92
|
+
const imgData = ctx.getImageData(0, 0, cols, rows).data;
|
|
93
|
+
let asciiFrame = "";
|
|
94
|
+
|
|
95
|
+
for (let y = 0; y < rows; y++) {
|
|
96
|
+
for (let x = 0; x < cols; x++) {
|
|
97
|
+
const i = (y * cols + x) * 4;
|
|
98
|
+
const r = imgData[i];
|
|
99
|
+
const g = imgData[i + 1];
|
|
100
|
+
const b = imgData[i + 2];
|
|
101
|
+
|
|
102
|
+
const avg = (r * 0.299 + g * 0.587 + b * 0.114);
|
|
103
|
+
const charIndex = Math.floor((avg / 255) * (densityStr.length - 1));
|
|
104
|
+
asciiFrame += densityStr[charIndex];
|
|
105
|
+
}
|
|
106
|
+
asciiFrame += "\n";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
window.renderFrame(asciiFrame);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
requestAnimationFrame(draw);
|
|
113
|
+
|
|
114
|
+
}, cols, rows, density);
|
|
115
|
+
|
|
116
|
+
} catch (err) {
|
|
117
|
+
process.stdout.write('\x1B[?25h');
|
|
118
|
+
console.error("An error occurred launching the camera:", err);
|
|
119
|
+
if (browser) await browser.close();
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ascii-cam",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Render webcam feed as ASCII art in the terminal",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ascii-cam": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"os": [
|
|
10
|
+
"win32",
|
|
11
|
+
"darwin",
|
|
12
|
+
"linux"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ascii",
|
|
19
|
+
"camera",
|
|
20
|
+
"webcam",
|
|
21
|
+
"terminal",
|
|
22
|
+
"video"
|
|
23
|
+
],
|
|
24
|
+
"author": "Your Name",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"puppeteer": "^24.3.0"
|
|
28
|
+
}
|
|
29
|
+
}
|