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.
Files changed (3) hide show
  1. package/README.md +27 -0
  2. package/index.js +122 -0
  3. 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
+ }