deta.shで画像の表示(deta.sh Drive編)

deta.sh - 画像の表示(Drive)

deta.sh - URLを指定しないで画像を表示する

deta.shでMicroを使うとき、テンポラリーファイルを作ろうとするとエラーになってしまいます。マニュアルをみると/tmpにしかデータが作れない、read only systemとなっています。

となると、imgのsrcでurlを指定して画像表示ができないことになります。/tmpを汚しては申し訳ないので、なんとかならないか試行錯誤が始まりました

生徒発案でBase64コードをそのまま表示する方法がわかる

一緒に授業をしていた生徒もdeta.shを実験していたのですが、やはり同じところで躓いてある方法を発見しました。

画像データをbase64に変換してデータベースに保存しておき、それを画面に表示する方法です

普通は srcにurlを指定する
<img src="abc.jpg" alt="">

urlが使えないのでbase64に変換して、そのコードをそのまま入れる
<img src="data:image/jpeg;base64,sdfg8dppga0u...dfhgh" alt="">

ただこの方法だとデータベースが大きくなるのでDriveの画像ファイルをその場で変換してbase64で表示するようにしてみました。

Driveの画像ファイルを表示するサンプル

DriveにuploadしたDriveのファイルを名前指定で表示するサンプルを作ってみましょう。

htmlは画像ファイル名を入れるindex.htmlと表示するdownload.htmlそして、それをコントロールするmain.pyで作成します。いつものようにbootstrapを使ってます

新規Microを作成する
deta new --python --name drivegettest

フォルダーに以下のファイルを作成していきます
main.py
requirements.txt
\templates
     index.html 
     download.html

index.html

<!DOCTYPE html>
	<html lang="ja">
		<head>
			<meta charset="UTF-8">
			<title>formTest</title>
			<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
		</head>
		<body>
			<h1 class="display-1 text-primary">deta.sh Driveからのダウンロード</h1>
			<div class="m-5 border">
				<p>Drive(photos)に保存したファイル名を入力してください</p>
				<form action="/download" method="post">
					<div class="mb-3 row">
						<div class="col-sm-4">
							<input type="text" name="file" size="20" maxlength="200">
						</div>
					</div>
					<div class="mb-3 row">
						<div class="col-sm-10">
							<input class="btn btn-primary" type="submit" value="DOWNLOAD">
						</div>
					</div>
				</form>
			</div>
			<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
			<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
		</body>
	</html>

index.htmlでformを作り、main.pyにPOSTしています。name="file"で送っているので、mainでそれを受信して操作します

download.html

<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="UTF-8">
		<title>formTest</title>
		<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
	</head>
	<body>
		<h1 class="display-1 text-primary">deta.sh Driveからのダウンロード</h1>
		<div class="m-5 border">
			<p>Drive(photos)に保存したファイルを表示します</p>
			<p class="display-5 text-secondary">filename: {{filename}}</p>
			<img src="data:image/jpeg;base64,{{image | safe }}" alt="">
		</div>
		<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
	</body>
</html>

index.htmlから渡されたファイル名をmain.pyでDriveから読み込み、base64に変換してdownload.htmlに渡しています。imgのsrcにbase64で記述した場所にjinja2でbase64のコードをそのまま展開して画面表示します。注意点はあまり大きな画像ができないこと。理由はどうやらタイムアウトらしく各レスポンスが10秒以内でないといけません。

MicroのDetailsをみるとたしかに Timeout: 10sと書いてあります。

requirements.txt

flask
deta

flaskとdetaを使用しています

main.py

from flask import Flask, render_template, request, Response
from deta import Deta
import base64

app = Flask(__name__)

#drive接続
deta = Deta("Project key")
photos = deta.Drive("photos")

@app.route("/", methods=["GET"])
def top():
	return render_template("index.html")

@app.route('/download',methods=['POST'])
def download_img():
	filename = request.form['file']
	large_file = photos.get( filename )
	image_data  = b''
	for chunk in large_file.iter_chunks(4096):
		image_data += bytearray(chunk)
	large_file.close()
	img = base64.b64encode(image_data).decode('utf-8')
	return render_template("download.html",image=img,filename=filename)

if __name__ == '__main__':
	app.debug = True
	app.run(host='localhost')

最初の呼び出しは top()で処理し、index.htmlを呼び出しているだけです

/downloadへのpostを download_img()で処理します。渡されたfile名で Driveの"photos"のオブジェクトをlarge_fileに受けます。

このオブジェクトはファイルとのパイプのようなもので、ここから少しづつファイルを受信する、chunkという方法です。 4096バイトづつ受信してはimage_dataにbytearrayで保存します。そのデータをbase64に変換して、utf-8で文字列にしてdownload.htmlに送ります

サンプルをdeta.shに作りましたので、公開します。

deta.shに公開したdrivegettest

sample.jpgとsample2.jpgが表示可能です

これでdeta.shでプログラムを作る準備ができましたので、もう少し大きめのサンプルを作っていこうと思います