ipcam 0.1.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.
@@ -0,0 +1,486 @@
1
+ /*
2
+ * Sample for v4l2-ruby
3
+ *
4
+ * Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ (function () {
8
+ /*
9
+ * define constants
10
+ */
11
+
12
+ const WS_URL = `ws://${location.hostname}:${parseInt(location.port)+1}/`;
13
+
14
+ /*
15
+ * declar package global variabled
16
+ */
17
+
18
+ var session;
19
+ var capabilities;
20
+ var controls;
21
+
22
+ var imageWidth;
23
+ var imageHeight;
24
+ var framerate;
25
+
26
+ var previewCanvas;
27
+ var previewGc;
28
+
29
+ /*
30
+ * declar functions
31
+ */
32
+
33
+ function setCameraInfo(info) {
34
+ var fg;
35
+ var bg;
36
+
37
+ $('h3#device-file').text(info["device"]);
38
+
39
+ switch (info["state"]) {
40
+ case "READY":
41
+ default:
42
+ fg = "royalblue";
43
+ bg = "white";
44
+ break;
45
+
46
+ case "BUSY":
47
+ fg = "gold";
48
+ bg = "black";
49
+ break;
50
+
51
+ case "ABORT":
52
+ fg = "crimson";
53
+ bg = "white";
54
+ }
55
+
56
+ $('div#state')
57
+ .css('color', fg)
58
+ .css('-webkit-text-stroke', `0.5px ${bg}`)
59
+ .text(info["state"]);
60
+ }
61
+
62
+ function setIdentString(str) {
63
+ var height;
64
+
65
+ $('h5#device-name').text(str);
66
+
67
+ height = $('body').height() - $('div.jumbotron').outerHeight(true);
68
+ $('div#main-area').height(height);
69
+ }
70
+
71
+ function sortCapabilities() {
72
+ capabilities.sort((a, b) => {
73
+ return ((a.width * a.height) - (b.width * b.height));
74
+ });
75
+
76
+ capabilities.forEach((info) => {
77
+ info["rate"].sort((a, b) => {return (a[0] / a[1]) - (b[0] / b[1])});
78
+ });
79
+ }
80
+
81
+ function setImageSizeSelect() {
82
+ /*
83
+ * 画像サイズ
84
+ */
85
+ $('select#image-size').empty();
86
+
87
+ capabilities.forEach((obj) => {
88
+ $('select#image-size')
89
+ .append($('<option>')
90
+ .attr('value', `${obj.width},${obj.height}`)
91
+ .text(`${obj.width} \u00d7 ${obj.height}`)
92
+ );
93
+ });
94
+
95
+ $('select#image-size')
96
+ .val(`${imageWidth},${imageHeight}`)
97
+ .on('change', (e) => {
98
+ let val;
99
+ let res;
100
+
101
+ val = $(e.target).val();
102
+ res = val.match(/(\d+),(\d+)/);
103
+
104
+ session.setImageSize(parseInt(res[1]), parseInt(res[2]));
105
+ });
106
+ }
107
+
108
+ function framerateString(val) {
109
+ var ret;
110
+
111
+ ret = Math.trunc((val[0] / val[1]) * 100) / 100;
112
+
113
+ return `${ret} fps`;
114
+ }
115
+
116
+ function chooseFramerate(rate) {
117
+ var info;
118
+ var list;
119
+ var targ;
120
+
121
+ targ = rate[0] / rate[1];
122
+
123
+ info = capabilities.find((obj) => {
124
+ return ((obj.width == imageWidth) && (obj.height == imageHeight));
125
+ });
126
+
127
+ list = info["rate"].reduce((m, n) => {m.push(n); return m}, []);
128
+ list.sort((a, b) => {
129
+ return Math.abs((a[0] / a[1]) - targ) - Math.abs((b[0] / b[1]) - targ);
130
+ });
131
+
132
+ return `${list[0][0]},${list[0][1]}`;
133
+ }
134
+
135
+ function setFramerateSelect() {
136
+ var info;
137
+
138
+ /*
139
+ * フレームレート
140
+ */
141
+
142
+ $('select#framerate').empty();
143
+
144
+ info = capabilities.find((obj) => {
145
+ return ((obj.width == imageWidth) && (obj.height == imageHeight));
146
+ });
147
+
148
+ if (info) {
149
+ info["rate"].forEach((obj) => {
150
+ $('select#framerate')
151
+ .append($('<option>')
152
+ .attr('value', `${obj[0]},${obj[1]}`)
153
+ .text(framerateString(obj))
154
+ );
155
+ });
156
+
157
+ $('select#framerate')
158
+ .val(chooseFramerate(framerate))
159
+ .on('change', (e) => {;
160
+ let val;
161
+ let res;
162
+
163
+ val = $(e.target).val();
164
+ res = val.match(/(\d+),(\d+)/)
165
+
166
+ session.setFramerate(parseInt(res[1]), parseInt(res[2]));
167
+ });
168
+ }
169
+ }
170
+
171
+ function addIntegerForm(info) {
172
+ var $input;
173
+ var tics;
174
+
175
+ $input = $('<input>')
176
+ .attr('id', `control-${info["id"]}`)
177
+ .attr('type', 'range')
178
+ .attr('value', info["value"]);
179
+
180
+ $('div#controls')
181
+ .append($('<div>')
182
+ .addClass("mb-2")
183
+ .append($('<label>')
184
+ .addClass("form-label")
185
+ .attr("for", `control-${info["id"]}`)
186
+ .text(info["name"])
187
+ )
188
+ .append($('<div>')
189
+ .addClass('form-group ml-3')
190
+ .append($input)
191
+ )
192
+ );
193
+
194
+ tics = info["max"] - info["min"];
195
+
196
+ $input
197
+ .ionRangeSlider({
198
+ type: "single",
199
+ min: info["min"],
200
+ max: info["max"],
201
+ step: info["step"],
202
+ skin: "sharp",
203
+ keyboard: true,
204
+ hide_min_max: true,
205
+ grid: true,
206
+ grid_num: (tics > 10)? 10: tics,
207
+
208
+ onFinish: (data) => {
209
+ session.setControl(info["id"], data["from"])
210
+ }
211
+ });
212
+ }
213
+
214
+ function addBooleanForm(info) {
215
+ var $input;
216
+
217
+ $input = $('<input>')
218
+ .attr('id', `control-${info["id"]}`)
219
+ .attr('type', 'checkbox')
220
+ .attr('checked', info["value"])
221
+ .on('change', (e) => {
222
+ session.setControl(info["id"], $(e.target).is(':checked'));
223
+ });
224
+
225
+ $('div#controls')
226
+ .append($('<div>')
227
+ .addClass('pretty p-default my-2')
228
+ .append($input)
229
+ .append($('<div>')
230
+ .addClass('state p-primary')
231
+ .append($('<label>')
232
+ .addClass('form-label')
233
+ .attr("for", `control-${info["id"]}`)
234
+ .text(info["name"])
235
+ )
236
+ )
237
+ );
238
+ }
239
+
240
+ function addMenuForm(info) {
241
+ var $select;
242
+
243
+ $select = $('<select>')
244
+ .attr('id', `control-${info["id"]}`)
245
+ .addClass('form-control offset-1 col-11');
246
+
247
+ for (const [label, value] of Object.entries(info["items"])) {
248
+ $select
249
+ .append($('<option>')
250
+ .attr("value", value)
251
+ .text(label)
252
+ );
253
+ }
254
+
255
+ $select
256
+ .val(info["value"])
257
+ .on('change', (e) => {
258
+ session.setControl(info["id"], parseInt($(e.target).val()));
259
+ });
260
+
261
+ $('div#controls')
262
+ .append($('<div>')
263
+ .addClass("form-group")
264
+ .append($('<label>')
265
+ .addClass('form-label')
266
+ .attr("for", `control-${info["id"]}`)
267
+ .text(info["name"])
268
+ )
269
+ .append($select)
270
+ );
271
+ }
272
+
273
+ function setControlForm(info) {
274
+ $('div#controls').empty();
275
+
276
+ $('div#controls').append($("<hr>"));
277
+
278
+ info.forEach((entry) => {
279
+ if (entry["type"] == "boolean") {
280
+ addBooleanForm(entry);
281
+ }
282
+ });
283
+
284
+ $('div#controls').append($("<hr>"));
285
+
286
+ info.forEach((entry) => {
287
+ if (entry["type"] == "menu") {
288
+ addMenuForm(entry);
289
+ }
290
+ });
291
+
292
+ $('div#controls').append($("<hr>"));
293
+
294
+ info.forEach((entry) => {
295
+ if (entry["type"] == "integer") {
296
+ addIntegerForm(entry);
297
+ }
298
+ });
299
+ }
300
+
301
+ function resizePreviewCanvas() {
302
+ previewCanvas.width = imageWidth;
303
+ previewCanvas.height = imageHeight;
304
+
305
+ $('div#preview').getNiceScroll().resize();
306
+ }
307
+
308
+ function updatePreviewCanvas(img) {
309
+ previewGc.drawImage(img,
310
+ 0,
311
+ 0,
312
+ img.width,
313
+ img.height,
314
+ 0,
315
+ 0,
316
+ imageWidth,
317
+ imageHeight);
318
+ }
319
+
320
+ function startSession() {
321
+ session
322
+ .on('update_image', (data) => {
323
+ Utils.loadImageFromData(data)
324
+ .then((img) => {
325
+ updatePreviewCanvas(img);
326
+ })
327
+ .fail((error) => {
328
+ console.log(error);
329
+ });
330
+ })
331
+ .on('update_image_size', (width, height) => {
332
+ imageWidth = width;
333
+ imageHeight = height;
334
+
335
+ resizePreviewCanvas();
336
+ setFramerateSelect();
337
+ })
338
+ .on('update_framerate', (num, deno) => {
339
+ console.log("not implemented yet");
340
+ })
341
+ .on('update_control', (id, val) => {
342
+ console.log("not implemented yet");
343
+ });
344
+
345
+ session.start()
346
+ .then(() => {
347
+ return session.getCameraInfo();
348
+ })
349
+ .then((info) => {
350
+ setCameraInfo(info);
351
+
352
+ if (info["state"] == "BUSY") {
353
+ session.getIdentString()
354
+ .then((str) => {
355
+ setIdentString(str);
356
+ });
357
+
358
+ session.getConfig()
359
+ .then((info) => {
360
+ let rates;
361
+
362
+ capabilities = info["capabilities"];
363
+ controls = info["controls"];
364
+ imageWidth = info["image_width"];
365
+ imageHeight = info["image_height"];
366
+ framerate = info["framerate"];
367
+
368
+ sortCapabilities();
369
+
370
+ resizePreviewCanvas();
371
+ setImageSizeSelect();
372
+ setFramerateSelect();
373
+
374
+ setControlForm(info["controls"]);
375
+
376
+ $('div#config').getNiceScroll().resize();
377
+ });
378
+ }
379
+
380
+ return session.addNotifyRequest();
381
+ })
382
+ .fail((error) => {
383
+ console.log(error);
384
+ });
385
+ }
386
+
387
+ function setupScreen() {
388
+ $('div#preview')
389
+ .niceScroll({
390
+ enablekeyboard: true,
391
+ zindex: 100,
392
+ autohidemode: true,
393
+ horizrailenabled: false
394
+ });
395
+
396
+ $('div#config')
397
+ .niceScroll({
398
+ enablekeyboard: true,
399
+ zindex: 100,
400
+ autohidemode: true,
401
+ horizrailenabled: false
402
+ });
403
+ }
404
+
405
+ function setupButtons() {
406
+ $('button#save-config')
407
+ .on('click', () => {
408
+ session.saveConfig();
409
+ });
410
+
411
+ $('button#copy-url')
412
+ .on('click', () => {
413
+ let url;
414
+
415
+ url = `${location.protocol}//${location.host}/stream`;
416
+ Utils.copyToClipboard(url);
417
+ });
418
+ }
419
+
420
+ function initialize() {
421
+ session = new Session(WS_URL);
422
+ capabilities = null;
423
+ controls = null;
424
+ imageWidth = null;
425
+ imageHeight = null;
426
+ framerate = null;
427
+ previewCanvas = $('canvas#preview-canvas')[0];
428
+ previewGc = previewCanvas.getContext('2d');
429
+
430
+ setupScreen();
431
+ setupButtons();
432
+
433
+ startSession();
434
+ }
435
+
436
+ /*
437
+ * set handler for global objects
438
+ */
439
+
440
+ /* エントリーポイントの設定 */
441
+ $(window)
442
+ .on('load', () => {
443
+ let list = [
444
+ "/css/bootstrap.min.css",
445
+ "/css/ion.rangeSlider.min.css",
446
+ "/css/pretty-checkbox.min.css",
447
+
448
+ "/js/popper.min.js",
449
+ "/js/bootstrap.min.js",
450
+ "/js/msgpack.min.js",
451
+ "/js/jquery.nicescroll.min.js",
452
+ "/js/ion.rangeSlider.min.js",
453
+
454
+ "/css/main/style.scss",
455
+ "/js/msgpack-rpc.js",
456
+ "/js/session.js",
457
+ ];
458
+
459
+ Utils.require(list)
460
+ .done(() => {
461
+ initialize();
462
+ });
463
+ });
464
+
465
+ /* デフォルトではコンテキストメニューをOFF */
466
+ $(document)
467
+ .on('contextmenu', (e) => {
468
+ e.stopPropagation();
469
+ return false;
470
+ });
471
+
472
+ /* Drop&Dragを無効にしておく */
473
+ $(document)
474
+ .on('dragover', (e) => {
475
+ e.stopPropagation();
476
+ return false;
477
+ })
478
+ .on('dragenter', (e) => {
479
+ e.stopPropagation();
480
+ return false;
481
+ })
482
+ .on('drop', (e) => {
483
+ e.stopPropagation();
484
+ return false;
485
+ });
486
+ })();
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Sample for v4l2-ruby
3
+ *
4
+ * Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ if (!msgpack || !msgpack.rpc) {
8
+ throw "msgpack-lite.js and msgpack-rpc.js is not load yet"
9
+ }
10
+
11
+ (function () {
12
+ Session = class extends msgpack.rpc {
13
+ constructor(url) {
14
+ super(url)
15
+ }
16
+
17
+ hello() {
18
+ return this.remoteCall('hello');
19
+ }
20
+
21
+ addNotifyRequest() {
22
+ var args;
23
+
24
+ args = Array.prototype.slice.call(arguments);
25
+ if (args.length == 0) {
26
+ args = Object.keys(this.handlers);
27
+ }
28
+
29
+ return this.remoteCall('add_notify_request', ...args);
30
+ }
31
+
32
+ getCameraInfo() {
33
+ return this.remoteCall('get_camera_info');
34
+ }
35
+
36
+ getIdentString() {
37
+ return this.remoteCall('get_ident_string');
38
+ }
39
+
40
+ getConfig() {
41
+ return this.remoteCall('get_config');
42
+ }
43
+
44
+ setImageSize(width, height) {
45
+ return this.remoteCall('set_image_size', width, height);
46
+ }
47
+
48
+ setFramerate(num, deno) {
49
+ return this.remoteCall('set_framerate', num, deno);
50
+ }
51
+
52
+ setControl(id, val) {
53
+ return this.remoteCall('set_control', id, val);
54
+ }
55
+
56
+ saveConfig() {
57
+ return this.remoteCall('save_config');
58
+ }
59
+
60
+ }
61
+ })();
@@ -0,0 +1,91 @@
1
+ @charset "UTF-8";
2
+
3
+ body {
4
+ width: 100%;
5
+ height: 100%;
6
+
7
+ div.jumbotron {
8
+ padding: 2rem 1rem;
9
+ margin-bottom: 0px;
10
+
11
+ h3 {
12
+ font-weight: bold;
13
+ }
14
+
15
+ h5 {
16
+ font-weight: lighter;
17
+ }
18
+ }
19
+
20
+ div#switches {
21
+ width: 15%;
22
+ height: 100%;
23
+ text-align: center;
24
+
25
+ button {
26
+ width: 90%;
27
+ }
28
+ }
29
+
30
+ div#preview {
31
+ width: 60%;
32
+ height: 100%;
33
+
34
+ text-align: center;
35
+ position: relative;
36
+
37
+ div#preview-wrapper {
38
+ position: relative;
39
+
40
+ div#state {
41
+ position: absolute;
42
+ top: 0px;
43
+ left: 16px;
44
+ font-weight: bold;
45
+ font-size: 24pt;
46
+ }
47
+
48
+ canvas {
49
+ width: 100%;
50
+ border: solid 1px rgba(0, 0, 0, 0.3);
51
+ }
52
+ }
53
+ }
54
+
55
+ div#config {
56
+ width: 25%;
57
+ height: 100%;
58
+ overflow: hidden;
59
+
60
+ select {
61
+ font-size: 80%;
62
+ }
63
+
64
+ .irs {
65
+ font-family: monospace;
66
+ }
67
+
68
+ .irs--sharp {
69
+ .irs-bar {
70
+ background-color: cornflowerblue;
71
+ }
72
+
73
+ .irs-handle {
74
+ background-color: dodgerblue;
75
+ }
76
+
77
+ .irs-handle > i:first-child {
78
+ border-top-color: dodgerblue;
79
+ }
80
+
81
+ .irs-min, .irs-max {
82
+ visibility: hidden;
83
+ background-color: transparent;
84
+ }
85
+
86
+ .irs-from, .irs-to, .irs-single {
87
+ background-color: gray;
88
+ }
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,68 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <title>Camera</title>
5
+
6
+ <% if not $develop_mode %>
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Cache-Control" content="no-cache">
9
+ <meta http-equiv="Expires" content="-1">
10
+ <% end %>
11
+ </meta>
12
+
13
+ <script type="text/javascript" src="/js/jquery-3.4.1.min.js"></script>
14
+ <script type="text/javascript" src="/js/util.js"></script>
15
+ <script type="text/javascript" src="/js/main.js"></script>
16
+ </head>
17
+
18
+ <body>
19
+ <div class="jumbotron jumbotron-fluid">
20
+ <div class="container">
21
+ <h3 id="device-file"></h3>
22
+ <h5 id="device-name"></h3>
23
+ </div>
24
+ </div>
25
+
26
+ <div id="main-area" class="d-flex d-flex-row">
27
+ <div id="switches">
28
+ <div class="form-grounp mt-3">
29
+ <button class="btn btn-primary" disabled>STOP</button>
30
+ </div>
31
+
32
+ <div class="form-grounp mt-3">
33
+ <button id="save-config" class="btn btn-primary">SAVE</button>
34
+ </div>
35
+
36
+ <div class="form-grounp mt-3">
37
+ <button id="copy-url" class="btn btn-primary">COPY URL</button>
38
+ </div>
39
+ </div>
40
+
41
+ <div id="preview">
42
+ <div id="preview-wrapper" class="mx-3 my-3">
43
+ <div id="state"></div>
44
+ <canvas id="preview-canvas" width=320 height=240></canvas>
45
+ </div>
46
+ </div>
47
+
48
+ <div id="config">
49
+ <div class="mx-3 my-3">
50
+ <div class="form-group">
51
+ <label for="image-size">image size</label>
52
+ <select id="image-size"
53
+ class="form-control offset-1 col-11"></select>
54
+ </div>
55
+
56
+ <div class="form-group">
57
+ <label for="framerate">framerate</label>
58
+ <select id="framerate"
59
+ class="form-control offset-1 col-11"></select>
60
+ </div>
61
+
62
+ <div id="controls">
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </body>
68
+ </html>