视频质量门禁

Polar Sponsor
爱发电 赞助
.NET 9.0

视频生产质量门禁 — 7道关卡自动化检查,全部通过才交付。覆盖HTML结构、时间线、TTS朗读、时长匹配、音频质量、动画执行、视觉抽检。

视频质量门禁 — 7道关卡

功能概述

视频质量门禁 — 7道关卡是一项面向实际任务的技能,主要用于视频生产完成后,必须依次通过7道关卡检查;任一关卡失败,立即停止并告警,不得交付;

核心要点

  • 品牌标准模板;
  • 所有模世界视频必须遵循以下标准:;
  • 模世界(禁止写成"膜世界");

使用与执行

MOSZE;Slogan;手机配件一站式批发;2563eb(蓝色);06b6d4(青色)、#fbbf24(黄色)、#10b981(绿色)、#f44336(红色);Inter, -a。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。

结果检查与注意事项

从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。

视频质量门禁 — 7道关卡

概述

视频生产完成后,必须依次通过7道关卡检查。任一关卡失败,立即停止并告警,不得交付。

品牌标准模板

所有模世界视频必须遵循以下标准:

项目 标准值
品牌名 模世界(禁止写成"膜世界")
英文名 MOSZE
Slogan 手机配件一站式批发
主色调 #2563eb(蓝色)
辅助色 #06b6d4(青色)、#fbbf24(黄色)、#10b981(绿色)、#f44336(红色)
字体 Inter, -apple-system, Helvetica Neue, Arial
分辨率 1080×1920(竖屏)
时长 25秒
Logo位置 底部居中,高度70px
场景数 4个(s1钩子 → s2参数 → s3卖点 → s4品牌)

关卡0:HTML结构检查

时机: HTML文件生成后立即执行
方法: 代码级静态检查

检查项

def gate0_html_structure(html_content: str) -> dict:
    """检查HTML文件结构完整性"""
    results = []
    
    # 1. doctype存在
    if not html_content.strip().upper().startswith(''):
        results.append(('FAIL', '缺少声明'))
        return {'pass': False, 'details': results}
    
    # 2. 
存在 if '
' not in html_content: results.append(('FAIL', '缺少
容器')) return {'pass': False, 'details': results} # 3. GSAP script存在 if 'gsap.timeline' not in html_content: results.append(('FAIL', '缺少GSAP时间线代码')) return {'pass': False, 'details': results} # 4. window.__timelines注册存在 if 'window.__timelines' not in html_content: results.append(('FAIL', '缺少window.__timelines注册')) return {'pass': False, 'details': results} # 5. 4个场景(支持两种写法:id="s1" 或 class="scene s1") for scene_num in ['1', '2', '3', '4']: has_id = f'id="s{scene_num}"' in html has_class = f'class="scene s{scene_num}"' in html if not has_id and not has_class: results.append(('FAIL', f'缺少场景 s{scene_num}')) return {'pass': False, 'details': results} # 6. 选择器格式正确(#root .s1 而非 .s1) import re gsap_calls = re.findall(r'#root .sd+', html_content) if not gsap_calls: results.append(('WARN', '未使用#root .s1选择器,动画可能不生效')) # 7. duration属性匹配 if 'data-duration="25"' not in html_content: results.append(('FAIL', 'data-duration不是25秒')) return {'pass': False, 'details': results} # 8. 品牌名检查 if '膜世界' in html_content: results.append(('FAIL', '发现错别字"膜世界",应为"模世界"')) return {'pass': False, 'details': results} results.append(('PASS', 'HTML结构检查通过')) return {'pass': True, 'details': results}

关卡0.5:时间线验证

时机: HTML结构检查通过后执行
方法: 模拟执行GSAP时间线,验证动画有效性

检查项

