上一篇文章的Python程式透過MediaFileUpload()建立上傳檔案物件。本文將修改《使用Python Flask建置影像圖檔上傳網站服務(五)》的Flask網站程式,原程式是將用戶透過網頁表單上傳的檔案存入網站伺服器的uploads資料夾,這個版本改成把檔案暫存在主記憶體,再交由MediaIoBaseUpload()建立上傳檔物件,轉傳到Google雲端硬碟。

MediaFileUpload(媒體檔案上傳)類別物件的參數
MediaFileUpload()其實有4個參數(參閱此Google API說明文件),它們的名稱和意義如下:
- filename:檔名,字串格式的上傳檔路徑和檔名。
- mimetype(MIME類型):上傳檔的MIME類型字串,如果沒有填寫,則依「副檔名」自動判斷。
- chunksize:區塊大小,僅resumable(可續傳)參數設為True才有效,指定切割檔案、分批上傳的位元組(整數)大小。Google雲端程式的「請求」訊息大小上限為5MB,所以區塊大小不可大於5MB,預設為1024×1024位元組(1MB)。此參數設成-1代表不分割(不建議)。
- resumable:可續傳,設成True代表可在斷線、重新連線之後續傳檔案。

使用io.BytesIO()建立緩存記憶體物件
Python的標準函式庫內建一個名叫io(代表「輸出∕入介面」)的模組,其中的BytesIO提供了在記憶體中讀寫位元組資料的功能,例如,把二進位檔案資料(如:圖檔)暫存在記憶體。
延續《使用Python Flask建置影像圖檔上傳網站服務(五)》貼文的Flask程式,上傳檔案的資料存在file物件,底下的敘述將建立一個BytesIO物件,然後在其中存入上傳檔案。程式碼的開頭必須先引用io模組:

Flask網站應用程式的src原始檔資料夾,也要存入Google的服務帳戶金鑰。

透過MediaIoBaseUpload物件上傳緩存記憶體內容到Google雲端硬碟
完整的上傳檔案到Google雲端硬碟的Python Flask主程式碼如下,這個程式也支援把ESP32-CAM拍攝的影像上傳到的Google雲端硬碟,ESP32-CAM的程式碼不變,請參閱《ESP32-CAM開發板(三):拍照並上傳影像到網站伺服器》貼文。
import datetime
import filetype
from flask import Flask, flash, request, redirect, url_for, render_template
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseUpload
import io # 內含BytesIO類別
UPLOAD_FOLDER = '你的上傳檔資料夾的ID '
SCOPES = ['https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = 'google_auth.json' # 金鑰檔案
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # 請自行修改密鑰
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024
# 驗證憑證
creds = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('drive', 'v3', credentials=creds)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/', methods=['POST'])
def upload_file():
res = handle_file(request)
if res['msg'] == 'ok':
flash('影像上傳完畢!')
return render_template('index.html', filename=res['filename'])
elif res['msg'] == 'type_error':
flash('僅允許上傳png, jpg, jpeg和gif影像檔')
elif res['msg'] == 'empty':
flash('請選擇要上傳的影像')
elif res['msg'] == 'no_file':
flash('沒有上傳檔案')
return redirect(url_for('index')) # 令瀏覽器跳回首頁
@app.route('/esp32cam', methods=['POST']) # 供ESP32-CAM上傳影像的路由
def esp32cam():
res = handle_file(request)
return res['msg']
def handle_file(request):
if 'filename' not in request.files:
return {"msg": 'no_file'}
file = request.files['filename'] # 取得上傳檔
if file.filename == '':
return {"msg": 'empty'} # 傳回代表"空白"的訊息
if file:
file_type = filetype.guess_extension(file) # 判斷上傳檔的類型
MIME = file.content_type
if file_type in ALLOWED_EXTENSIONS:
file.stream.seek(0)
# 重新設定檔名
filename = str(datetime.datetime.now()).replace(
':', '_') + '.' + file_type
buffer = io.BytesIO() # 宣告記憶體物件
file.save(buffer) # 把檔案存入緩存記憶體
print("上傳到Google Drive...")
media = MediaIoBaseUpload(buffer, mimetype=MIME,
chunksize=1024*1024, resumable=True)
body = {'name': filename, 'parents': [UPLOAD_FOLDER]}
file_id = service.files().create(
body=body, media_body=media).execute()
print('雲端檔名: ' + str(body['name']))
print('雲端檔案ID:' + str(file_id['id']))
# return {"msg": 'ok', "filename": filename}
# 改成傳遞Google雲端檔案的ID給HTML樣板
return {"msg": 'ok', "filename": str(file_id['id'])}
else:
return {"msg": 'type_error'} # 傳回代表「檔案類型錯誤」的訊息
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
顯示上傳到Google雲端硬碟的影像檔
Flask程式上傳圖檔之後,網頁將會呈現剛剛上傳的影像,原本的HTML樣板檔(templates/index.html)是讀取伺服器的static/uploads路徑的上傳影像,要改成讀取Google雲端硬碟的上傳檔,其中的filename變數值是Flask傳入的Google雲端檔案ID。

完整的Python Flask上傳影像到Google雲端硬碟的程式原始碼請按此連結下載。
