20032025
This commit is contained in:
Executable
+173
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import uuid
|
||||||
|
import mimetypes
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from b2sdk.v2 import InMemoryAccountInfo, B2Api
|
||||||
|
|
||||||
|
# .envファイルから環境変数を読み込む
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# ロギングの設定
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_environment():
|
||||||
|
"""環境変数の確認"""
|
||||||
|
required_vars = [
|
||||||
|
'B2_APPLICATION_KEY_ID',
|
||||||
|
'B2_APPLICATION_KEY',
|
||||||
|
'B2_BUCKET_NAME',
|
||||||
|
'B2_CUSTOM_URL'
|
||||||
|
]
|
||||||
|
|
||||||
|
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
||||||
|
if missing_vars:
|
||||||
|
raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
|
||||||
|
|
||||||
|
def is_valid_file(file_path):
|
||||||
|
"""
|
||||||
|
アップロード可能なファイルであることを確認
|
||||||
|
画像およびドキュメントファイルを許可
|
||||||
|
"""
|
||||||
|
valid_types = [
|
||||||
|
# 画像ファイル
|
||||||
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
|
||||||
|
# Apple系画像フォーマット
|
||||||
|
'image/heic', 'image/heif', 'image/heif-sequence', 'image/heic-sequence',
|
||||||
|
'image/x-heic', 'image/x-heif',
|
||||||
|
# Apple Live Photos
|
||||||
|
'image/mov', 'video/quicktime',
|
||||||
|
# ドキュメントファイル
|
||||||
|
'application/pdf', 'application/msword',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.ms-powerpoint',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'text/plain', 'text/markdown', 'text/csv'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 拡張子からコンテンツタイプを推測
|
||||||
|
content_type, _ = mimetypes.guess_type(file_path)
|
||||||
|
|
||||||
|
# mimetypesモジュールがHEICなどを認識しない場合があるため、拡張子ベースでもチェック
|
||||||
|
if content_type is None:
|
||||||
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
if ext == '.heic':
|
||||||
|
return True
|
||||||
|
if ext == '.heif':
|
||||||
|
return True
|
||||||
|
|
||||||
|
return content_type in valid_types
|
||||||
|
|
||||||
|
def check_file_size(file_path, max_size_mb=10):
|
||||||
|
"""ファイルサイズが上限以内か確認"""
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
max_size_bytes = max_size_mb * 1024 * 1024
|
||||||
|
return file_size <= max_size_bytes, file_size / (1024 * 1024) # サイズをMBで返す
|
||||||
|
|
||||||
|
def upload_to_b2(file_path):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
file_path (str): アップロードするファイルのパス。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: アップロードされたファイルのURL。
|
||||||
|
None: アップロードに失敗した場合。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_environment()
|
||||||
|
|
||||||
|
# ファイルタイプの検証
|
||||||
|
if not is_valid_file(file_path):
|
||||||
|
logging.error(f"Invalid file type: {file_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ファイルサイズの検証
|
||||||
|
is_size_valid, size_mb = check_file_size(file_path)
|
||||||
|
if not is_size_valid:
|
||||||
|
logging.error(f"File too large: {file_path} ({size_mb:.2f} MB)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# B2 APIクライアントの初期化
|
||||||
|
info = InMemoryAccountInfo()
|
||||||
|
b2_api = B2Api(info)
|
||||||
|
b2_api.authorize_account("production",
|
||||||
|
os.getenv('B2_APPLICATION_KEY_ID'),
|
||||||
|
os.getenv('B2_APPLICATION_KEY'))
|
||||||
|
|
||||||
|
# バケットの取得
|
||||||
|
bucket = b2_api.get_bucket_by_name(os.getenv('B2_BUCKET_NAME'))
|
||||||
|
|
||||||
|
# 現在の年と月を取得
|
||||||
|
now = datetime.now()
|
||||||
|
year = now.strftime("%Y")
|
||||||
|
month = now.strftime("%m")
|
||||||
|
|
||||||
|
# ランダムなファイル名を生成(拡張子は元のまま)
|
||||||
|
random_filename = str(uuid.uuid4()) + os.path.splitext(file_path)[1]
|
||||||
|
|
||||||
|
# 年/月/ファイル名 の形式でパスを構築
|
||||||
|
file_path_in_bucket = f"{year}/{month}/{random_filename}"
|
||||||
|
|
||||||
|
# ファイルのContentTypeをファイル名から推測
|
||||||
|
content_type, _ = mimetypes.guess_type(file_path)
|
||||||
|
if content_type is None:
|
||||||
|
# MIMEタイプが不明な場合はoctet-streamを使用
|
||||||
|
content_type = 'application/octet-stream'
|
||||||
|
|
||||||
|
# ファイルのアップロード
|
||||||
|
logging.info(f"Uploading {file_path} to {file_path_in_bucket}")
|
||||||
|
|
||||||
|
# 大きなファイルの場合はチャンクアップロード
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
if file_size > 5 * 1024 * 1024: # 5MB以上
|
||||||
|
uploaded_file = bucket.upload_local_file(
|
||||||
|
local_file=file_path,
|
||||||
|
file_name=file_path_in_bucket,
|
||||||
|
content_type=content_type,
|
||||||
|
min_part_size=5 * 1024 * 1024 # 5MB
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
uploaded_file = bucket.upload_local_file(
|
||||||
|
local_file=file_path,
|
||||||
|
file_name=file_path_in_bucket,
|
||||||
|
content_type=content_type
|
||||||
|
)
|
||||||
|
|
||||||
|
# アップロードされたファイルのURLを返す
|
||||||
|
file_url = f"{os.getenv('B2_CUSTOM_URL')}{file_path_in_bucket}"
|
||||||
|
logging.info(f"Successfully uploaded: {file_url}")
|
||||||
|
return file_url
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logging.error(f"Configuration error: {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Unexpected error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Upload files to Backblaze B2.')
|
||||||
|
parser.add_argument('--files-path', nargs='+', required=True, help='Paths to files to upload.')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
for file_path in args.files_path:
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
print(f"Error: File not found at {file_path}. Skipping.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_url = upload_to_b2(file_path)
|
||||||
|
if file_url:
|
||||||
|
print(file_url)
|
||||||
|
else:
|
||||||
|
print(f"File upload failed for {file_path}.")
|
||||||
Reference in New Issue
Block a user