def gate05_timeline_validation(html_content: str) -> dict:
    """验证GSAP时间线是否有效"""
    results = []
    
    # 1. 提取所有tl.to()调用
    import re
    to_calls = re.findall(r'.to([^)]+)', html_content)
    
    if len(to_calls) < 4:
        results.append(('FAIL', f'GSAP .to()调用过少({len(to_calls)}个,至少需要4个场景动画)'))
        return {'pass': False, 'details': results}
    
    # 2. 每个场景是否有动画(支持 #root .s1 和 .s1 两种写法)
    scenes_with_anim = set()
    for call in to_calls:
        for scene_num in ['1', '2', '3', '4']:
            if f'.s{scene_num}' in call:
                scenes_with_anim.add(f'.s{scene_num}')
    
    missing = set(['.s1', '.s2', '.s3', '.s4']) - scenes_with_anim
    if missing:
        results.append(('FAIL', f'以下场景无动画:{missing}'))
        return {'pass': False, 'details': results}
    
    # 3. 检查时间线总时长(通过解析时间戳估算)
    # 提取所有时间点
    time_points = re.findall(r'},s*(d+.?d*)s*)', html_content)
    if time_points:
        max_time = max(float(t) for t in time_points)
        if max_time < 20:
            results.append(('FAIL', f'时间线最大时间仅{max_time}s,不足25s'))
            return {'pass': False, 'details': results}
        if max_time > 30:
            results.append(('WARN', f'时间线最大时间{max_time}s,超过25s'))
    
    # 4. 检查是否有重复注册同一场景
    scene_registrations = re.findall(r'set('#root .s(d)'', html_content)
    from collections import Counter
    counts = Counter(scene_registrations)
    for scene, count in counts.items():
        if count > 1:
            results.append(('WARN', f'场景s{scene}被注册{count}次'))
    
    results.append(('PASS', '时间线验证通过'))
    return {'pass': True, 'details': results}

关卡1:脚本内容检查

时机: 生成配音前执行
方法: 文本扫描 + TTS友好度检测

检查项

def gate1_script_content(texts: list, html_content: str) -> dict:
    """检查配音文本和HTML内容"""
    results = []
    
    # 1. 错别字扫描
    forbidden = {
        '膜世界': '模世界',
        '精钻膜': '晶钻膜',
        '防窥uvm': '防窥UV膜',
        '壳磁': '壳瓷',
        '电镀丝滑mo': '电镀丝滑膜',
    }
    
    all_text = ' '.join([t[2] for t in texts]) + html_content
    for wrong, correct in forbidden.items():
        if wrong in all_text:
            results.append(('FAIL', f'发现错别字"{wrong}",应为"{correct}"'))
            return {'pass': False, 'details': results}
    
    # 2. 品牌名统一检查
    if '鑫源光电' in html_content and '模世界' not in html_content:
        results.append(('WARN', 'HTML中使用鑫源光电但未使用模世界,请确认品牌'))
    
    # 3. 参数合理性
    # 厚度检查
    thickness_match = re.search(r'(d+.?d*)s*(mm|厘米|m)', all_text)
    if thickness_match:
        val = float(thickness_match.group(1))
        unit = thickness_match.group(2)
        if unit == '厘米' and val > 0.1:
            results.append(('FAIL', f'厚度{val}厘米不合理,应为{val*10}mm'))
            return {'pass': False, 'details': results}
    
    # 透光率检查
    trans_match = re.search(r'(d+.?d*)%', all_text)
    if trans_match:
        val = float(trans_match.group(1))
        if val > 100:
            results.append(('FAIL', f'透光率{val}%超过100%,不合理'))
            return {'pass': False, 'details': results}
    
    # 4. 广告法风险词
    risk_words = ['最', '第一', '绝对', '顶级', '国家级', '独家']
    for word in risk_words:
        if word in all_text:
            results.append(('WARN', f'发现广告法风险词"{word}",建议删除或修改'))
    
    # 5. TTS友好度检测
    tts_issues = []
    # 检查数字+字母组合
    num_alpha = re.findall(r'(d+[A-Z]+)', all_text)
    for na in num_alpha:
        tts_issues.append(f'"{na}"可能被TTS误读(如9H读成九H)')
    
    # 检查小数点
    decimals = re.findall(r'(d+.d+)', all_text)
    for d in decimals:
        tts_issues.append(f'"{d}"可能被TTS读成"零点...",建议确认是否需汉字')
    
    if tts_issues:
        results.append(('WARN', f'TTS朗读潜在问题:{len(tts_issues)}处'))
        for issue in tts_issues[:3]:
            results.append(('INFO', issue))
    
    # 6. 标点符号检查(逗号=短停,句号=长停)
    sentences = re.split(r'[。!?]', all_text)
    for s in sentences:
        if len(s) > 50:
            results.append(('WARN', f'句子过长({len(s)}字),建议拆分'))
    
    results.append(('PASS', '脚本内容检查通过'))
    return {'pass': True, 'details': results}

关卡2:时长匹配检查

时机: 配音生成后、渲染前执行
方法: 对比配音时长与场景动画时间窗口

检查项

def gate2_duration_match(audio_files: dict, html_content: str) -> dict:
    """检查配音时长与场景时间窗口是否匹配"""
    results = []
    
    # 1. 获取每个音频文件的实际时长
    import subprocess
    audio_durations = {}
    for name, path in audio_files.items():
        if os.path.exists(path):
            cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration',
                   '-of', 'default=noprint_wrappers=1:nokey=1', path]
            proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
            if proc.returncode == 0:
                audio_durations[name] = float(proc.stdout.strip())
    
    # 2. 提取HTML中每个场景的时间窗口
    # s1: 0-5s, s2: 5-13s, s3: 13-20s, s4: 20-25s
    scene_windows = {
        's1': (0, 5),
        's2': (5, 13),
        's3': (13, 20),
        's4': (20, 25),
    }
    
    # 3. 对比音频时长与场景窗口
    for name, duration in audio_durations.items():
        scene_num = name[1]  # s1 -> 1
        scene_id = f's{scene_num}'
        if scene_id in scene_windows:
            start, end = scene_windows[scene_id]
            window = end - start
            
            if duration > window + 2:  # 允许2秒缓冲
                results.append(('FAIL', f'{name}时长{duration:.1f}s超过场景窗口{window}s(超限{duration-window:.1f}s)'))
                return {'pass': False, 'details': results}
            elif duration < window - 5:  # 少于窗口5秒告警
                results.append(('WARN', f'{name}时长{duration:.1f}s远小于场景窗口{window}s'))
    
    # 4. 总时长检查
    total_audio = sum(audio_durations.values())
    if total_audio > 27:  # 25秒+2秒缓冲
        results.append(('WARN', f'总配音时长{total_audio:.1f}s,接近或超过25秒'))
    
    results.append(('PASS', '时长匹配检查通过'))
    return {'pass': True, 'details': results}

