下载地址:https://www.pan38.com/dow/share.php?code=JCnzE 提取密码:1782
这个项目包含三个主要文件:main.py包含核心的图片生成逻辑,utils.py提供数据生成工具函数,run.py是运行入口。项目可以生成逼真的抖音用户主页截图,包含头像、用户名、ID、简介、统计数据以及视频网格等元素。使用前需要确保安装了Pillow和Faker库,并准备好必要的字体文件。
import os
import random
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime
import textwrap
import json
class DouyinProfileGenerator:
def init(self):
self.template_path = "templates"
self.output_path = "output"
self.font_path = "fonts"
self.data_path = "data"
# 创建必要目录
self._create_dirs()
# 加载配置
self.config = self._load_config()
def _create_dirs(self):
"""创建必要的目录"""
os.makedirs(self.template_path, exist_ok=True)
os.makedirs(self.output_path, exist_ok=True)
os.makedirs(self.font_path, exist_ok=True)
os.makedirs(self.data_path, exist_ok=True)
def _load_config(self):
"""加载配置文件"""
config_path = os.path.join(self.data_path, "config.json")
if not os.path.exists(config_path):
default_config = {
"background_color": (255, 255, 255),
"header_height": 150,
"avatar_size": 80,
"text_color": (0, 0, 0),
"highlight_color": (255, 59, 48),
"padding": 20,
"font_sizes": {
"username": 24,
"userid": 16,
"bio": 14,
"stats": 18,
"video_title": 16
}
}
with open(config_path, "w") as f:
json.dump(default_config, f, indent=4)
return default_config
else:
with open(config_path, "r") as f:
return json.load(f)
def generate_profile(self, user_data):
"""生成抖音主页截图"""
# 创建基础画布
width = 1080
height = 1920
image = Image.new("RGB", (width, height), self.config["background_color"])
draw = ImageDraw.Draw(image)
# 绘制顶部导航栏
self._draw_header(draw, width)
# 绘制用户信息区域
self._draw_user_info(draw, image, user_data, width)
# 绘制统计数据
self._draw_stats(draw, user_data, width)
# 绘制视频网格
self._draw_video_grid(draw, image, user_data, width, height)
# 保存图片
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
output_filename = f"douyin_profile_{timestamp}.png"
output_path = os.path.join(self.output_path, output_filename)
image.save(output_path)
return output_path
def _draw_header(self, draw, width):
"""绘制顶部导航栏"""
header_height = self.config["header_height"]
draw.rectangle([(0, 0), (width, header_height)], fill=(255, 255, 255))
# 绘制返回箭头
arrow_size = 20
arrow_x = self.config["padding"]
arrow_y = header_height // 2
draw.polygon([
(arrow_x, arrow_y),
(arrow_x + arrow_size, arrow_y - arrow_size),
(arrow_x + arrow_size, arrow_y + arrow_size)
], fill=(0, 0, 0))
# 绘制标题
title_font = ImageFont.truetype(os.path.join(self.font_path, "PingFang.ttc"), 18)
draw.text((width // 2, header_height // 2), "个人主页",
fill=self.config["text_color"], font=title_font, anchor="mm")
def _draw_user_info(self, draw, image, user_data, width):
"""绘制用户信息区域"""
header_height = self.config["header_height"]
padding = self.config["padding"]
avatar_size = self.config["avatar_size"]
# 绘制头像
avatar_x = padding
avatar_y = header_height + padding
avatar_box = [(avatar_x, avatar_y),
(avatar_x + avatar_size, avatar_y + avatar_size)]
# 如果有头像图片则使用,否则绘制默认头像
if "avatar_path" in user_data and os.path.exists(user_data["avatar_path"]):
avatar = Image.open(user_data["avatar_path"])
avatar = avatar.resize((avatar_size, avatar_size))
image.paste(avatar, avatar_box[0])
else:
draw.ellipse(avatar_box, fill=(200, 200, 200))
# 绘制用户名
username_font = ImageFont.truetype(
os.path.join(self.font_path, "PingFang.ttc"),
self.config["font_sizes"]["username"]
)
username_x = avatar_x + avatar_size + padding
username_y = avatar_y
draw.text((username_x, username_y), user_data["username"],
fill=self.config["text_color"], font=username_font)
# 绘制用户ID
userid_font = ImageFont.truetype(
os.path.join(self.font_path, "PingFang.ttc"),
self.config["font_sizes"]["userid"]
)
userid_y = username_y + self.config["font_sizes"]["username"] + 5
draw.text((username_x, userid_y), f"抖音号: {user_data['userid']}",
fill=(150, 150, 150), font=userid_font)
# 绘制简介
bio_font = ImageFont.truetype(
os.path.join(self.font_path, "PingFang.ttc"),
self.config["font_sizes"]["bio"]
)
bio_x = avatar_x
bio_y = avatar_y + avatar_size + padding
bio_width = width - 2 * padding
# 自动换行处理
lines = textwrap.wrap(user_data["bio"], width=30)
for i, line in enumerate(lines):
draw.text((bio_x, bio_y + i * (self.config["font_sizes"]["bio"] + 5)),
line, fill=self.config["text_color"], font=bio_font)
def _draw_stats(self, draw, user_data, width):
"""绘制统计数据"""
padding = self.config["padding"]
header_height = self.config["header_height"]
avatar_size = self.config["avatar_size"]
stats_font = ImageFont.truetype(
os.path.join(self.font_path, "PingFang.ttc"),
self.config["font_sizes"]["stats"]
)
stats_y = header_height + padding + avatar_size + padding + len(
textwrap.wrap(user_data["bio"], width=30)
) * (self.config["font_sizes"]["bio"] + 5) + padding
# 关注数
follow_text = f"关注 {user_data['following']}"
follow_width = draw.textlength(follow_text, font=stats_font)
follow_x = width // 2 - follow_width - 30
draw.text((follow_x, stats_y), follow_text,
fill=self.config["text_color"], font=stats_font)
# 粉丝数
fans_text = f"粉丝 {user_data['followers']}"
fans_x = width // 2 + 30
draw.text((fans_x, stats_y), fans_text,
fill=self.config["highlight_color"], font=stats_font)
# 获赞数
likes_text = f"获赞 {user_data['likes']}"
likes_x = width - padding - draw.textlength(likes_text, font=stats_font)
draw.text((likes_x, stats_y), likes_text,
fill=self.config["text_color"], font=stats_font)
def _draw_video_grid(self, draw, image, user_data, width, height):
"""绘制视频网格"""
padding = self.config["padding"]
header_height = self.config["header_height"]
avatar_size = self.config["avatar_size"]
# 计算视频网格起始位置
grid_start_y = header_height + padding + avatar_size + padding + len(
textwrap.wrap(user_data["bio"], width=30)
) * (self.config["font_sizes"]["bio"] + 5) + padding + 50
# 绘制选项卡
tab_font = ImageFont.truetype(
os.path.join(self.font_path, "PingFang.ttc"),
self.config["font_sizes"]["stats"]
)
draw.text((padding, grid_start_y - 30), "作品",
fill=self.config["highlight_color"], font=tab_font)
# 视频网格参数
cols = 3
video_padding = 2
video_width = (width - 2 * padding - (cols - 1) * video_padding) // cols
# 生成随机视频缩略图
for i in range(9): # 3x3网格
row = i // cols
col = i % cols
video_x = padding + col * (video_width + video_padding)
video_y = grid_start_y + row * (video_width + video_padding)
# 随机颜色作为视频缩略图
video_color = (
random.randint(100, 200),
random.randint(100, 200),
random.randint(100, 200)
)
draw.rectangle([(video_x, video_y),
(video_x + video_width, video_y + video_width)],
fill=video_color)
# 添加播放量
views = f"{random.randint(1, 999)}w"
views_font = ImageFont.truetype(
os.path.join(self.font_path, "PingFang.ttc"),
self.config["font_sizes"]["video_title"] - 2
)
draw.text((video_x + 5, video_y + video_width - 25), views,
fill=(255, 255, 255), font=views_font)
import os
import random
from faker import Faker
class DataGenerator:
def init(self):
self.fake = Faker("zh_CN")
def generate_user_data(self):
"""生成随机用户数据"""
return {
"username": self.fake.user_name(),
"userid": f"dy{self.fake.unique.random_number(digits=8)}",
"bio": self._generate_bio(),
"following": self._format_number(random.randint(100, 9999)),
"followers": self._format_number(random.randint(1000, 1000000)),
"likes": self._format_number(random.randint(10000, 10000000)),
"videos": []
}
def _generate_bio(self):
"""生成随机个人简介"""
bios = [
"记录美好生活",
"爱跳舞的小姐姐",
"美食探店达人",
"旅行爱好者",
"分享日常",
"正能量传播者",
"搞笑视频创作者",
"音乐爱好者",
"摄影爱好者",
"健身达人"
]
return random.choice(bios) + " ✨"
def _format_number(self, num):
"""格式化数字显示"""
if num >= 10000:
return f"{num//10000}w"
elif num >= 1000:
return f"{num//1000}k"
return str(num)
def generate_multiple_users(self, count=10):
"""生成多个用户数据"""
return [self.generate_user_data() for _ in range(count)]
def ensure_fonts(font_dir):
"""确保字体文件存在"""
os.makedirs(font_dir, exist_ok=True)
required_fonts = {
"PingFang.ttc": "https://example.com/fonts/PingFang.ttc" # 实际使用时需要替换为真实字体文件
}
for font_name, font_url in required_fonts.items():
font_path = os.path.join(font_dir, font_name)
if not os.path.exists(font_path):
print(f"请手动下载字体文件 {font_name} 并放置到 {font_dir} 目录")
print(f"下载地址: {font_url}")
return False
return True