ipcam 0.2.1 → 0.3.2
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.
- 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
|