关卡2.5:音频质量检查

时机: 渲染前执行
方法: ffprobe检测静音、爆音、音量突变

检查项

def gate25_audio_quality(audio_files: dict) -> dict:
    """检查音频文件质量"""
    results = []
    
    import subprocess
    
    for name, path in audio_files.items():
        if not os.path.exists(path):
            results.append(('FAIL', f'{name}文件不存在'))
            return {'pass': False, 'details': results}
        
        # 1. 检测开头静音(前0.5秒应有声音)
        cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format_tags=loudness',
               '-of', 'default=noprint_wrappers=1:nokey=1', path]
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
        
        # 2. 检测总体音量(RMS)
        cmd = ['ffmpeg', '-i', path, '-af', 'astats=metadata=1:reset=0',
               '-f', 'null', '-']
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
        stderr = proc.stderr
        
        # 检查是否有静音段(RMS < -60dB)
        if 'Flat factor' in stderr:
            # 提取RMS值
            import re
            rms_match = re.search(r'RMS level db: (-?d+.?d*)', stderr)
            if rms_match:
                rms = float(rms_match.group(1))
                if rms < -60:
                    results.append(('WARN', f'{name}平均音量过低({rms}dB)'))
        
        # 3. 检测文件大小合理性
        size = os.path.getsize(path)
        if size < 1000:  # 小于1KB通常为静音或空文件
            results.append(('FAIL', f'{name}文件大小仅{size}字节,可能为空或静音'))
            return {'pass': False, 'details': results}
    
    results.append(('PASS', '音频质量检查通过'))
    return {'pass': True, 'details': results}

关卡3:渲染结果检查

