| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- #!/usr/bin/env python3
- """
- thumbnail_audit.py - 基于MrBeast缩略图理论的检查清单脚本
- 检查维度(基于MrBeast公开分享的缩略图原则):
- 1. 标题与缩略图互补性:是否「互补而非重复」?
- 2. 焦点数量:是否只有1-2个视觉焦点?
- 3. 文字量:缩略图文字是否少于5个词?
- 4. 情绪表达:是否有明确的面部表情/情绪?
- 5. 颜色对比:颜色对比是否足够醒目?
- 如果提供了图片文件,会用PIL分析颜色分布和亮度对比。
- 用法:
- python thumbnail_audit.py --title "I Spent 50 Hours Buried Alive"
- python thumbnail_audit.py --title "..." --image thumbnail.jpg
- python thumbnail_audit.py --title "..." --image thumbnail.jpg -o report.md
- 依赖: Pillow(仅图片分析时需要,纯文本检查无依赖)
- """
- import argparse
- import sys
- from pathlib import Path
- # ---------- MrBeast缩略图原则 ----------
- REDUNDANCY_WORDS = {
- # 如果标题中的关键词大量出现在缩略图文字中,说明重复而非互补
- "challenge", "survive", "hours", "days", "dollars", "money",
- "biggest", "world", "first", "last", "never", "impossible",
- }
- # 情绪相关词汇(用于标题分析)
- EMOTION_WORDS = [
- "shocked", "scared", "amazed", "crying", "screaming", "laughing",
- "surprised", "angry", "terrified", "excited", "happy", "sad",
- "emotional", "insane", "crazy", "unbelievable", "incredible",
- # 中文
- "震惊", "害怕", "惊讶", "哭", "尖叫", "笑", "疯狂", "不敢相信",
- ]
- def check_title_thumbnail_complementarity(title: str, thumb_text: str = "") -> dict:
- """检查标题与缩略图是否互补(而非重复)"""
- title_words = set(title.lower().split())
- thumb_words = set(thumb_text.lower().split()) if thumb_text else set()
- if not thumb_text:
- return {
- "score": 3,
- "max": 5,
- "note": "未提供缩略图文字,无法完整评估。建议:缩略图应补充标题没说的信息。",
- }
- overlap = title_words & thumb_words & REDUNDANCY_WORDS
- overlap_ratio = len(overlap) / max(len(thumb_words), 1)
- if overlap_ratio > 0.5:
- score = 1
- note = f"缩略图文字与标题高度重复(重复词: {', '.join(overlap)})。MrBeast原则:缩略图应该补充标题,而不是重复标题。"
- elif overlap_ratio > 0.2:
- score = 3
- note = f"有部分重复({', '.join(overlap)}),但还可以。考虑让缩略图传递标题没说的信息。"
- else:
- score = 5
- note = "标题与缩略图互补性好,各自传递不同信息。"
- return {"score": score, "max": 5, "note": note, "overlap": list(overlap)}
- def check_text_amount(thumb_text: str = "") -> dict:
- """检查缩略图文字量"""
- if not thumb_text:
- return {
- "score": 4,
- "max": 5,
- "word_count": 0,
- "note": "未提供缩略图文字。MrBeast的缩略图通常文字极少(0-3词)或不用文字。",
- }
- word_count = len(thumb_text.split())
- if word_count == 0:
- score, note = 5, "无文字,干净利落。"
- elif word_count <= 3:
- score, note = 5, f"仅{word_count}词,符合MrBeast标准。"
- elif word_count <= 5:
- score, note = 3, f"{word_count}词,接近上限。考虑精简到3词以内。"
- else:
- score, note = 1, f"{word_count}词,太多了!MrBeast缩略图极少超过3-5个词。文字越少,点击率越高。"
- return {"score": score, "max": 5, "word_count": word_count, "note": note}
- def check_emotion_in_title(title: str) -> dict:
- """检查标题是否暗示明确的情绪(间接评估缩略图情绪需求)"""
- title_lower = title.lower()
- found_emotions = [w for w in EMOTION_WORDS if w in title_lower]
- # 检查感叹号和问号
- has_exclamation = "!" in title or "?" in title or "!" in title
- has_question = "?" in title or "?" in title
- if found_emotions:
- score = 5
- note = f"标题有明确情绪暗示({', '.join(found_emotions[:3])})。缩略图应该用面部表情呼应这种情绪。"
- elif has_exclamation or has_question:
- score = 3
- note = "标题有情绪标点,但缺少明确情绪词。缩略图需要用面部表情补充情绪。"
- else:
- score = 2
- note = "标题情绪不明显。MrBeast原则:缩略图必须有一张表情夸张的人脸,或者明确的情绪视觉元素。"
- return {
- "score": score,
- "max": 5,
- "emotions_found": found_emotions,
- "note": note,
- }
- def check_title_curiosity_gap(title: str) -> dict:
- """检查标题是否制造好奇心缺口"""
- curiosity_patterns = [
- ("数字对比", ["vs", "versus", "$", "比"]),
- ("悬念词", ["secret", "mystery", "hidden", "never", "impossible", "秘密", "不可能"]),
- ("挑战框架", ["challenge", "survive", "last", "endure", "挑战", "坚持"]),
- ("极端词", ["world", "biggest", "smallest", "most", "least", "最大", "最小", "最"]),
- ("时间压力", ["hours", "days", "minutes", "seconds", "小时", "天", "分钟"]),
- ]
- found = []
- title_lower = title.lower()
- for pattern_name, keywords in curiosity_patterns:
- if any(k in title_lower for k in keywords):
- found.append(pattern_name)
- if len(found) >= 3:
- score, note = 5, f"标题有{len(found)}个好奇心元素({', '.join(found)}),非常强!"
- elif len(found) >= 2:
- score, note = 4, f"标题有{len(found)}个好奇心元素({', '.join(found)}),不错。"
- elif len(found) == 1:
- score, note = 3, f"标题有1个好奇心元素({found[0]}),可以更强。"
- else:
- score, note = 1, "标题缺少好奇心缺口。MrBeast标题通常至少包含2-3个好奇心元素。"
- return {"score": score, "max": 5, "patterns_found": found, "note": note}
- def analyze_image(image_path: str) -> dict:
- """用PIL分析图片的颜色和对比度"""
- try:
- from PIL import Image, ImageStat
- except ImportError:
- return {
- "available": False,
- "note": "Pillow未安装,跳过图片分析。安装: pip install Pillow",
- }
- path = Path(image_path)
- if not path.exists():
- return {"available": False, "note": f"图片文件不存在: {image_path}"}
- try:
- img = Image.open(path)
- except Exception as e:
- return {"available": False, "note": f"无法打开图片: {e}"}
- # 转换为RGB
- if img.mode != "RGB":
- img = img.convert("RGB")
- stat = ImageStat.Stat(img)
- width, height = img.size
- # 平均亮度
- avg_brightness = sum(stat.mean) / 3
- # 亮度标准差(对比度指标)
- avg_stddev = sum(stat.stddev) / 3
- # 颜色饱和度分析
- hsv_img = img.convert("HSV")
- hsv_stat = ImageStat.Stat(hsv_img)
- avg_saturation = hsv_stat.mean[1]
- # 主色调分析(简化版:取中心区域和边缘区域对比)
- center_crop = img.crop((width // 4, height // 4, 3 * width // 4, 3 * height // 4))
- center_stat = ImageStat.Stat(center_crop)
- center_brightness = sum(center_stat.mean) / 3
- # 评估
- results = {
- "available": True,
- "size": f"{width}x{height}",
- "brightness": {
- "average": round(avg_brightness, 1),
- "score": 5 if 80 < avg_brightness < 200 else 3 if 50 < avg_brightness < 230 else 1,
- "note": "亮度适中" if 80 < avg_brightness < 200 else "偏暗或偏亮",
- },
- "contrast": {
- "stddev": round(avg_stddev, 1),
- "score": 5 if avg_stddev > 60 else 3 if avg_stddev > 40 else 1,
- "note": "对比度强" if avg_stddev > 60 else "对比度中等" if avg_stddev > 40 else "对比度不足,缩略图在小尺寸下可能不够醒目",
- },
- "saturation": {
- "average": round(avg_saturation, 1),
- "score": 5 if avg_saturation > 100 else 3 if avg_saturation > 60 else 2,
- "note": "色彩饱和度高" if avg_saturation > 100 else "色彩饱和度中等" if avg_saturation > 60 else "色彩偏淡,考虑增加饱和度",
- },
- "center_focus": {
- "center_brightness": round(center_brightness, 1),
- "edge_contrast": round(abs(center_brightness - avg_brightness), 1),
- "note": "中心区域与边缘有明显对比" if abs(center_brightness - avg_brightness) > 15 else "中心与边缘对比不明显,焦点可能不够突出",
- },
- }
- return results
- def generate_report(title: str, thumb_text: str = "", image_path: str = None) -> str:
- """生成完整审核报告"""
- complementarity = check_title_thumbnail_complementarity(title, thumb_text)
- text_amount = check_text_amount(thumb_text)
- emotion = check_emotion_in_title(title)
- curiosity = check_title_curiosity_gap(title)
- image_analysis = analyze_image(image_path) if image_path else None
- # 计算总分
- scores = [complementarity["score"], text_amount["score"], emotion["score"], curiosity["score"]]
- if image_analysis and image_analysis.get("available"):
- scores.append(image_analysis["brightness"]["score"])
- scores.append(image_analysis["contrast"]["score"])
- scores.append(image_analysis["saturation"]["score"])
- total = sum(scores)
- max_total = len(scores) * 5
- lines = []
- lines.append("# 缩略图审核报告\n")
- lines.append(f"**标题**: {title}")
- if thumb_text:
- lines.append(f"**缩略图文字**: {thumb_text}")
- if image_path:
- lines.append(f"**图片**: {image_path}")
- lines.append(f"\n**总分**: {total}/{max_total} ({total/max_total*100:.0f}%)\n")
- # 评级
- pct = total / max_total * 100
- if pct >= 80:
- grade = "A - 优秀,点击率潜力高"
- elif pct >= 60:
- grade = "B - 良好,有优化空间"
- elif pct >= 40:
- grade = "C - 及格,需要重点改进"
- else:
- grade = "D - 需要重做"
- lines.append(f"**评级**: {grade}\n")
- # 各项检查
- lines.append("## 1. 标题-缩略图互补性 ({}/{})\n".format(complementarity["score"], complementarity["max"]))
- lines.append(complementarity["note"])
- lines.append("")
- lines.append("## 2. 缩略图文字量 ({}/{})\n".format(text_amount["score"], text_amount["max"]))
- lines.append(text_amount["note"])
- lines.append("")
- lines.append("## 3. 情绪表达 ({}/{})\n".format(emotion["score"], emotion["max"]))
- lines.append(emotion["note"])
- lines.append("")
- lines.append("## 4. 好奇心缺口 ({}/{})\n".format(curiosity["score"], curiosity["max"]))
- lines.append(curiosity["note"])
- lines.append("")
- # 图片分析
- if image_analysis:
- if image_analysis.get("available"):
- lines.append(f"## 5. 图片技术分析 (尺寸: {image_analysis['size']})\n")
- b = image_analysis["brightness"]
- c = image_analysis["contrast"]
- s = image_analysis["saturation"]
- cf = image_analysis["center_focus"]
- lines.append(f"- **亮度** ({b['score']}/5): 平均 {b['average']} - {b['note']}")
- lines.append(f"- **对比度** ({c['score']}/5): 标准差 {c['stddev']} - {c['note']}")
- lines.append(f"- **饱和度** ({s['score']}/5): 平均 {s['average']} - {s['note']}")
- lines.append(f"- **焦点**: 中心-边缘差 {cf['edge_contrast']} - {cf['note']}")
- else:
- lines.append(f"## 5. 图片分析\n")
- lines.append(f"跳过: {image_analysis['note']}")
- lines.append("")
- # MrBeast缩略图清单
- lines.append("## MrBeast缩略图黄金法则\n")
- lines.append("- [ ] 缩略图在手机小屏上是否清晰可辨?")
- lines.append("- [ ] 是否只有1-2个视觉焦点(不杂乱)?")
- lines.append("- [ ] 是否有一张情绪强烈的人脸?")
- lines.append("- [ ] 缩略图是否让人产生「我必须点进去看」的冲动?")
- lines.append("- [ ] 标题和缩略图组合是否创造了信息缺口?")
- lines.append("- [ ] 与同时段其他视频放在一起时是否够醒目?")
- lines.append("")
- # 改进建议
- lines.append("## 改进建议\n")
- suggestions = []
- if complementarity["score"] < 4:
- suggestions.append("让缩略图传递标题没说的信息(比如标题说挑战,缩略图展示结果或最戏剧性的瞬间)")
- if text_amount["score"] < 4:
- suggestions.append("减少缩略图文字,理想是0-3个词,用视觉而非文字讲故事")
- if emotion["score"] < 4:
- suggestions.append("缩略图加入表情夸张的人脸照片,情绪越强烈越好")
- if curiosity["score"] < 4:
- suggestions.append("标题加入数字/极端词/时间压力等好奇心元素")
- if image_analysis and image_analysis.get("available"):
- if image_analysis["contrast"]["score"] < 4:
- suggestions.append("提高图片对比度,确保缩略图在小尺寸下也清晰醒目")
- if image_analysis["saturation"]["score"] < 4:
- suggestions.append("增加色彩饱和度,让图片在YouTube首页中跳出来")
- if suggestions:
- for i, s in enumerate(suggestions, 1):
- lines.append(f"{i}. {s}")
- else:
- lines.append("整体表现优秀,继续保持!")
- return "\n".join(lines)
- def main():
- parser = argparse.ArgumentParser(
- description="基于MrBeast缩略图理论的审核工具",
- formatter_class=argparse.RawDescriptionHelpFormatter,
- epilog='示例:\n python thumbnail_audit.py --title "I Survived 50 Hours In Antarctica"\n python thumbnail_audit.py --title "..." --thumb-text "50 HOURS" --image thumb.jpg',
- )
- parser.add_argument("--title", required=True, help="视频标题")
- parser.add_argument("--thumb-text", default="", help="缩略图上的文字(如果有)")
- parser.add_argument("--image", help="缩略图图片文件路径(可选)")
- parser.add_argument("-o", "--output", help="输出报告文件路径")
- args = parser.parse_args()
- report = generate_report(args.title, args.thumb_text, args.image)
- if args.output:
- Path(args.output).write_text(report, encoding="utf-8")
- print(f"[OK] 报告已保存到: {args.output}")
- else:
- print(report)
- if __name__ == "__main__":
- main()
|