snapio-mock-camera 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/_index.js +110 -0
- package/camera.js +364 -0
- package/index.js +4 -0
- package/octopus.js +93 -0
- package/package.json +23 -0
- package/printer.js +38 -0
- package/public/1024x680.jpg +0 -0
- package/public/490x636.jpg +0 -0
- package/public/640x960.jpg +0 -0
- package/public/960x640.jpg +0 -0
- package/test-ai.js +21 -0
package/_index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const app = express();
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const { Observable, Subject } = require('rxjs');
|
|
7
|
+
const { take } = require('rxjs/operators')
|
|
8
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
9
|
+
const octopus = require('./octopus')
|
|
10
|
+
const printer = require('./printer')
|
|
11
|
+
|
|
12
|
+
app.use(cors());
|
|
13
|
+
|
|
14
|
+
let takeImage = {};
|
|
15
|
+
|
|
16
|
+
const IMAGE_SRC = 'public/960x640.jpg';
|
|
17
|
+
|
|
18
|
+
app.get('/liveViewImage', (req, res) => {
|
|
19
|
+
let s = fs.createReadStream(IMAGE_SRC);
|
|
20
|
+
s.on('open', function () {
|
|
21
|
+
res.set('Content-Type', 'image/jpeg');
|
|
22
|
+
s.pipe(res);
|
|
23
|
+
});
|
|
24
|
+
s.on('error', function () {
|
|
25
|
+
res.set('Content-Type', 'text/plain');
|
|
26
|
+
res.status(404).end('Not found');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
app.get('/image', (req, res) => {
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
const latest = Math.max.apply(Math, Object.keys(takeImage));
|
|
32
|
+
if (!takeImage[latest]) return;
|
|
33
|
+
const { width, height, $ } = takeImage[latest];
|
|
34
|
+
const canvas = createCanvas(width, height)
|
|
35
|
+
const ctx = canvas.getContext('2d');
|
|
36
|
+
$.pipe(
|
|
37
|
+
take(1)
|
|
38
|
+
).subscribe(value => {
|
|
39
|
+
console.log(new Date().toISOString(), 'image taken');
|
|
40
|
+
loadImage(IMAGE_SRC).then(img => {
|
|
41
|
+
const nRatio = img.naturalWidth / img.naturalHeight;
|
|
42
|
+
const sRatio = width / height;
|
|
43
|
+
if (nRatio < sRatio) {
|
|
44
|
+
const scale = width / img.naturalWidth;
|
|
45
|
+
ctx.drawImage(img,
|
|
46
|
+
0, (img.naturalHeight - height / scale) / 2, img.naturalWidth, height / scale,
|
|
47
|
+
0, 0, width, height);
|
|
48
|
+
} else {
|
|
49
|
+
const scale = height / img.naturalHeight;
|
|
50
|
+
ctx.drawImage(img,
|
|
51
|
+
(img.naturalWidth - width / scale) / 2, 0, width / scale, img.naturalHeight,
|
|
52
|
+
0, 0, width, height);
|
|
53
|
+
}
|
|
54
|
+
res.set('Content-Type', 'image/jpeg');
|
|
55
|
+
canvas.createJPEGStream().pipe(res)
|
|
56
|
+
})
|
|
57
|
+
delete takeImage[latest];
|
|
58
|
+
})
|
|
59
|
+
}, 200)
|
|
60
|
+
});
|
|
61
|
+
app.post('/startLiveView', (req, res) => {
|
|
62
|
+
res.status(200).end();
|
|
63
|
+
})
|
|
64
|
+
app.post('/stopLiveView', (req, res) => {
|
|
65
|
+
res.status(200).end();
|
|
66
|
+
})
|
|
67
|
+
app.post('/shoot', (req, res) => {
|
|
68
|
+
const latest = Math.max.apply(Math, Object.keys(takeImage));
|
|
69
|
+
if (takeImage[latest])
|
|
70
|
+
takeImage[latest].$.next(true);
|
|
71
|
+
res.status(200).end();
|
|
72
|
+
})
|
|
73
|
+
app.post('/createTimer', (req, res) => {
|
|
74
|
+
const params = req.query;
|
|
75
|
+
const { width, height, seconds } = params;
|
|
76
|
+
const intSeconds = seconds ? parseInt(seconds) : 0;
|
|
77
|
+
console.log(new Date().toISOString(), `${intSeconds} secs`);
|
|
78
|
+
const now = new Date().getTime();
|
|
79
|
+
takeImage[now] = {
|
|
80
|
+
width: parseInt(width),
|
|
81
|
+
height: parseInt(height),
|
|
82
|
+
$: new Subject()
|
|
83
|
+
};
|
|
84
|
+
if (intSeconds > 0) {
|
|
85
|
+
const timeout = setTimeout(_ => {
|
|
86
|
+
console.log(new Date().toISOString(), 'timeout');
|
|
87
|
+
if (takeImage[now])
|
|
88
|
+
takeImage[now].$.next(true);
|
|
89
|
+
}, intSeconds * 1000);
|
|
90
|
+
}
|
|
91
|
+
res.status(200).end();
|
|
92
|
+
})
|
|
93
|
+
app.post('/setProperty', (req, res) => {
|
|
94
|
+
res.status(200).end();
|
|
95
|
+
})
|
|
96
|
+
app.get('/health', (req, res) => {
|
|
97
|
+
res.setHeader('content-type', 'application/json')
|
|
98
|
+
res.send({
|
|
99
|
+
status: 'pass',
|
|
100
|
+
details: {
|
|
101
|
+
isSDKLoaded: true,
|
|
102
|
+
isSessionOpen: true,
|
|
103
|
+
},
|
|
104
|
+
version: '999.9'
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
app.listen(40084, function () {
|
|
109
|
+
console.log('Camera listening on http://localhost:40084/');
|
|
110
|
+
});
|
package/camera.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// Import necessary modules
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const cors = require('cors');
|
|
4
|
+
const { spawn } = require('child_process'); // To run external commands like ffmpeg
|
|
5
|
+
const http = require('http'); // Required for HTTP server
|
|
6
|
+
const { Observable, Subject, BehaviorSubject } = require('rxjs');
|
|
7
|
+
const { take, takeUntil, filter, switchMap, first, throttleTime, takeWhile } = require('rxjs/operators')
|
|
8
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
9
|
+
|
|
10
|
+
// Initialize Express application
|
|
11
|
+
const app = express();
|
|
12
|
+
app.use(cors())
|
|
13
|
+
const port = process.env['SNAPIO_CAMERA_MANAGER_PORT'] ? Number(process.env['SNAPIO_CAMERA_MANAGER_PORT']) : 40084;
|
|
14
|
+
|
|
15
|
+
// Create an HTTP server and attach Express app
|
|
16
|
+
const server = http.createServer(app);
|
|
17
|
+
|
|
18
|
+
let ffmpegProcess = null; // Variable to hold the ffmpeg process
|
|
19
|
+
let liveView = new BehaviorSubject(null);
|
|
20
|
+
let takeImage = {};
|
|
21
|
+
// --- MJPEG Stream Endpoint ---
|
|
22
|
+
app.get('/liveViewImage', (req, res) => {
|
|
23
|
+
console.log('Client connected to /liveViewImage endpoint (multipart/x-mixed-replace).');
|
|
24
|
+
const close$ = new Subject()
|
|
25
|
+
let isHeadersSent = false;
|
|
26
|
+
liveView.pipe(
|
|
27
|
+
throttleTime(1000/8),
|
|
28
|
+
filter(data => data != null),
|
|
29
|
+
takeUntil(close$)
|
|
30
|
+
).subscribe({
|
|
31
|
+
next: (data) => {
|
|
32
|
+
if (!isHeadersSent) {
|
|
33
|
+
res.writeHead(200, {
|
|
34
|
+
'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary',
|
|
35
|
+
'Cache-Control': 'no-cache',
|
|
36
|
+
'Connection': 'keep-alive',
|
|
37
|
+
});
|
|
38
|
+
isHeadersSent = true
|
|
39
|
+
}
|
|
40
|
+
res.write('--myboundary\r\n');
|
|
41
|
+
res.write('Content-Type: image/jpeg\r\n');
|
|
42
|
+
res.write('Content-Length: ' + data.length + '\r\n');
|
|
43
|
+
res.write('\r\n');
|
|
44
|
+
res.write(data); // Send raw MJPEG frame data
|
|
45
|
+
res.write('\r\n');
|
|
46
|
+
},
|
|
47
|
+
error: (err) => {
|
|
48
|
+
console.log(`Live view subscription error: ${ err }`)
|
|
49
|
+
res.status(500).send(`Error in ffmpeg process: ${ err }`)
|
|
50
|
+
res.end()
|
|
51
|
+
ffmpegProcess?.kill('SIGKILL')
|
|
52
|
+
ffmpegProcess = null;
|
|
53
|
+
},
|
|
54
|
+
complete: () => {
|
|
55
|
+
console.log('Live view subscription complete')
|
|
56
|
+
res.end('--myboundary--\r\n'); // End the multipart stream gracefully
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
// Handle client disconnection from the HTTP stream
|
|
60
|
+
req.on('close', () => {
|
|
61
|
+
console.log('Client disconnected from /liveViewImage. Killing ffmpeg process.');
|
|
62
|
+
close$.next()
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
req.on('error', (err) => {
|
|
66
|
+
console.error('Request error on /liveViewImage:', err);
|
|
67
|
+
close$.next()
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
app.get('/image', (req, res) => {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
const latest = Math.max.apply(Math, Object.keys(takeImage));
|
|
73
|
+
if (!takeImage[latest]) return;
|
|
74
|
+
const { width, height, $ } = takeImage[latest];
|
|
75
|
+
const canvas = createCanvas(width, height)
|
|
76
|
+
const ctx = canvas.getContext('2d');
|
|
77
|
+
$.pipe(
|
|
78
|
+
take(1),
|
|
79
|
+
switchMap(() => liveView.pipe(
|
|
80
|
+
takeWhile(data => data != null)
|
|
81
|
+
)),
|
|
82
|
+
).subscribe(async data => {
|
|
83
|
+
console.log(new Date().toISOString(), 'image taken');
|
|
84
|
+
const { w, h } = jpegProps(data);
|
|
85
|
+
const nRatio = w / h;
|
|
86
|
+
const sRatio = width / height;
|
|
87
|
+
const img = await loadImage(data)
|
|
88
|
+
if (nRatio < sRatio) {
|
|
89
|
+
const scale = width / w;
|
|
90
|
+
ctx.drawImage(img,
|
|
91
|
+
0, (h - height / scale) / 2, w, height / scale,
|
|
92
|
+
0, 0, width, height);
|
|
93
|
+
} else {
|
|
94
|
+
const scale = height / h;
|
|
95
|
+
ctx.drawImage(img,
|
|
96
|
+
(w - width / scale) / 2, 0, width / scale, h,
|
|
97
|
+
0, 0, width, height);
|
|
98
|
+
}
|
|
99
|
+
res.set('Content-Type', 'image/jpeg');
|
|
100
|
+
canvas.createJPEGStream().pipe(res)
|
|
101
|
+
delete takeImage[latest];
|
|
102
|
+
})
|
|
103
|
+
}, 200)
|
|
104
|
+
})
|
|
105
|
+
app.post('/shoot', (req, res) => {
|
|
106
|
+
const latest = Math.max.apply(Math, Object.keys(takeImage));
|
|
107
|
+
if (takeImage[latest])
|
|
108
|
+
takeImage[latest].$.next(true);
|
|
109
|
+
res.status(200).end();
|
|
110
|
+
})
|
|
111
|
+
app.post('/createTimer', (req, res) => {
|
|
112
|
+
const params = req.query;
|
|
113
|
+
const { width, height, seconds } = params;
|
|
114
|
+
const intSeconds = seconds ? parseInt(seconds) : 0;
|
|
115
|
+
console.log(new Date().toISOString(), `${intSeconds} secs`);
|
|
116
|
+
const now = new Date().getTime();
|
|
117
|
+
takeImage[now] = {
|
|
118
|
+
width: parseInt(width),
|
|
119
|
+
height: parseInt(height),
|
|
120
|
+
$: new Subject()
|
|
121
|
+
};
|
|
122
|
+
if (intSeconds > 0) {
|
|
123
|
+
const timeout = setTimeout(_ => {
|
|
124
|
+
console.log(new Date().toISOString(), 'timeout');
|
|
125
|
+
if (takeImage[now])
|
|
126
|
+
takeImage[now].$.next(true);
|
|
127
|
+
}, intSeconds * 1000);
|
|
128
|
+
}
|
|
129
|
+
res.status(200).end();
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
app.post('/startLiveView', (req, res) => {
|
|
133
|
+
if (ffmpegProcess) {
|
|
134
|
+
console.log('ffmpeg is already running')
|
|
135
|
+
res.status(200).end();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
liveView = new BehaviorSubject(null);
|
|
140
|
+
if (process.platform == 'darwin') {
|
|
141
|
+
// ffmpeg -y -f avfoundation -r 8 -i 0 -s 960x540 -f mjpeg -
|
|
142
|
+
// ffmpegProcess = spawn('ffmpeg', [
|
|
143
|
+
// '-y', '-f', 'avfoundation', '-r', '8', '-i', '0', '-s', '960x540', '-f', 'mjpeg', '-'
|
|
144
|
+
// ]);
|
|
145
|
+
// ffmpeg -y -f avfoundation -r 7.5 -i 0 -s 1280x720 -f mjpeg -
|
|
146
|
+
ffmpegProcess = spawn('ffmpeg', [
|
|
147
|
+
'-y', '-f', 'avfoundation', '-r', '15', '-i', '0', '-s', '960x720', '-f', 'mjpeg', '-'
|
|
148
|
+
]);
|
|
149
|
+
} else if (process.platform == 'win32') {
|
|
150
|
+
ffmpegProcess = spawn('ffmpeg', [
|
|
151
|
+
'-y',
|
|
152
|
+
'-f', 'vfwcap',
|
|
153
|
+
'-i', '0',
|
|
154
|
+
// '-q:v', '3', // Video quality (lower is better, 1-31)
|
|
155
|
+
'-r', '8', // Frame rate (frames per second)
|
|
156
|
+
// '-s', '640x480', // Resolution
|
|
157
|
+
'-f', 'mjpeg',
|
|
158
|
+
'-' // Output to stdout
|
|
159
|
+
]);
|
|
160
|
+
} else {
|
|
161
|
+
throw new Error('Unsupported platform: ' + process.platform);
|
|
162
|
+
}
|
|
163
|
+
ffmpegProcess.stdout.on('data', data => {
|
|
164
|
+
liveView.next(data);
|
|
165
|
+
});
|
|
166
|
+
// Handle ffmpeg process errors
|
|
167
|
+
ffmpegProcess.stderr.on('data', data => {
|
|
168
|
+
// the standard output always comes out from here, ignore
|
|
169
|
+
// console.error(`ffmpeg stderr: ${data}`);
|
|
170
|
+
// liveView.error(data)
|
|
171
|
+
// liveView.complete()
|
|
172
|
+
});
|
|
173
|
+
// Handle ffmpeg process close
|
|
174
|
+
ffmpegProcess.on('close', code => {
|
|
175
|
+
console.log(`ffmpeg process exited with code ${code}. Closing stream.`);
|
|
176
|
+
liveView.complete()
|
|
177
|
+
ffmpegProcess = null; // Clear the process reference
|
|
178
|
+
});
|
|
179
|
+
// Handle ffmpeg process errors (e.g., ffmpeg not found)
|
|
180
|
+
ffmpegProcess.on('error', err => {
|
|
181
|
+
console.error('Failed to start ffmpeg process:', err);
|
|
182
|
+
liveView.error(err)
|
|
183
|
+
liveView.complete()
|
|
184
|
+
ffmpegProcess = null;
|
|
185
|
+
});
|
|
186
|
+
res.status(200).end();
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error spawning ffmpeg:', error);
|
|
189
|
+
liveView.error(err)
|
|
190
|
+
liveView.complete()
|
|
191
|
+
ffmpegProcess = null
|
|
192
|
+
res.status(500).end()
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
app.post('/stopLiveView', (req, res) => {
|
|
196
|
+
if (ffmpegProcess) {
|
|
197
|
+
console.log('stopLiveView, killing ffmpeg')
|
|
198
|
+
ffmpegProcess.kill('SIGKILL');
|
|
199
|
+
ffmpegProcess = null;
|
|
200
|
+
liveView.complete();
|
|
201
|
+
res.status(200).end();
|
|
202
|
+
} else {
|
|
203
|
+
console.log('ffmpeg is not running')
|
|
204
|
+
res.status(200).end();
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
app.post('/setProperty', (req, res) => {
|
|
208
|
+
res.status(200).end();
|
|
209
|
+
})
|
|
210
|
+
app.get('/health', (req, res) => {
|
|
211
|
+
res.setHeader('content-type', 'application/json')
|
|
212
|
+
res.send({
|
|
213
|
+
status: 'pass',
|
|
214
|
+
details: {
|
|
215
|
+
isSDKLoaded: true,
|
|
216
|
+
isSessionOpen: true,
|
|
217
|
+
},
|
|
218
|
+
version: '999.9'
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// --- Express Server Logic ---
|
|
223
|
+
|
|
224
|
+
// This doesn't work without calling startLiveView first
|
|
225
|
+
app.get('/', (req, res) => {
|
|
226
|
+
res.send(`
|
|
227
|
+
<!DOCTYPE html>
|
|
228
|
+
<html lang="en">
|
|
229
|
+
<head>
|
|
230
|
+
<meta charset="UTF-8">
|
|
231
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
232
|
+
<title>Live Webcam Stream (MJPEG)</title>
|
|
233
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
234
|
+
<style>
|
|
235
|
+
body {
|
|
236
|
+
font-family: 'Inter', sans-serif;
|
|
237
|
+
background-color: #f0f2f5;
|
|
238
|
+
display: flex;
|
|
239
|
+
justify-content: center;
|
|
240
|
+
align-items: center;
|
|
241
|
+
min-height: 100vh;
|
|
242
|
+
margin: 0;
|
|
243
|
+
}
|
|
244
|
+
.container {
|
|
245
|
+
background-color: #ffffff;
|
|
246
|
+
border-radius: 10px;
|
|
247
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
248
|
+
padding: 2rem;
|
|
249
|
+
text-align: center;
|
|
250
|
+
width: 90%;
|
|
251
|
+
max-width: 800px;
|
|
252
|
+
}
|
|
253
|
+
#videoStream {
|
|
254
|
+
width: 100%;
|
|
255
|
+
max-width: 720px;
|
|
256
|
+
height: auto;
|
|
257
|
+
border-radius: 8px;
|
|
258
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
259
|
+
margin-top: 1rem;
|
|
260
|
+
background-color: #000;
|
|
261
|
+
display: block; /* Ensure it takes up full width */
|
|
262
|
+
margin-left: auto;
|
|
263
|
+
margin-right: auto;
|
|
264
|
+
}
|
|
265
|
+
.status-message {
|
|
266
|
+
margin-top: 1rem;
|
|
267
|
+
font-size: 1rem;
|
|
268
|
+
color: #555;
|
|
269
|
+
}
|
|
270
|
+
.error-message {
|
|
271
|
+
color: #e53e3e;
|
|
272
|
+
font-weight: bold;
|
|
273
|
+
}
|
|
274
|
+
</style>
|
|
275
|
+
</head>
|
|
276
|
+
<body>
|
|
277
|
+
<div class="container flex flex-col items-center">
|
|
278
|
+
<h1 class="text-3xl font-bold text-gray-800 mb-4">Live Webcam Stream (MJPEG)</h1>
|
|
279
|
+
<!-- The img tag's src points directly to the MJPEG stream endpoint -->
|
|
280
|
+
<img id="videoStream" class="rounded-lg" src="/liveViewImage" alt="Live Webcam Stream">
|
|
281
|
+
<p id="status" class="status-message">Loading video stream...</p>
|
|
282
|
+
<button id="refreshButton" class="mt-4 px-6 py-2 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">
|
|
283
|
+
Refresh Stream
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<script>
|
|
288
|
+
const videoStream = document.getElementById('videoStream');
|
|
289
|
+
const statusElement = document.getElementById('status');
|
|
290
|
+
const refreshButton = document.getElementById('refreshButton');
|
|
291
|
+
|
|
292
|
+
// Basic status update for initial load
|
|
293
|
+
videoStream.onload = () => {
|
|
294
|
+
statusElement.textContent = 'Live Streaming...';
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
videoStream.onerror = () => {
|
|
298
|
+
statusElement.className = 'status-message error-message';
|
|
299
|
+
statusElement.textContent = 'Failed to load stream. Ensure the server is running and FFmpeg is configured correctly.';
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
refreshButton.addEventListener('click', () => {
|
|
303
|
+
// Force reload the image source to re-establish the stream
|
|
304
|
+
const currentSrc = videoStream.src;
|
|
305
|
+
videoStream.src = ''; // Clear src to force reload
|
|
306
|
+
videoStream.src = currentSrc.split('?')[0] + '?' + new Date().getTime(); // Add cache busting
|
|
307
|
+
statusElement.className = 'status-message';
|
|
308
|
+
statusElement.textContent = 'Attempting to reconnect...';
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Initial check for stream status (optional, for faster feedback if stream fails immediately)
|
|
312
|
+
// This doesn't strictly monitor the stream, but can catch initial connection issues.
|
|
313
|
+
setTimeout(() => {
|
|
314
|
+
if (!videoStream.complete && videoStream.naturalWidth === 0) {
|
|
315
|
+
statusElement.className = 'status-message error-message';
|
|
316
|
+
statusElement.textContent = 'Stream not loading. Check server logs and FFmpeg setup.';
|
|
317
|
+
}
|
|
318
|
+
}, 5000); // Check after 5 seconds
|
|
319
|
+
</script>
|
|
320
|
+
</body>
|
|
321
|
+
</html>
|
|
322
|
+
`);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Start the HTTP server
|
|
326
|
+
server.listen(port, () => {
|
|
327
|
+
console.log(`Webcam streaming server listening at http://localhost:${port}`);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
331
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
process.on('uncaughtException', (error) => {
|
|
335
|
+
console.error('Uncaught Exception:', error);
|
|
336
|
+
if (ffmpegProcess) {
|
|
337
|
+
ffmpegProcess.kill('SIGKILL');
|
|
338
|
+
console.log('Killed ffmpeg process due to uncaught exception.');
|
|
339
|
+
}
|
|
340
|
+
process.exit(1); // Exit process with failure
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
function jpegProps(data) {
|
|
344
|
+
var off = 0;
|
|
345
|
+
while(off<data.length) {
|
|
346
|
+
while(data[off]==0xff) off++;
|
|
347
|
+
var mrkr = data[off]; off++;
|
|
348
|
+
|
|
349
|
+
if(mrkr==0xd8) continue; // SOI
|
|
350
|
+
if(mrkr==0xd9) break; // EOI
|
|
351
|
+
if(0xd0<=mrkr && mrkr<=0xd7) continue;
|
|
352
|
+
if(mrkr==0x01) continue; // TEM
|
|
353
|
+
|
|
354
|
+
var len = (data[off]<<8) | data[off+1]; off+=2;
|
|
355
|
+
|
|
356
|
+
if(mrkr==0xc0) return {
|
|
357
|
+
bpc : data[off], // precission (bits per channel)
|
|
358
|
+
h : (data[off+1]<<8) | data[off+2],
|
|
359
|
+
w : (data[off+3]<<8) | data[off+4],
|
|
360
|
+
cps : data[off+5] // number of color components
|
|
361
|
+
}
|
|
362
|
+
off+=len-2;
|
|
363
|
+
}
|
|
364
|
+
}
|
package/index.js
ADDED
package/octopus.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const app = express()
|
|
4
|
+
|
|
5
|
+
app.use(cors())
|
|
6
|
+
|
|
7
|
+
app.post('/init', (req, res) => {
|
|
8
|
+
res.setHeader('content-type', 'application/json')
|
|
9
|
+
res.send({
|
|
10
|
+
data: {
|
|
11
|
+
deviceId: 12345678,
|
|
12
|
+
isBlacklistOutdated: true,
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
let count = 0
|
|
18
|
+
app.post('/payments', (req, res) => {
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
res.setHeader('content-type', 'application/json')
|
|
21
|
+
res.send({
|
|
22
|
+
error: {
|
|
23
|
+
code: -100,
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
// if (count++ % 3 == 2) {
|
|
27
|
+
// res.send({
|
|
28
|
+
// data: {
|
|
29
|
+
// timestamp: Math.floor(Date.now() / 1000),
|
|
30
|
+
// deviceId: 12345678,
|
|
31
|
+
// receiptNo: 'qwertyasdf',
|
|
32
|
+
// cardId: 12345678,
|
|
33
|
+
// deductAmount: 60,
|
|
34
|
+
// remainingValue: 123,
|
|
35
|
+
// octopusType: 1,
|
|
36
|
+
// lastAddValueInfo: {
|
|
37
|
+
// date: '2024-11-12',
|
|
38
|
+
// type: 1,
|
|
39
|
+
// },
|
|
40
|
+
// }
|
|
41
|
+
// })
|
|
42
|
+
// } else {
|
|
43
|
+
// res.send({
|
|
44
|
+
// error: {
|
|
45
|
+
// extraData: {
|
|
46
|
+
// cardId: 123456789,
|
|
47
|
+
// receiptNo: 1234567890,
|
|
48
|
+
// },
|
|
49
|
+
// code: 100022,
|
|
50
|
+
// }
|
|
51
|
+
// })
|
|
52
|
+
// }
|
|
53
|
+
}, 3000)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
app.post('/enquiry', (req, res) => {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
res.send({
|
|
59
|
+
data: {
|
|
60
|
+
octopusType: 2,
|
|
61
|
+
remainingValue: 56.7,
|
|
62
|
+
cardId: 12345678,
|
|
63
|
+
cardLogs: [{
|
|
64
|
+
transactionAmount: -50,
|
|
65
|
+
transactionTimestamp: Math.floor(Date.now() / 1000),
|
|
66
|
+
deviceId: 1234,
|
|
67
|
+
isSameDevice: false,
|
|
68
|
+
}, {
|
|
69
|
+
transactionAmount: -12.3,
|
|
70
|
+
transactionTimestamp: Math.floor(Date.now() / 1000),
|
|
71
|
+
deviceId: 1234,
|
|
72
|
+
isSameDevice: false,
|
|
73
|
+
}, {
|
|
74
|
+
transactionAmount: 500,
|
|
75
|
+
transactionTimestamp: Math.floor(Date.now() / 1000),
|
|
76
|
+
deviceId: 1234,
|
|
77
|
+
isSameDevice: false,
|
|
78
|
+
}, {
|
|
79
|
+
transactionAmount: -0.1,
|
|
80
|
+
transactionTimestamp: Math.floor(Date.now() / 1000),
|
|
81
|
+
deviceId: 123456,
|
|
82
|
+
isSameDevice: true,
|
|
83
|
+
}],
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}, 5_000)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const port = process.env['ALMAGEST_OCTOPUS_MANAGER_PORT'] ? Number(process.env['ALMAGEST_OCTOPUS_MANAGER_PORT']) : 40082;
|
|
90
|
+
|
|
91
|
+
app.listen(port, () => {
|
|
92
|
+
console.log(`Octopus listening on http://localhost:${port}`)
|
|
93
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snapio-mock-camera",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "nodemon index.js",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"canvas": "^3.0.0-rc2",
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"express": "^4.17.1",
|
|
17
|
+
"node-webcam": "^0.8.2",
|
|
18
|
+
"rxjs": "^7.8.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"nodemon": "^3.1.10"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/printer.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const app = express()
|
|
6
|
+
|
|
7
|
+
app.use(cors())
|
|
8
|
+
|
|
9
|
+
let remaining = 100;
|
|
10
|
+
|
|
11
|
+
app.get('/health', (req, res) => {
|
|
12
|
+
res.setHeader('content-type', 'application/json')
|
|
13
|
+
res.send({
|
|
14
|
+
// status: Math.random() < 0.9 ? 'fail' : 'pass',
|
|
15
|
+
status: 'pass',
|
|
16
|
+
message: 'Hello world',
|
|
17
|
+
remainingMediaQuantity: remaining,
|
|
18
|
+
details: { printerModel: 'DS620' }
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
app.post('/printPhoto', (req, res) => {
|
|
22
|
+
const ws = fs.createWriteStream(path.join(__dirname, 'tmp', `${Date.now()}.jpg`))
|
|
23
|
+
req.pipe(ws)
|
|
24
|
+
ws.on('finish', () => {
|
|
25
|
+
res.setHeader('content-type', 'application/json')
|
|
26
|
+
res.send({
|
|
27
|
+
data: {
|
|
28
|
+
remainingMediaQuantity: --remaining,
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const port = process.env['SNAPIO_PRINTER_MANAGER_PORT'] ? Number(process.env['SNAPIO_PRINTER_MANAGER_PORT']) : 40080;
|
|
35
|
+
|
|
36
|
+
app.listen(port, () => {
|
|
37
|
+
console.log(`Printer listening on http://localhost:${port}`)
|
|
38
|
+
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/test-ai.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
const IMAGE_SRC = 'public/960x640.jpg';
|
|
5
|
+
const blob = new File([await fs.openAsBlob(IMAGE_SRC)], 'asdf.jpg', { type: 'image/jpeg' })
|
|
6
|
+
const formData = new FormData()
|
|
7
|
+
formData.append('image_1', blob)
|
|
8
|
+
// formData.append('style', 'cr7')
|
|
9
|
+
const res = await fetch('https://pons-ai--generation-pipeline-fastapi-app.modal.run/ponsai-generation-pipeline?style=cr7', {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
// 'content-type': 'multipart/form-data',
|
|
13
|
+
'accept': 'application/json',
|
|
14
|
+
'Modal-Key': 'wk-rZQi1PiNn6esAt9Mh2GlUs',
|
|
15
|
+
'Modal-Secret': 'ws-0U18fCTu1Zv6G1yTsvS8ZW'
|
|
16
|
+
},
|
|
17
|
+
body: formData
|
|
18
|
+
})
|
|
19
|
+
const json = await (res).json();
|
|
20
|
+
console.log(json)
|
|
21
|
+
})()
|