时机: 视频渲染完成后执行
方法: 文件检查 + 像素对比验证动画执行

检查项

def gate3_render_result(video_path: str, audio_path: str) -> dict:
    """检查渲染结果"""
    results = []
    
    import subprocess
    import os
    
    # 1. 文件存在性
    if not os.path.exists(video_path):
        results.append(('FAIL', '渲染视频文件不存在'))
        return {'pass': False, 'details': results}
    
    # 2. 文件大小检查
    size = os.path.getsize(video_path)
    if size < 10000:  # 小于10KB异常
        results.append(('FAIL', f'视频文件仅{size}字节,渲染可能失败'))
        return {'pass': False, 'details': results}
    
    # 3. 视频时长检查
    cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration',
           '-of', 'default=noprint_wrappers=1:nokey=1', video_path]
    proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
    if proc.returncode == 0:
        duration = float(proc.stdout.strip())
        if abs(duration - 25) > 2:  # 允许2秒误差
            results.append(('FAIL', f'视频时长{duration:.1f}s,预期25s'))
            return {'pass': False, 'details': results}
    
    # 4. 音频合并检查
    if os.path.exists(audio_path):
        cmd = ['ffprobe', '-v', 'error', '-select_streams', 'a:0',
               '-show_entries', 'stream=codec_type',
               '-of', 'default=noprint_wrappers=1:nokey=1', video_path]
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
        if 'audio' not in proc.stdout:
            results.append(('FAIL', '视频文件中无音频轨道'))
            return {'pass': False, 'details': results}
    
    # 5. 动画执行验证(首帧vs中帧像素差异)
    # 截取0s帧和12.5s帧
    frame0 = video_path.replace('.mp4', '_frame0.png')
    frame_mid = video_path.replace('.mp4', '_frame_mid.png')
    
    subprocess.run(['ffmpeg', '-y', '-ss', '0', '-i', video_path,
                    '-frames:v', '1', frame0], capture_output=True, timeout=30)
    subprocess.run(['ffmpeg', '-y', '-ss', '12.5', '-i', video_path,
                    '-frames:v', '1', frame_mid], capture_output=True, timeout=30)
    
    if os.path.exists(frame0) and os.path.exists(frame_mid):
        # 计算像素差异
        import cv2
        img0 = cv2.imread(frame0)
        img_mid = cv2.imread(frame_mid)
        
        if img0 is not None and img_mid is not None:
            diff = cv2.absdiff(img0, img_mid)
            diff_ratio = np.count_nonzero(diff) / (img0.size / 3)
            
            if diff_ratio < 0.05:  # 差异小于5%
                results.append(('FAIL', '动画可能未执行(首帧与中帧差异<5%)'))
                return {'pass': False, 'details': results}
    
    results.append(('PASS', '渲染结果检查通过'))
    return {'pass': True, 'details': results}

关卡4:视觉抽检

时机: 所有关卡通过后执行
方法: 截取关键帧,用vision模型检查画面

检查项

def gate4_visual_review(video_path: str) -> dict:
    """视觉抽检 — 关键帧画面检查"""
    results = []
    
    # 截取5个关键帧
    keyframes = {
        '0s': '0',
        '5s': '5',
        '13s': '13',
        '20s': '20',
        '24s': '24',
    }
    
    frame_paths = {}
    for name, time_str in keyframes.items():
        frame_path = video_path.replace('.mp4', f'_kf_{name}.png')
        subprocess.run(['ffmpeg', '-y', '-ss', time_str, '-i', video_path,
                        '-frames:v', '1', frame_path], capture_output=True, timeout=30)
        if os.path.exists(frame_path):
            frame_paths[name] = frame_path
    
    # 调用vision模型检查
    # 注意:这里需要vision工具,在skill执行时动态调用
    results.append(('INFO', f'已截取{len(frame_paths)}个关键帧'))
    results.append(('INFO', '请使用vision工具检查以下关键帧:'))
    for name, path in frame_paths.items():
        results.append(('INFO', f'  {name}: {path}'))
    
    # 检查项(人工+vision):
    # 1. 文字是否清晰可读
    # 2. 品牌名是否正确(模世界)
    # 3. Logo是否完整显示
    # 4. 颜色是否符合品牌标准
    # 5. 是否有明显排版错误
    
    results.append(('PASS', '视觉抽检完成(请人工确认)'))
    return {'pass': True, 'details': results}

