diff --git a/.gitignore b/.gitignore index 629b92a..7af35eb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ include lib pyvenv.cfg __pycache__ -log.txt \ No newline at end of file +log.txt +latest.jpg \ No newline at end of file diff --git a/camera.py b/camera.py index 1ec1c98..892099a 100644 --- a/camera.py +++ b/camera.py @@ -1,5 +1,7 @@ import subprocess +LATEST_IMAGE = 'latest.jpg' + # Each preview frame is about 180 KB READ_SIZE = 100 * 1024 SOI = b'\xff\xd8' @@ -7,8 +9,8 @@ # Kill existing gphoto2 processes to properly detect camera def reset(): - command = ['pkill', '-9', '-f', 'gphoto2'] - subprocess.run(command) + subprocess.run(['pkill', '-9', '-f', 'gphoto2']) + subprocess.run(['rm', LATEST_IMAGE]) def command(arg): command = ['gphoto2', arg] @@ -21,6 +23,7 @@ def capture_image(): command = ['gphoto2', '--capture-image-and-download', '--stdout'] result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode == 0: + subprocess.run(['tee', LATEST_IMAGE], input=result.stdout) return result.stdout; else: raise IOError(result.stderr.decode('utf-8')) diff --git a/readme.md b/readme.md index 0061a17..e1d5736 100644 --- a/readme.md +++ b/readme.md @@ -32,12 +32,17 @@ python /path/to/repo/server.py > 카메라에서 960 x 640 해상도의 MJPEG 영상을 받아와 HTTP 스트림으로 제공. > HTML의 경우 `` 등으로 렌더링할 수 있음. -MIME Type: `multipat/x-mixed-replace` +#### Parameter -Parameter: 요청 시 선택적으로 `?timeout=N`을 제공하면 N초 만큼 촬영 후 종료. +요청 시 선택적으로 `?timeout=N`을 제공하면 N초 만큼 촬영 후 종료. -Response Example: -``` +#### Response + +Status Code | MIME Type | Description +:-:|:-:|:-: +200 | `multipat/x-mixed-replace` | 영상 촬영 시작 성공 + +```text HTTP/1.1 200 OK Content-Type: multipart/x-mixed-replace; boundary=frame @@ -50,32 +55,121 @@ Content-Type: image/jpeg ``` + +
+ +Status Code | MIME Type | Description +:-:|:-:|:-: +503 | `text/plain` | 카메라 연결 상태로 인한 촬영 실패. + +``` +HTTP/1.1 503 SERVICE UNAVAILABLE +Content-Type: text/plain + +Capturing preview frames as movie to stdout. Press Ctrl-C to abort. + +*** Error *** +An error occurred in the io-library ('Could not claim the USB device"): Could not claim interface 0 (Device or resource busy). Make sure no other program (gvfs-gphoto2-volume-monitor) or kernel module (such as sdc2xx, stv680, spca50x) is using the device and you have read/write access to the device. +ERROR: Movie capture error... Exiting. +Movie capture finished (0 frames) +``` + +
+ +Status Code | MIME Type | Description +:-:|:-:|:-: +504 | `text/plain` | 503 이외의 카메라 연결 상태로 인한 촬영 실패 + +``` +HTTP/1.1 504 GATEWAY TIMEOUT +Content-Type: text/plain + +*** Error: No camera found. *** +``` + +
+ +Status Code | MIME Type | Description +:-:|:-:|:-: +500 | `text/plain` | 카메라 서버 프로그램 버그로 인한 오류 + +``` +HTTP/1.1 500 INTERNAL SERVER ERROR +Content-Type: text/html + + + +500 Internal Server Error +

Internal Server Error

+

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

+``` +
+ ### GET `/capture` > 카메라에서 2592 x 1728 해상도의 JPEG 사진을 즉시 촬영하여 전송. > 초점을 맞추고 셔터 소리가 날 때까지 약 2초의 딜레이가 있음. -MIME Type: image/jpeg +#### Response + +Status Code | MIME Type | Description +:-:|:-:|:-: +200 | `image/jpeg`| 사진 촬영 성공 -Response Example: ``` HTTP/1.1 200 OK Content-Type: image/jpeg ``` + +
+ +Status Code | MIME Type | Description +:-:|:-:|:-: +503 | `text/plain` | 카메라 연결 상태로 인한 촬영 실패. + +``` +HTTP/1.1 503 SERVICE UNAVAILABLE +Content-Type: text/plain + +*** Error *** +Could not detect any camera +*** Error (-105: 'Unknown model') *** +``` + +
+ +Status Code | MIME Type | Description +:-:|:-:|:-: +500 | `text/plain` | 카메라 서버 프로그램 버그로 인한 오류 + +``` +HTTP/1.1 500 INTERNAL SERVER ERROR +Content-Type: text/html + + + +500 Internal Server Error +

Internal Server Error

+

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

+``` +
### GET `/reset` > 카메라 상태를 초기화. 카메라 상태로 인한 촬영 오류 시에 유용할 수 있음. -MIME Type: text/html +#### Response -Response Example: -``` +Status Code | MIME Type | Description +:-:|:-:|:-: +200 | `text/html`| 카메라 리셋 성공 + +```text HTTP/1.1 200 OK Content-Type: text/html @@ -85,13 +179,18 @@ Camera has been reset. ### GET `/cmd` -> `gphoto2` 명령행 인자를 `arg` 쿼리파람으로 전송하고, 텍스트로 응답. +> `gphoto2` 명령행 인자를 `arg` 쿼리파람으로 전송하고, stdout 출력을 텍스트로 응답. + +#### Parameter -MIME Type: text/plain +요청 시 `?arg=` 뒤에 `gphoto2` 커맨드와 호환되는 1개의 인자를 제공해야 함. -Parameter: 요청 시 `?arg=` 뒤에 `gphoto2` 커맨드와 호환되는 1개의 인자를 제공해야함. +#### Response + +Status Code | MIME Type | Description +:-:|:-:|:-: +200 | `text/plain` | gphoto2 명령 실행 완료 -Response Example: `/cmd?arg=--auto-detect` ``` HTTP/1.1 200 OK Content-Type: text/plain @@ -101,6 +200,8 @@ Model Port Canon EOS 700D usb:001,007 ``` +
+ ## Reference - https://blog.miguelgrinberg.com/post/video-streaming-with-flask - http://www.gphoto.org/doc/manual/ref-gphoto2-cli.html diff --git a/server.py b/server.py index d5fd817..d51a191 100755 --- a/server.py +++ b/server.py @@ -1,6 +1,6 @@ #!/bin/python -from flask import Flask, Response, render_template, request +from flask import Flask, Response, render_template, send_file, request import camera app = Flask(__name__) @@ -34,6 +34,13 @@ def capture(): except IOError as err: return Response(str(err), 503, mimetype='text/plain') +@app.route('/capture/latest', methods=["GET"]) +def latest_capture(): + try: + return send_file(camera.LATEST_IMAGE, mimetype='image/jpeg') + except IOError as err: + return Response(str(err), 503, mimetype='text/plain') + @app.route('/reset', methods=["GET"]) def reset(): camera.reset()