ipcam 0.2.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/bin/ipcam +5 -1
- data/lib/ipcam/main.rb +47 -8
- data/lib/ipcam/version.rb +1 -1
- data/lib/ipcam/webserver.rb +23 -6
- data/lib/ipcam/websock.rb +27 -1
- data/resource/ipcam/js/main.js +36 -7
- data/resource/ipcam/js/session.js +7 -0
- data/resource/ipcam/views/main.erb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3673112cdc6871dd10631f4a67f4f5e1c93b3f6056af3acd6fbee4ef1ca3a8f8
|
4
|
+
data.tar.gz: 119f2126a4a68f850285af9f8fe06e32e08324ff4e17b1939ba0e7ad2f818122
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63874cacbf9f2b194b4829a168067b976b3f6692cdf2b1fdc807b1fe66ca1863af4f43857a6b0a89a606af4d050ddb2372a79c21da859a990ba1407951651096
|
7
|
+
data.tar.gz: 871dbee9300615fea6fd5b98e0053f0afe1e5311a1cfe615548a6ed189b45ac4b66d0e8f31d1a1d5dd15c595d8ff5489a1182e36ee7d6b4a4a5353cb1f8390aa
|
data/README.md
CHANGED
@@ -25,6 +25,7 @@ options:
|
|
25
25
|
--bind=ADDR
|
26
26
|
--port=PORT
|
27
27
|
-d, --database-file=FILE
|
28
|
+
-e, --extend-header
|
28
29
|
--log-file=FILE
|
29
30
|
--log-age=AGE
|
30
31
|
--log-size=SIZE
|
@@ -48,6 +49,9 @@ Then connect to port 4567 by http browser and operate. The accessible URLs are a
|
|
48
49
|
<dt>-d, --database-file=FILE</dt>
|
49
50
|
<dd>Specify the file name to save the camera setting value. by default, it tries to save to "~/.ipcam.db".</dd>
|
50
51
|
|
52
|
+
<dt>-e, --extend-header</dt>
|
53
|
+
<dd>Add extend header to part data (for debug).</dd>
|
54
|
+
|
51
55
|
<dt>--log-file=FILE</dt>
|
52
56
|
<dd></dd>
|
53
57
|
|
@@ -63,7 +67,7 @@ specify target device file (ex: /dev/video1). if omittedm, it will use "/dev/vi
|
|
63
67
|
|
64
68
|
## etc
|
65
69
|
### About image data
|
66
|
-
いらすとや(https://www.irasutoya.com
|
70
|
+
いらすとや (https://www.irasutoya.com) で配布されている『特撮映画のイラスト』(https://www.irasutoya.com/2018/12/blog-post_90.html) を改変して使用しています。
|
67
71
|
|
68
72
|
## License
|
69
73
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/ipcam
CHANGED
@@ -52,7 +52,7 @@ OptionParser.new { |opt|
|
|
52
52
|
}
|
53
53
|
|
54
54
|
opt.on('--port=PORT', Integer) { |val|
|
55
|
-
$
|
55
|
+
$http_port = val
|
56
56
|
$ws_port = val + 1
|
57
57
|
}
|
58
58
|
|
@@ -60,6 +60,10 @@ OptionParser.new { |opt|
|
|
60
60
|
$db_file = Pathname.new(val)
|
61
61
|
}
|
62
62
|
|
63
|
+
opt.on('-e', '--extend-header') { |val|
|
64
|
+
$extend_header = true
|
65
|
+
}
|
66
|
+
|
63
67
|
opt.on('--log-file=FILE') { |val|
|
64
68
|
log[:device] = val
|
65
69
|
}
|
data/lib/ipcam/main.rb
CHANGED
@@ -23,10 +23,10 @@ module IPCam
|
|
23
23
|
@camera = nil
|
24
24
|
@state = :STOP
|
25
25
|
@img_que = Thread::Queue.new
|
26
|
-
@cam_thr = Thread.new {camera_thread}
|
27
|
-
@snd_thr = Thread.new {sender_thread}
|
28
26
|
@clients = []
|
29
27
|
|
28
|
+
start_thread()
|
29
|
+
|
30
30
|
WebServer.start(self)
|
31
31
|
WebSocket.start(self)
|
32
32
|
|
@@ -34,19 +34,28 @@ module IPCam
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def stop
|
37
|
-
|
38
|
-
|
39
|
-
@snd_thr.raise(Stop)
|
40
|
-
@snd_thr.join
|
41
|
-
|
37
|
+
stop_thread()
|
42
38
|
|
43
39
|
WebServer.stop
|
44
40
|
EM.stop
|
45
41
|
end
|
46
42
|
|
43
|
+
def start_thread
|
44
|
+
@cam_thr = Thread.new {camera_thread}
|
45
|
+
end
|
46
|
+
private :start_thread
|
47
|
+
|
48
|
+
def stop_thread
|
49
|
+
@cam_thr.raise(Stop)
|
50
|
+
@cam_thr.join
|
51
|
+
@cam_thr = nil
|
52
|
+
end
|
53
|
+
private :stop_thread
|
54
|
+
|
47
55
|
def restart_camera
|
48
56
|
@cam_thr.raise(Restart)
|
49
57
|
end
|
58
|
+
private :restart_camera
|
50
59
|
|
51
60
|
def select_capabilities(cam)
|
52
61
|
ret = cam.frame_capabilities(:MJPEG).instance_eval {
|
@@ -235,6 +244,8 @@ module IPCam
|
|
235
244
|
raise("#{$target} is not support Motion-JPEG")
|
236
245
|
end
|
237
246
|
|
247
|
+
snd_thr = Thread.new {sender_thread}
|
248
|
+
|
238
249
|
begin
|
239
250
|
@mutex.synchronize {
|
240
251
|
@config = load_settings()
|
@@ -271,6 +282,10 @@ module IPCam
|
|
271
282
|
ensure
|
272
283
|
@camera&.close
|
273
284
|
@camera = nil
|
285
|
+
|
286
|
+
snd_thr&.raise(Stop)
|
287
|
+
snd_thr&.join
|
288
|
+
|
274
289
|
$logger.info("main") {"camera thread stop"}
|
275
290
|
end
|
276
291
|
private :camera_thread
|
@@ -285,12 +300,12 @@ module IPCam
|
|
285
300
|
}
|
286
301
|
|
287
302
|
rescue Stop
|
288
|
-
$logger.info("main") {"accept stop request"}
|
289
303
|
@clients.each {|c| c[:que] << nil}
|
290
304
|
|
291
305
|
ensure
|
292
306
|
$logger.info("main") {"sender thread stop"}
|
293
307
|
end
|
308
|
+
private:sender_thread
|
294
309
|
|
295
310
|
def get_camera_info
|
296
311
|
@mutex.synchronize {
|
@@ -370,5 +385,29 @@ module IPCam
|
|
370
385
|
def remove_client(que)
|
371
386
|
@clients.reject! {|c| c[:que] == que}
|
372
387
|
end
|
388
|
+
|
389
|
+
def start_camera
|
390
|
+
raise("state violation") if @state != :STOP and @state != :ABORT
|
391
|
+
|
392
|
+
start_thread()
|
393
|
+
end
|
394
|
+
|
395
|
+
def stop_camera
|
396
|
+
raise("state violation") if @state != :ALIVE
|
397
|
+
|
398
|
+
stop_thread()
|
399
|
+
end
|
400
|
+
|
401
|
+
def alive?
|
402
|
+
@state == :ALIVE
|
403
|
+
end
|
404
|
+
|
405
|
+
def abort?
|
406
|
+
@state == :ABORT
|
407
|
+
end
|
408
|
+
|
409
|
+
def stop?
|
410
|
+
@state == :STOP
|
411
|
+
end
|
373
412
|
end
|
374
413
|
end
|
data/lib/ipcam/version.rb
CHANGED
data/lib/ipcam/webserver.rb
CHANGED
@@ -66,6 +66,9 @@ module IPCam
|
|
66
66
|
end
|
67
67
|
|
68
68
|
get "/stream" do
|
69
|
+
halt 500 if app.abort?
|
70
|
+
halt 404 if app.stop?
|
71
|
+
|
69
72
|
boundary = SecureRandom.hex(20)
|
70
73
|
queue = Thread::Queue.new
|
71
74
|
|
@@ -83,18 +86,32 @@ module IPCam
|
|
83
86
|
port << "\r\n"
|
84
87
|
app.add_client(queue)
|
85
88
|
|
89
|
+
fc = 0
|
90
|
+
|
86
91
|
loop {
|
87
92
|
data = queue.deq
|
88
93
|
break if not data
|
89
94
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
95
|
+
if $extend_header
|
96
|
+
port << <<~EOT.b
|
97
|
+
--#{boundary}
|
98
|
+
Content-Type: image/jpeg\r
|
99
|
+
Content-Length: #{data.bytesize}\r
|
100
|
+
X-Frame-Number: #{fc}
|
101
|
+
X-Timestamp: #{(Time.now.to_f * 1000).round}
|
102
|
+
\r
|
103
|
+
EOT
|
104
|
+
else
|
105
|
+
port << <<~EOT.b
|
106
|
+
--#{boundary}
|
107
|
+
Content-Type: image/jpeg\r
|
108
|
+
Content-Length: #{data.bytesize}\r
|
109
|
+
\r
|
110
|
+
EOT
|
111
|
+
end
|
96
112
|
|
97
113
|
port << data
|
114
|
+
fc += 1
|
98
115
|
|
99
116
|
# データ詰まりを防ぐ為にキューをクリア
|
100
117
|
queue.clear
|
data/lib/ipcam/websock.rb
CHANGED
@@ -106,7 +106,7 @@ module IPCam
|
|
106
106
|
addr = $bind_addr
|
107
107
|
end
|
108
108
|
|
109
|
-
return "tcp://#{addr}:#{$
|
109
|
+
return "tcp://#{addr}:#{$ws_port}"
|
110
110
|
end
|
111
111
|
private :bind_url
|
112
112
|
|
@@ -376,5 +376,31 @@ module IPCam
|
|
376
376
|
return :OK
|
377
377
|
end
|
378
378
|
remote_public :save_config
|
379
|
+
|
380
|
+
#
|
381
|
+
# カメラの撮影開始
|
382
|
+
#
|
383
|
+
# @return [:OK] 固定値
|
384
|
+
#
|
385
|
+
def start_camera(df)
|
386
|
+
EM.defer {
|
387
|
+
@app.start_camera()
|
388
|
+
df.resolve(:OK)
|
389
|
+
}
|
390
|
+
end
|
391
|
+
remote_async :start_camera
|
392
|
+
|
393
|
+
#
|
394
|
+
# カメラの撮影停止
|
395
|
+
#
|
396
|
+
# @return [:OK] 固定値
|
397
|
+
#
|
398
|
+
def stop_camera(df)
|
399
|
+
EM.defer {
|
400
|
+
@app.stop_camera()
|
401
|
+
df.resolve(:OK)
|
402
|
+
}
|
403
|
+
end
|
404
|
+
remote_async :stop_camera
|
379
405
|
end
|
380
406
|
end
|
data/resource/ipcam/js/main.js
CHANGED
@@ -27,6 +27,8 @@
|
|
27
27
|
var previewCanvas;
|
28
28
|
var previewGc;
|
29
29
|
|
30
|
+
var cameraState;
|
31
|
+
|
30
32
|
/*
|
31
33
|
* declar functions
|
32
34
|
*/
|
@@ -40,24 +42,28 @@
|
|
40
42
|
var bg;
|
41
43
|
var lb;
|
42
44
|
|
45
|
+
cameraState = state;
|
46
|
+
|
43
47
|
switch (state) {
|
44
48
|
case "STOP":
|
45
49
|
default:
|
46
50
|
fg = "royalblue";
|
47
|
-
bg = "
|
51
|
+
bg = "rgba(160, 160, 160, 0.5)";
|
48
52
|
lb = "START";
|
53
|
+
clearPreviewCanvas();
|
49
54
|
break;
|
50
55
|
|
51
56
|
case "ALIVE":
|
52
57
|
fg = "springgreen";
|
53
|
-
bg = "
|
58
|
+
bg = "rgba(0, 0, 0, 0.5)";
|
54
59
|
lb = "STOP";
|
55
60
|
break;
|
56
61
|
|
57
62
|
case "ABORT":
|
58
63
|
fg = "crimson";
|
59
|
-
bg = "
|
64
|
+
bg = "rgba(160, 160, 160, 0.5)";
|
60
65
|
lb = "RECOVER";
|
66
|
+
clearPreviewCanvas();
|
61
67
|
break;
|
62
68
|
}
|
63
69
|
|
@@ -332,6 +338,8 @@
|
|
332
338
|
previewCanvas.width = imageWidth;
|
333
339
|
previewCanvas.height = imageHeight;
|
334
340
|
|
341
|
+
clearPreviewCanvas();
|
342
|
+
|
335
343
|
setTimeout(() => {
|
336
344
|
$('div#preview').getNiceScroll().resize();
|
337
345
|
}, 0);
|
@@ -433,14 +441,12 @@
|
|
433
441
|
});
|
434
442
|
|
435
443
|
} else {
|
436
|
-
$('h6#device-name').text(null);
|
437
|
-
|
438
444
|
$('select#image-size > option').remove();
|
439
445
|
$('select#framerate > option').remove();
|
440
446
|
$('div#controls').empty();
|
441
|
-
|
442
|
-
setupScreenSize();
|
443
447
|
}
|
448
|
+
|
449
|
+
$('button#action').prop('disabled', false);
|
444
450
|
}
|
445
451
|
|
446
452
|
function startSession() {
|
@@ -501,6 +507,23 @@
|
|
501
507
|
}
|
502
508
|
|
503
509
|
function setupButtons() {
|
510
|
+
$('button#action')
|
511
|
+
.on('click', () => {
|
512
|
+
clearPreviewCanvas();
|
513
|
+
$('button#action').prop('disabled', true);
|
514
|
+
|
515
|
+
switch (cameraState) {
|
516
|
+
case "STOP":
|
517
|
+
case "ABORT":
|
518
|
+
session.startCamera();
|
519
|
+
break;
|
520
|
+
|
521
|
+
case "ALIVE":
|
522
|
+
session.stopCamera();
|
523
|
+
break;
|
524
|
+
}
|
525
|
+
});
|
526
|
+
|
504
527
|
$('button#save-config')
|
505
528
|
.on('click', () => {
|
506
529
|
session.saveConfig();
|
@@ -516,6 +539,11 @@
|
|
516
539
|
});
|
517
540
|
}
|
518
541
|
|
542
|
+
function clearPreviewCanvas() {
|
543
|
+
previewGc.fillStyle = "black";
|
544
|
+
previewGc.fillRect(0, 0, previewCanvas.width, previewCanvas.height);
|
545
|
+
}
|
546
|
+
|
519
547
|
function initialize() {
|
520
548
|
session = new Session(WS_URL);
|
521
549
|
capabilities = null;
|
@@ -529,6 +557,7 @@
|
|
529
557
|
|
530
558
|
setupScreen();
|
531
559
|
setupButtons();
|
560
|
+
clearPreviewCanvas();
|
532
561
|
|
533
562
|
startSession();
|
534
563
|
}
|
@@ -26,7 +26,7 @@
|
|
26
26
|
<div id="main-area" class="d-flex d-flex-row">
|
27
27
|
<div id="switches">
|
28
28
|
<div class="form-grounp mt-3">
|
29
|
-
<button id="action" class="btn btn-primary"
|
29
|
+
<button id="action" class="btn btn-primary">STOP</button>
|
30
30
|
</div>
|
31
31
|
|
32
32
|
<div class="form-grounp mt-3">
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ipcam
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hirosho Kuwagata
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-06-
|
11
|
+
date: 2019-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|