Files
audiobook-maker-pro-v4.2/media_storage.py

171 lines
6.0 KiB
Python

# media_storage.py - File-based media storage manager (v4.3)
import os
import base64
import shutil
from config import MEDIA_STORAGE_DIR
def _ensure_dir(path):
os.makedirs(path, exist_ok=True)
def get_project_dir(project_id):
"""প্রজেক্টের base ডিরেক্টরি (absolute path) রিটার্ন করে।"""
return os.path.join(MEDIA_STORAGE_DIR, f'project_{int(project_id)}')
def get_audio_dir(project_id):
return os.path.join(get_project_dir(project_id), 'audio')
def get_images_dir(project_id):
return os.path.join(get_project_dir(project_id), 'images')
# ============================================
# Save operations (base64 string → file)
# ============================================
def save_audio(project_id, block_id, audio_base64, audio_format='mp3'):
"""
Audio ফাইল সেভ করে relative path রিটার্ন করে।
relative path ডেটাবেসে জমা হবে, যেমন: project_5/audio/block_123.mp3
"""
if not audio_base64:
return None
audio_dir = get_audio_dir(project_id)
_ensure_dir(audio_dir)
fmt = (audio_format or 'mp3').lower()
filename = f'block_{int(block_id)}.{fmt}'
abs_path = os.path.join(audio_dir, filename)
with open(abs_path, 'wb') as f:
f.write(base64.b64decode(audio_base64))
return f'project_{int(project_id)}/audio/{filename}'
def save_image(project_id, image_id, image_base64, image_format='png'):
"""Image ফাইল সেভ করে relative path রিটার্ন করে।"""
if not image_base64:
return None
images_dir = get_images_dir(project_id)
_ensure_dir(images_dir)
fmt = (image_format or 'png').lower()
filename = f'img_{int(image_id)}.{fmt}'
abs_path = os.path.join(images_dir, filename)
with open(abs_path, 'wb') as f:
f.write(base64.b64decode(image_base64))
return f'project_{int(project_id)}/images/{filename}'
def save_thumbnail(project_id, image_bytes, image_format='png'):
"""Thumbnail সেভ করে relative path রিটার্ন করে (raw bytes নেয়)।"""
if not image_bytes:
return None
proj_dir = get_project_dir(project_id)
_ensure_dir(proj_dir)
fmt = (image_format or 'png').lower()
filename = f'thumbnail.{fmt}'
abs_path = os.path.join(proj_dir, filename)
# পুরোনো thumbnail অন্য ফরম্যাটে থাকলে মুছে দিই
for old_ext in ('png', 'jpeg', 'jpg', 'webp', 'gif'):
old = os.path.join(proj_dir, f'thumbnail.{old_ext}')
if old_ext != fmt and os.path.exists(old):
try:
os.remove(old)
except OSError:
pass
with open(abs_path, 'wb') as f:
f.write(image_bytes)
return f'project_{int(project_id)}/{filename}'
# ============================================
# Read operations (file → bytes / base64)
# ============================================
def _safe_abs_path(relative_path):
"""
relative path কে absolute path-এ রূপান্তর করে এবং নিশ্চিত করে
যে এটা MEDIA_STORAGE_DIR-এর বাইরে যাচ্ছে না (path traversal রোধ)।
"""
if not relative_path:
return None
base = os.path.realpath(MEDIA_STORAGE_DIR)
abs_path = os.path.realpath(os.path.join(base, relative_path))
if not abs_path.startswith(base + os.sep) and abs_path != base:
return None # নিরাপত্তা লঙ্ঘন
return abs_path
def read_file_bytes(relative_path):
"""ফাইলের raw bytes রিটার্ন করে। না থাকলে None।"""
abs_path = _safe_abs_path(relative_path)
if not abs_path or not os.path.exists(abs_path):
return None
with open(abs_path, 'rb') as f:
return f.read()
def read_file_base64(relative_path):
"""ফাইল base64 স্ট্রিং হিসেবে রিটার্ন করে। না থাকলে খালি স্ট্রিং।"""
data = read_file_bytes(relative_path)
if data is None:
return ''
return base64.b64encode(data).decode('utf-8')
def get_safe_abs_path(relative_path):
"""Flask send_file-এর জন্য নিরাপদ absolute path।"""
return _safe_abs_path(relative_path)
# ============================================
# Storage stats (v4.3)
# ============================================
def get_storage_usage_bytes():
"""media-storage ফোল্ডারের মোট সাইজ (bytes) রিটার্ন করে।"""
total = 0
if not os.path.exists(MEDIA_STORAGE_DIR):
return 0
for root, _, files in os.walk(MEDIA_STORAGE_DIR):
for name in files:
fp = os.path.join(root, name)
try:
total += os.path.getsize(fp)
except OSError:
pass
return total
# ============================================
# Delete operations
# ============================================
def delete_project_media(project_id):
"""প্রজেক্টের পুরো media ফোল্ডার মুছে দেয় (প্রজেক্ট ডিলিটের সময়)।"""
proj_dir = get_project_dir(project_id)
if os.path.exists(proj_dir):
try:
shutil.rmtree(proj_dir)
print(f"🗑️ Deleted media folder: {proj_dir}")
return True
except OSError as e:
print(f"⚠️ Failed to delete media folder {proj_dir}: {e}")
return False
return False
def delete_file(relative_path):
"""একটা নির্দিষ্ট ফাইল মুছে দেয়।"""
abs_path = _safe_abs_path(relative_path)
if abs_path and os.path.exists(abs_path):
try:
os.remove(abs_path)
return True
except OSError:
return False
return False