Theta
Pythonista
写真

RICOH Theta S に Pythonistaからアクセスしたい

More than 1 year has passed since last update.

この記事は Pythonista Advent Calendar 2017 の8日目の記事です。
Theta S に Pythonista 経由でアクセスして写真を撮る方法を紹介します。

📷 概要

iPhoneを持ち歩くようになってから、コンデジを連れ出す機会が少なくなりましたが、
ThetaやGoProなど単機能特化型カメラはiPhoneでカバーできない写真が撮れるので、よく持ち歩いています。

Theta Sには公式のアプリもあって使いやすいのですが、
ファイル共有に難があり、写真の加工は他のアプリに任せる必要があることの2点に、不便さを感じます。
そこで、PythonistaとTheta APIを使って必要最低限の機能を実装して見ました。

APIは下記を参考にしています。
https://developers.theta360.com/ja/docs/v2.1/api_reference/

🎒 準備

  • Pythonista
  • RICOH Theta S以降 (無印のThetaとapiが異なります)

最近発売されたTheta Vも同じ構成で動くと思います。
requests モジュールに依存しますが、親切な Pythonista にはプリインストールされているので、
準備はこれだけ。

💻 コードの中身

コードを書き始める前に、流れを把握します。

静止画を1枚撮るまでの流れ

  1. APIバージョンの指定
  2. 撮影前状態の取得
  3. プロパティの取得・設定
  4. 静止画撮影
  5. ファイル保存確認
  6. ファイル取得

https://developers.theta360.com/ja/docs/v2.1/api_reference/getting_started.html

例えば、静止画のプロパティ設定をPOSTするには、

property
POST /osc/commands/execute
{
    "name": "camera.setOptions",
    "parameters": {
        "options": {
            "fileFormat": {
                "type": "jpeg",
                "width": 2048,
                "height": 1024
            }
        }
    }
}

と書きますが、これを Python と request で書くと

property
import request

param = {
    "name": "camera.setOptions",
    "parameters": {
        "options": {
            "fileFormat": {
                "type": "jpeg",
                "width": 2048,
                "height": 1024
            }
        }
    }
}
request.post("http://192.168.1.1:80/osc/commands/execute", data=param) 

になります。

ラッピングしたコード

流れに沿って、Python でラップしていきます。

wrapper
import io
import json
import inspect

import requests
from PIL import Image

cam = 'http://192.168.1.1:80'
url = cam+'/osc/'
exe = url+'commands/execute'


def info():
    r=requests.get(url+inspect.currentframe().f_code.co_name)
    return r.json()


def state():
    r=requests.post(url+inspect.currentframe().f_code.co_name)
    return r.json()


def startSession():
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name),
'parameters':{}}

    r=requests.post(exe, data=json.dumps(j))
    return r.json()


def closeSession(id):
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name),
'parameters':{'sessionId':'{}'.format(id)}}

    r=requests.post(exe, data=json.dumps(j))
    return r.json()


def setOptionsid(id):
    j={'name': 'camera.setOptions',
    'parameters':{'sessionId':id,'options':{'clientVersion':2}}}
    r=requests.post(exe,data=json.dumps(j))
    return r.json()


def getCameraOption():
    j={'name': 'camera.{}'.format(inspect.currentframe().f_code.co_name),
    'parameters':{'optionNames':['fileFormat','fileFormatSupport']}}
    r=requests.post(exe,data=json.dumps(j))
    return r.json()


def listFiles():
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
    r=requests.post(exe,data=json.dumps(j))
    return r.json()


def getLivePreview():
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
    r=requests.post(exe,data=json.dumps(j))
    return r.json()


def setOptions(type,width,height):
    j={'name': 'camera.{}'.format(inspect.currentframe().f_code.co_name),
    'parameters':{'options':{'fileFormat':{'type':type,'width':width,'height':height}}}}

    r=requests.post(exe, data=json.dumps(j))
    return r.json()


def takePicture():
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}

    r=requests.post(exe, data=json.dumps(j))
    return r.json()


def startCapture():
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}

    r=requests.post(exe,data=json.dumps(j))
    return r.json()


def stopCapture():
    j={'name':'camera.{}'.format(inspect.currentframe().f_code.co_name)}
    r=requests.post(exe,data=json.dumps(j))
    return r.json()


def checkForUpdates(fig):
    j={'stateFingerprint':fig}
    r=requests.post(url+inspect.currentframe().f_code.co_name, data=json.dumps((j)))
    return r.json()


def reset():
    r=requests.post(exe+inspect.currentframe().f_code.co_name)


def image():
    while len(state()['state']['_latestFileUrl']) > 0:
        break

    r = requests.get(state()['state']['_latestFileUrl'])
    return Image.open(io.BytesIO(r.content))


def main():

    # session
    d = startSession()
    if d['state'] == 'error':
        return closeSession('SID_001')

    # api and options
    k=d['results']['sessionId']
    r=setOptionsid(k)

    while r['state'] == 'done':
        break   
    setOptions('jpeg',2048,1024)

    # take pictures
    d = checkForUpdates(state()['fingerprint'])
    tmp = d['stateFingerprint']

    t = takePicture()
    while tmp != state()['fingerprint']:
        break

    # image
    img=image()

    # session
    closeSession(k)

    return img


if __name__ == '__main__':
    main()

ui モジュールでカメラ設定を入力したり、撮った写真を PIL で画像加工したりする前の
最小限のコードになりました。

📚 まとめ

バッテリー同梱型の Python を生活に持ち込んで、日常の小さな問題を解決できると楽しいです。

今回はカメラのコントロールにiPhoneとPythonistaを使って見ましたが、
IOT 機器のコントロールに応用出来るだろうし、Pythonista には Bluetooth モジュールが入っているので、
iPhoneを汎用リモコン化することもできそうですね。機会があれば試して見たいです。

最後までお読みいただきありがとうございました。どなたかの参考になれば嬉しいです。