Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ include
lib
pyvenv.cfg
__pycache__
log.txt
log.txt
latest.jpg
7 changes: 5 additions & 2 deletions camera.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import subprocess

LATEST_IMAGE = 'latest.jpg'

# Each preview frame is about 180 KB
READ_SIZE = 100 * 1024
SOI = b'\xff\xd8'
EOI = b'\xff\xd9'

# 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]
Expand All @@ -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'))
Expand Down
127 changes: 114 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ python /path/to/repo/server.py
> 카메라에서 960 x 640 해상도의 MJPEG 영상을 받아와 HTTP 스트림으로 제공.
> HTML의 경우 `<img src="/preview" width="640" height="480">` 등으로 렌더링할 수 있음.

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

Expand All @@ -50,32 +55,121 @@ Content-Type: image/jpeg

<jpeg data here>
```

<br>

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)
```

<br>

Status Code | MIME Type | Description
:-:|:-:|:-:
504 | `text/plain` | 503 이외의 카메라 연결 상태로 인한 촬영 실패

```
HTTP/1.1 504 GATEWAY TIMEOUT
Content-Type: text/plain

*** Error: No camera found. ***
```

<br>

Status Code | MIME Type | Description
:-:|:-:|:-:
500 | `text/plain` | 카메라 서버 프로그램 버그로 인한 오류

```
HTTP/1.1 500 INTERNAL SERVER ERROR
Content-Type: text/html

<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>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.</p>
```

<br>


### 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

<jpeg data here>
```

<br>

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') ***
```

<br>

Status Code | MIME Type | Description
:-:|:-:|:-:
500 | `text/plain` | 카메라 서버 프로그램 버그로 인한 오류

```
HTTP/1.1 500 INTERNAL SERVER ERROR
Content-Type: text/html

<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>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.</p>
```

<br>

### 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

Expand All @@ -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
Expand All @@ -101,6 +200,8 @@ Model Port
Canon EOS 700D usb:001,007
```

<br>

## Reference
- https://blog.miguelgrinberg.com/post/video-streaming-with-flask
- http://www.gphoto.org/doc/manual/ref-gphoto2-cli.html
Expand Down
9 changes: 8 additions & 1 deletion server.py
Original file line number Diff line number Diff line change
@@ -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__)
Expand Down Expand Up @@ -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()
Expand Down