执行流程

视频生成完成
    │
    ▼
关卡0:HTML结构检查 ──── 失败 → 停止,返回错误
    │ 通过
    ▼
关卡0.5:时间线验证 ──── 失败 → 停止,返回错误
    │ 通过
    ▼
关卡1:脚本内容检查 ──── 失败 → 停止,返回错误
    │ 通过(WARN允许继续)
    ▼
关卡2:时长匹配检查 ──── 失败 → 停止,返回错误
    │ 通过
    ▼
关卡2.5:音频质量检查 ── 失败 → 停止,返回错误
    │ 通过
    ▼
关卡3:渲染结果检查 ──── 失败 → 停止,返回错误
    │ 通过
    ▼
关卡4:视觉抽检 ──────── 人工确认
    │
    ▼
交付视频

告警规则

  • FAIL:必须修复,不得跳过
  • WARN:记录问题,可继续但需在交付报告中说明
  • INFO:提示信息,不影响流程

交付报告格式

## 视频质量门禁报告

- 关卡0 HTML结构:✅ 通过
- 关卡0.5 时间线验证:✅ 通过
- 关卡1 脚本内容:⚠️ WARN(TTS朗读问题3处)
- 关卡2 时长匹配:✅ 通过
- 关卡2.5 音频质量:✅ 通过
- 关卡3 渲染结果:✅ 通过
- 关卡4 视觉抽检:✅ 通过

总耗时:X秒

注意事项

  1. 关卡0-2.5是自动化检查,无需人工干预
  2. 关卡3的像素对比需要opencv-python,如未安装则跳过
  3. 关卡4需要vision模型,如不可用则改为人工检查
  4. 任一关卡FAIL,立即停止流程,不得交付
  5. 品牌名"模世界"是最高优先级检查,发现"膜世界"直接FAIL

关联文件

  • references/integration-matrix.md — 所有引用本skill的视频生产流程清单
  • scripts/quality_gate.py — 自动化执行脚本

Pitfalls(实战经验)

音频截断陷阱(2026-07-08发现 ⭐)

症状:视频文件存在、时长正确、有音频轨道,但实际时长只有21秒而不是25秒。

原因:ffmpeg合成时用了-shortest参数,音频只有21秒所以视频被截断到21秒。

修复:配音短于25秒时,用ffmpeg -af "apad=pad_dur=X"填充到25秒,再用-shortest合成。

# 错误做法:直接 -shortest
ffmpeg -i video.mp4 -i audio.m4a -c:v copy -shortest final.mp4  # 可能被截断

# 正确做法:先填充音频
AUDIO_DUR=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 audio.m4a)
if (( $(echo "$AUDIO_DUR < 23" | bc -l 2>/dev/null || echo 0) )); then
    ffmpeg -y -i audio.m4a -af "apad=pad_dur=$((25-${AUDIO_DUR%.*}))" -t 25 audio_padded.m4a
    ffmpeg -y -i video.mp4 -i audio_padded.m4a -c:v copy -c:a aac -b:a 192k -shortest final.mp4
else
    ffmpeg -y -i video.mp4 -i audio.m4a -c:v copy -c:a aac -b:a 192k -shortest final.mp4
fi

场景选择器兼容(2026-07-08发现)

V7模板使用class="scene s1"而非id="s1"。关卡0和关卡0.5必须同时检查两种写法。

TTS误报(2026-07-08发现)

关卡1的TTS友好度检测会扫描整个HTML(包括CSS类名如6B、4ECDC),产生大量误报。实际只需检查配音文本文件,不应扫描HTML源码中的class/id。

相关专题

更多
Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

0

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

0

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

0

10

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

2026.10.22

0

11

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

2026.10.22

0

10

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

2026.10.22

0

15

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

2026.10.22

0

10

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

2026.10.22

0

25

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

2026.10.22

0

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习