iPhoneのカメラで本のISBNを取得し指定した図書館の在書を検索する

2020/06/25 コードの一部修正
#動機
いい本を見つけたとき、その本が図書館にあるかどうか調べたかった。しかし、調べたい図書館のウェブサイトでいちいち検索するのは手間がかかる。そして調べたい図書館が複数ヶ所ある場合はなおさら手間がかかる。そこでそれを手軽にできるように自動化しようと考えた。
#実装方法
iPhoneのカメラで本の裏側のについているバーコードを読み取る。多くの本はバーコードが2個ついているが、上方のバーコード(ISBN13)を読み取る。そして取得したISBNを[カーリル](https://calil.jp/doc/api.html)のAPIに投げ、指定した図書館にその本があるかどうか検索する。これをすべてiPhoneのアプリであるPythonista上で実装する。

```
Directory |ー main.py
          |ー camera.py
```

#コード
##main.py
```python:main.py
import requests
import json
import time
import re
import sys
import camera


CALIL_API_KEY = 'Your API Key'
LIBRARYS = ['Univ_Aizu','Fukushima_Aizuwakamatsu']

def main():
    #QRコードスキャナーでISBNを取得
    ISBN = camera.camera()
    #LIBRARYSのList上にある図書館全てで検索する
    for lib_key in LIBRARYS:
        search_books(ISBN,lib_key)

def search_books(ISBN,lib_key):
    print('searching at the library you like...\n')
    #いくつかの引数を加えたURL 
    url = 'http://api.calil.jp/check?appkey{{0}}&isbn={1}&systemid={2}&format=json'.format(CALIL_API_KEY,ISBN,lib_key)
    rq = requests.get(url)
    #jsonとしてloadするための前処理
    prejsn = rq.text.strip('callback(').strip(');')
    try:
        #strip関数を使いcallback(と);を取り除く,I
        jsn = json.loads(prejsn)
        #session部のみを抽出する
        session = jsn['session']
    except json.decoder.JSONDecodeError:
        print('Server Error')
        #session部がない場合はサーバーエラーとして処理
        #その場合はプログラム終了
        sys.exit()
        
    try:
        #サーバーからのjsonファイルを解析
        if jsn['books'][ISBN][lib_key]['status'] == 'Running':
            print('loading now...\n')
            #Running Stateの場合はサーバーの処理待ち
            #ゆえに待機時間5秒をいれる
            time.sleep(5)
    
        #Running Stateの場合はサーバーの処理結果を問い合わせるためのURLを取得
        reserveurl = jsn['books'][ISBN][lib_key]['reserveurl']
    
        if reserveurl:
            #reserveurlがNoneではない場合
            bookexist = jsn['books'][ISBN][lib_key]['libkey']
            #所在している図書館と貸出状況のみ抽出
            lst = list(filter(lambda x:x,re.findall(r'[\w]*',str(bookexist))))
            #result=[図書館名,貸出状況]
            for num,_ in enumerate(lst):
                #図書館名と貸出状況は２つで組である
                if num % 2 == 0:
                    print('{} {}'.format(lst[num],lst[num + 1]))
                
        else:
            print('The book does not exist.')
            
    except KeyError:
        #要求したKeyがない場合
        print(jsn)
        print('ERROR')
    
if __name__ == '__main__':
   main()
```
##camera.py
Pythonista上でQRコードスキャナーを動かすためのコードは[ここから](https://forum.omz-software.com/topic/2779/barcode-qr-code-camera-scanner-demo-objc_util)拝借した。(一部改変)

```python:camera.py
# coding: utf-8
# Barcode scanner demo for Pythonista
# Based on http://www.infragistics.com/community/blogs/torrey-betts/archive/2013/10/10/scanning-barcodes-with-ios-7-objective-c.aspx

from objc_util import *
from ctypes import c_void_p
import ui
import sound

found_codes = set()
main_view = None

AVCaptureSession = ObjCClass('AVCaptureSession')
AVCaptureDevice = ObjCClass('AVCaptureDevice')
AVCaptureDeviceInput = ObjCClass('AVCaptureDeviceInput')
AVCaptureMetadataOutput = ObjCClass('AVCaptureMetadataOutput')
AVCaptureVideoPreviewLayer = ObjCClass('AVCaptureVideoPreviewLayer')
dispatch_get_current_queue = c.dispatch_get_current_queue
dispatch_get_current_queue.restype = c_void_p

def captureOutput_didOutputMetadataObjects_fromConnection_(_self, _cmd, _output, _metadata_objects, _conn):
	objects = ObjCInstance(_metadata_objects)
	for obj in objects:
		s = str(obj.stringValue())
		if s not in found_codes:
			found_codes.add(s)
			sound.play_effect('digital:PowerUp7')
		main_view['label'].text = 'Last scan: ' + s
		session.stopRunning()
		delegate.release()
		session.release()
		output.release()
		if found_codes:
			main_view.close()
			

MetadataDelegate = create_objc_class('MetadataDelegate', methods=[captureOutput_didOutputMetadataObjects_fromConnection_], protocols=['AVCaptureMetadataOutputObjectsDelegate'])

@on_main_thread
def camera():
	global main_view
	global session
	global delegate
	global output
	global main_view
	delegate = MetadataDelegate.new()
	main_view = ui.View(frame=(0, 0, 400, 400))
	main_view.name = 'Barcode Scanner'
	session = AVCaptureSession.alloc().init()
	device = AVCaptureDevice.defaultDeviceWithMediaType_('vide')
	_input = AVCaptureDeviceInput.deviceInputWithDevice_error_(device, None)
	if _input:
		session.addInput_(_input)
	else:
		print('Failed to create input')
		return
	output = AVCaptureMetadataOutput.alloc().init()
	queue = ObjCInstance(dispatch_get_current_queue())
	output.setMetadataObjectsDelegate_queue_(delegate, queue)
	session.addOutput_(output)
	output.setMetadataObjectTypes_(output.availableMetadataObjectTypes())
	prev_layer = AVCaptureVideoPreviewLayer.layerWithSession_(session)
	prev_layer.frame = ObjCInstance(main_view).bounds()
	prev_layer.setVideoGravity_('AVLayerVideoGravityResizeAspectFill')
	ObjCInstance(main_view).layer().addSublayer_(prev_layer)
	label = ui.Label(frame=(0, 0, 400, 30), flex='W', name='label')
	label.background_color = (0, 0, 0, 0.5)
	label.text_color = 'white'
	label.text = 'Nothing scanned yet'
	label.alignment = ui.ALIGN_CENTER
	main_view.add_subview(label)
	session.startRunning()
	main_view.present('sheet')
	#the 64 line code waiting code
	main_view.wait_modal()
	return(''.join(found_codes))
	

if __name__ == '__main__':
	camera()
```