Vooo / test_something.py
TDN-M's picture
Upload 40 files
7ad1067 verified
import os
import logging
from moviepy.editor import *
from moviepy.config import change_settings
from PIL import Image
import PIL
# ==== CONFIG ====
THUMBNAIL_HEIGHT_RATIO = 0.25
LOGO_SIZE_RATIO = 0.12
TEXT_MARGIN = 20
LOGO_MARGIN = 20
THUMBNAIL_SHOW_DURATION = 5
if not hasattr(PIL.Image, 'ANTIALIAS'):
PIL.Image.ANTIALIAS = PIL.Image.Resampling.LANCZOS
# Font đẹp hơn nếu đã cài: 'Roboto-Bold', 'Montserrat-Bold'...
FONT_NAME = 'Roboto-Bold'
# Đặt đường dẫn ImageMagick nếu cần
change_settings({
"IMAGEMAGICK_BINARY": r"C:\Program Files\ImageMagick-7.1.2-Q16-HDRI\magick.exe"
})
logging.basicConfig(level=logging.INFO)
# ==== TEST INPUT ====
thumbnail_path = "thumbnails/thumbnail1.jpg"
logo_path = "logo.png"
description = "MÔ TẢ TEST VỀ DESCRIPTION MỘT HAI BA BỐN NĂM SÁU"
date = "23/07/2025"
output_path = "test_output_frame.png"
target_resolution = (1080, 1920) # Tỉ lệ dọc 9:16
def create_thumbnail_overlay(
thumbnail_path: str,
logo_path: str,
description: str,
date: str,
target_resolution: tuple[int, int],
duration: float = THUMBNAIL_SHOW_DURATION
) -> CompositeVideoClip:
"""
Tạo overlay thumbnail với logo và text cho 5 giây đầu video.
Thumbnail sẽ được scale để CHIỀU RỘNG luôn bao phủ đủ (có thể hi sinh chiều cao).
Layout:
┌─────────────────────────┐
│ VIDEO │
│ LOGO │ ← Góc phải trên
│ │
│ │
├─────────────────────────┤
│ THUMBNAIL DATE │ ← Date ở góc phải trên của thumbnail
│ + DESCRIPTION │ (Text overlay trên thumbnail)
│ │
└─────────────────────────┘
Args:
thumbnail_path: Đường dẫn ảnh thumbnail
logo_path: Đường dẫn logo
description: Mô tả văn bản (hiển thị trên thumbnail)
date: Ngày tháng (hiển thị ở góc phải trên thumbnail)
target_resolution: (width, height) của video
duration: Thời lượng hiển thị overlay (mặc định 5 giây)
Returns:
CompositeVideoClip chứa thumbnail overlay
"""
target_width, target_height = target_resolution
max_thumbnail_height = int(target_height * THUMBNAIL_HEIGHT_RATIO)
# 1. Tạo nền trong suốt cho overlay
overlay_background = ColorClip(
size=target_resolution,
color=(255, 255, 255), # Đen
duration=duration
).set_opacity(0) # Trong suốt
overlay_clips = [overlay_background]
# 2. Xử lý thumbnail - SCALE ĐỂ FILL WIDTH
actual_thumbnail_height = max_thumbnail_height # Mặc định
thumbnail_y_position = target_height - max_thumbnail_height
if thumbnail_path and os.path.exists(thumbnail_path):
try:
thumbnail_clip = ImageClip(thumbnail_path, duration=duration)
# Lấy kích thước gốc của thumbnail
original_thumb_width, original_thumb_height = get_media_dimensions(thumbnail_clip)
if original_thumb_width and original_thumb_height:
# SCALE ĐỂ FILL WIDTH (ưu tiên chiều rộng)
scale_to_fit_width = target_width / original_thumb_width
# Tính chiều cao sau khi scale theo width
scaled_height = int(original_thumb_height * scale_to_fit_width)
# Resize thumbnail để fill width
thumbnail_resized = thumbnail_clip.resize((target_width, scaled_height))
# Nếu chiều cao sau scale vượt quá max_thumbnail_height, crop từ giữa
if scaled_height > max_thumbnail_height:
# Crop từ giữa để giữ lại phần quan trọng nhất
crop_start_y = (scaled_height - max_thumbnail_height) // 2
crop_end_y = crop_start_y + max_thumbnail_height
thumbnail_resized = thumbnail_resized.crop(
x1=0, y1=crop_start_y,
x2=target_width, y2=crop_end_y
)
actual_thumbnail_height = max_thumbnail_height
logging.info(f"Thumbnail được crop: từ {scaled_height}px xuống {max_thumbnail_height}px")
else:
# Nếu chiều cao sau scale nhỏ hơn max, cập nhật actual height
actual_thumbnail_height = scaled_height
# Tính lại vị trí Y dựa trên chiều cao thực tế
thumbnail_y_position = target_height - actual_thumbnail_height
# Đặt vị trí thumbnail ở dưới cùng, fill width
thumbnail_positioned = thumbnail_resized.set_position((0, thumbnail_y_position))
overlay_clips.append(thumbnail_positioned)
logging.info(f"Thumbnail - Original: {original_thumb_width}x{original_thumb_height}")
logging.info(f"Thumbnail - Scaled: {target_width}x{scaled_height}")
logging.info(f"Thumbnail - Final: {target_width}x{actual_thumbnail_height}")
logging.info(f"Thumbnail - Position: (0, {thumbnail_y_position})")
else:
# Fallback nếu không lấy được kích thước
thumbnail_resized = thumbnail_clip.resize((target_width, max_thumbnail_height))
thumbnail_positioned = thumbnail_resized.set_position((0, thumbnail_y_position))
overlay_clips.append(thumbnail_positioned)
logging.warning(f"Sử dụng fallback resize cho thumbnail: {thumbnail_path}")
except Exception as e:
logging.warning(f"Không thể xử lý thumbnail '{thumbnail_path}': {e}")
# 3. Xử lý logo - đặt ở góc phải trên
if logo_path and os.path.exists(logo_path):
try:
logo_clip = ImageClip(logo_path, duration=duration)
# Resize logo theo tỷ lệ
logo_width = int(target_width * LOGO_SIZE_RATIO)
logo_resized = logo_clip.resize(width=logo_width)
# Đặt vị trí logo ở góc phải trên
logo_x = target_width - logo_resized.w - LOGO_MARGIN
logo_y = LOGO_MARGIN
logo_positioned = logo_resized.set_position((logo_x, logo_y))
overlay_clips.append(logo_positioned)
logging.info(f"Logo - Size: {logo_resized.w}x{logo_resized.h} - Position: ({logo_x}, {logo_y})")
except Exception as e:
logging.warning(f"Không thể xử lý logo '{logo_path}': {e}")
# 4. Thêm text description - đặt TRONG vùng thumbnail
if description and description.strip():
try:
# Tính toán vị trí text trong vùng thumbnail
desc_y = thumbnail_y_position + TEXT_MARGIN # Bên trong thumbnail, từ trên xuống
# Tạo text clip cho description với font đẹp hơn
font_size = max(int(target_width * 0.025), 40) # Minimum 16px
# Thử các font đẹp theo thứ tự ưu tiên
preferred_fonts = [
'Open Sans',
'Poppins-Bold', # Clean, modern font
'Helvetica-Bold', # Classic font
'Arial-Bold' # Fallback
]
description_font = 'Arial-Bold' # Default fallback
for font in preferred_fonts:
try:
# Test font by creating a small clip
test_clip = TextClip("Test", fontsize=14, font=font)
description_font = font
break
except:
continue
desc_clip = TextClip(
description.strip(),
fontsize=font_size,
font=description_font,
color='white',
stroke_color='black',
stroke_width=0,
method='caption',
size=(target_width - 2 * TEXT_MARGIN, None) # Giới hạn width
).set_duration(duration).set_position((TEXT_MARGIN, desc_y))
overlay_clips.append(desc_clip)
logging.info(f"Description text - Font: {description_font} {font_size}px - Position: ({TEXT_MARGIN}, {desc_y})")
except Exception as e:
logging.warning(f"Không thể tạo description text: {e}")
# 5. Thêm text date - đặt ở góc phải trên của thumbnail
if date and date.strip():
try:
# Tính toán vị trí date ở góc phải trên của thumbnail
date_y = thumbnail_y_position + TEXT_MARGIN - 40 # Trên cùng của thumbnail
# Tạo text clip cho date với font đẹp hơn
date_font_size = max(int(target_width * 0.02), 28) # Nhỏ hơn description, minimum 14px
# Thử các font đẹp cho date
date_preferred_fonts = [
'Open Sans',
'Segoe UI', # Windows modern font
'SF Pro Text', # macOS system font
'Roboto', # Android/Google font
'Inter', # Modern web font
'Poppins', # Clean, modern font
'Helvetica', # Classic font
'Arial' # Fallback
]
date_font = 'Arial' # Default fallback
for font in date_preferred_fonts:
try:
# Test font by creating a small clip
test_clip = TextClip("Test", fontsize=14, font=font)
date_font = font
break
except:
continue
# Tạo date clip để tính toán width
temp_date_clip = TextClip(
date.strip(),
fontsize=date_font_size,
font=date_font,
color='white'
)
# Tính toán vị trí X để đặt ở góc phải
date_x = target_width - temp_date_clip.w - TEXT_MARGIN
date_clip = TextClip(
date.strip(),
fontsize=date_font_size,
font=date_font,
color='white',
stroke_color='black',
stroke_width=0
).set_duration(duration).set_position((date_x, date_y))
overlay_clips.append(date_clip)
logging.info(f"Date text - Font: {date_font} {date_font_size}px - Position: ({date_x}, {date_y})")
except Exception as e:
logging.warning(f"Không thể tạo date text: {e}")
# 6. Composite tất cả các elements
thumbnail_overlay = CompositeVideoClip(overlay_clips, size=target_resolution)
return thumbnail_overlay
if __name__ == "__main__":
# Gọi hàm tạo overlay
clip = create_thumbnail_overlay(
thumbnail_path=thumbnail_path,
logo_path=logo_path,
description=description,
date=date,
target_resolution=target_resolution
)
# Trích xuất frame tại thời điểm t = 0 giây
frame = clip.get_frame(0) # Trả về numpy array (H, W, 3)
# Lưu frame thành ảnh PNG
image = Image.fromarray(frame)
image.save("output_thumbnail_frame.png")