媒体文件问题检测脚本(一)(python+ffmpeg)

一、效果

使用方法:在安装了anaconda和ffmpeg以后(安装对应的库也行)(ffmpeg需要加入系统路径)

命令: python <脚本名称> <档案名称>

二、代码

test1.py

python 复制代码
import subprocess
import pandas as pd
import matplotlib.pyplot as plt

def extract_packets_data(filename):
    """
    使用 ffprobe 获取packet的多字段数据,csv格式输出。
    - 解析字段包括:codec_type, pts, pts_time, pos, size
    """
    cmd = [
        'ffprobe',
        '-show_packets',
        filename,
        '-show_entries', 'packet=codec_type,pts,pts_time,pos,size',
        '-of', 'csv=p=0'
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"ffprobe error: {result.stderr}")
    return result.stdout

def parse_ffprobe_output(raw_text):
    """
    解析 ffprobe 输出的csv数据,返回 DataFrame
    每行格式是 codec_type,pts,pts_time,pos,size
    parts[0] = codec_type
    parts[1] = pts
    parts[2] = pts_time
    parts[4] = pos (pkt_pos)
    parts[3] = size (pkt_size)
    """
    lines = raw_text.strip().splitlines()
    data = []
    for line in lines:
        parts = line.split(',')
        if len(parts) != 5:
            continue
        codec_type = parts[0].strip()
        try:
            pts = float(parts[1]) if parts[1] != 'N/A' else None
            pts_time = float(parts[2]) if parts[2] != 'N/A' else None
            pkt_pos = int(parts[4]) if parts[4] != 'N/A' else None
            pkt_size = int(parts[3]) if parts[3] != 'N/A' else None
        except (ValueError, TypeError):
            continue
        data.append({
            'Type': codec_type,
            'pts': pts,
            'pts_time': pts_time,
            'pkt_pos': pkt_pos,
            'pkt_size': pkt_size,
        })
    df = pd.DataFrame(data)
    df['seq'] = range(1, len(df) + 1)
    return df

def save_csv(df, csv_filename):
    df.to_csv(csv_filename, index=False)

def plot_multi(df, output_prefix):
    """
    生成多副散点图,帮助分析:
    - 横轴:seq (包序号)
    - 纵轴分别是 pts, pts_time, pkt_pos, pkt_size
    - 点颜色区分 audio/video/subtitle
    """
    base_colors = {
        'video': 'tab:blue',
        'audio': 'tab:orange',
        'subtitle': 'tab:green'
    }
    types_in_data = df['Type'].unique()

    y_cols = ['pts', 'pts_time', 'pkt_pos', 'pkt_size']
    y_labels = ['PTS', 'PTS (seconds)', 'Packet Position (file offset)', 'Packet Size (bytes)']

    for y_col, ylabel in zip(y_cols, y_labels):
        plt.figure(figsize=(12, 6))
        for t in types_in_data:
            sub = df[df['Type'] == t]
            color = base_colors.get(t, 'gray')
            plt.scatter(sub['seq'], sub[y_col], label=t, color=color, s=20, alpha=0.6)

        plt.xlabel('Packet Sequence Number')
        plt.ylabel(ylabel)
        plt.title(f'{ylabel} vs Packet Sequence Number by Packet Type')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.tight_layout()
        filename = f"{output_prefix}_{y_col}.png"
        # plt.savefig(filename, dpi=150)
        print(f"Saved plot: {filename}")
        plt.show()

def main(filename):
    print("Extracting packet data with ffprobe...")
    raw = extract_packets_data(filename)

    print("Parsing packet data...")
    df = parse_ffprobe_output(raw)

    csv_file = filename + '_packets.csv'
    print(f"Saving packet CSV to: {csv_file}")
    save_csv(df, csv_file)

    print("Generating analysis plots...")
    plot_multi(df, filename + '_analysis')

    print("All done.")

if __name__ == '__main__':
    import sys
    if len(sys.argv) < 2:
        print("Usage: python script.py video_file")
        exit(1)
    video_file = sys.argv[1]
    main(video_file)

test2.py

python 复制代码
import subprocess
import json
import csv
import os
import matplotlib.pyplot as plt
import matplotlib

# 配置 matplotlib 中文字体,防止中文乱码
matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # Windows常用黑体,如无请换成支持中文的字体
matplotlib.rcParams['axes.unicode_minus'] = False    # 负号正常显示

def run_ffprobe_packets(file_path: str):
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"文件不存在: {file_path}")

    cmd = [
        "ffprobe",
        "-v", "error",
        "-show_packets",
        "-print_format", "json",
        file_path
    ]

    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"ffprobe 执行失败: {result.stderr}")

    return json.loads(result.stdout)


def analyze_packets(packets):
    threshold_pts_dts_sec = 5
    size_min = 10
    size_max = 5 * 1024 * 1024

    last_pts = {}
    last_dts = {}

    analyzed = []
    for p in packets:
        err = []
        si = p.get('stream_index')
        pts_time = safe_float(p.get('pts_time'))
        dts_time = safe_float(p.get('dts_time'))
        size = int(p.get('size', 0))

        if si in last_pts and pts_time >= 0:
            if pts_time < last_pts[si]:
                err.append("PTS逆序")
        last_pts[si] = pts_time

        if si in last_dts and dts_time >= 0:
            if dts_time < last_dts[si]:
                err.append("DTS逆序")
        last_dts[si] = dts_time

        if pts_time >= 0 and dts_time >= 0 and abs(pts_time - dts_time) > threshold_pts_dts_sec:
            err.append("PTS-DTS差距异常")

        if size < size_min:
            err.append("包大小异常-过小")
        elif size > size_max:
            err.append("包大小异常-过大")

        p['error_type'] = ",".join(err)
        p['pts_time'] = pts_time
        p['dts_time'] = dts_time
        p['size'] = size
        p['stream_index'] = si
        analyzed.append(p)
    return analyzed


def safe_float(x):
    try:
        return float(x)
    except Exception:
        return -1


def write_csv(packets, csv_path):
    with open(csv_path, "w", encoding="utf-8-sig", newline='') as f:
        writer = csv.writer(f)
        writer.writerow(["packet_index", "stream_index", "pts_time", "dts_time", "size", "error_type"])
        for i, p in enumerate(packets):
            writer.writerow([
                i,
                p.get('stream_index'),
                p.get('pts_time'),
                p.get('dts_time'),
                p.get('size'),
                p.get('error_type')
            ])


def plot_packet_trends(packets, file_path):
    # 识别视频和音频流索引(默认0是视频,1是音频)
    video_streams = [0]
    audio_streams = [1]

    video_pts, video_dts, video_size, video_error_x = [], [], [], []
    audio_pts, audio_dts, audio_size, audio_error_x = [], [], [], []

    video_x, audio_x = [], []

    video_count = 0
    audio_count = 0

    for p in packets:
        si = p.get('stream_index')
        pts = p.get('pts_time')
        dts = p.get('dts_time')
        size = p.get('size')
        error = p.get('error_type')

        if si in video_streams:
            video_x.append(video_count)
            video_pts.append(pts)
            video_dts.append(dts)
            video_size.append(size)
            if error:
                video_error_x.append(video_count)
            video_count += 1
        elif si in audio_streams:
            audio_x.append(audio_count)
            audio_pts.append(pts)
            audio_dts.append(dts)
            audio_size.append(size)
            if error:
                audio_error_x.append(audio_count)
            audio_count += 1

    plt.figure(figsize=(18, 16))

    # 视频散点图 PTS 和 DTS (加微小y轴偏移避免点完全重叠)
    plt.subplot(4, 1, 1)
    plt.scatter(video_x, video_pts, label="PTS 视频", color='blue', s=16, alpha=0.7, marker='o', edgecolors='blue')
    # 给 DTS 点加0.1秒微小偏移避免和PTS点重合覆盖
    video_dts_offset = [x + 0.1 if x >= 0 else x for x in video_dts]
    plt.scatter(video_x, video_dts_offset, label="DTS 视频", color='cyan', s=8, alpha=0.7, marker='^', edgecolors='cyan')

    if video_error_x:
        plt.scatter(video_error_x, [video_pts[x] for x in video_error_x], c='red', label="异常包", marker='x', s=50)

    plt.title("视频流 PTS 和 DTS 趋势 (DTS加0.1秒偏移防止覆盖)")
    plt.xlabel("视频流包序号")
    plt.ylabel("时间 (秒)")
    plt.legend()

    # 音频散点图 PTS 和 DTS (同样加微小偏移)
    plt.subplot(4, 1, 2)
    plt.scatter(audio_x, audio_pts, label="PTS 音频", color='green', s=16, alpha=0.7, marker='o', edgecolors='blue')
    audio_dts_offset = [x + 0.05 if x >= 0 else x for x in audio_dts]
    plt.scatter(audio_x, audio_dts_offset, label="DTS 音频", color='lime', s=8, alpha=0.7, marker='^', edgecolors='cyan')

    if audio_error_x:
        plt.scatter(audio_error_x, [audio_pts[x] for x in audio_error_x], c='red', label="异常包", marker='x', s=50)

    plt.title("音频流 PTS 和 DTS 趋势 (DTS加0.05秒偏移防止覆盖)")
    plt.xlabel("音频流包序号")
    plt.ylabel("时间 (秒)")
    plt.legend()

    # 包大小趋势
    plt.subplot(4, 1, 3)
    plt.plot(video_x, video_size, label="视频包大小", color='magenta')
    plt.plot(audio_x, audio_size, label="音频包大小", color='orange')
    plt.title("包大小趋势")
    plt.xlabel("包序号")
    plt.ylabel("大小 (字节)")
    plt.legend()

    # 新增图:视频PTS和音频PTS对比图
    plt.subplot(4, 1, 4)
    plt.scatter(video_x, video_pts, label="PTS 视频", color='blue', s=16, alpha=0.7, marker='o', edgecolors='green')
    plt.scatter(audio_x, audio_pts, label="PTS 音频", color='green', s=8, alpha=0.7, marker='^', edgecolors='lime')
    plt.title("视频PTS和音频PTS趋势对比")
    plt.xlabel("包序号")
    plt.ylabel("时间 (秒)")
    plt.legend()

    plt.tight_layout()
    plt.suptitle(f"文件:{os.path.basename(file_path)} 的包数据趋势", y=1.03, fontsize=18)
    plt.show()


def main(file_path):
    print(f"开始检测: {file_path}")

    try:
        data = run_ffprobe_packets(file_path)
        packets = data.get("packets", [])
        if not packets:
            print(f"{file_path} 无包数据,可能格式不支持或文件损坏")
            return

        analyzed = analyze_packets(packets)
        csv_path = file_path + ".packet_analysis.csv"
        write_csv(analyzed, csv_path)
        print(f"包分析结果已保存至: {csv_path}")

        plot_packet_trends(analyzed, file_path)

    except Exception as e:
        print(f"处理文件时出错: {e}")


if __name__ == "__main__":
    import sys

    if len(sys.argv) != 2:
        print("用法: python detect_packets_trend.py 文件名")
    else:
        main(sys.argv[1])
相关推荐
速易达网络2 小时前
flask与fastapi的区别
python
2501_941111842 小时前
分布式日志系统实现
开发语言·c++·算法
ycydynq2 小时前
python html 解析的一些写法
linux·python·html
西猫雷婶2 小时前
CNN的四维Pytorch张量格式
人工智能·pytorch·python·深度学习·神经网络·机器学习·cnn
未来之窗软件服务3 小时前
幽冥大陆(二十三)python语言智慧农业电子秤读取——东方仙盟炼气期
开发语言·python·仙盟创梦ide·东方仙盟·东方仙盟sdk·东方仙盟浏览器
程序员三藏3 小时前
Web自动化测试详细流程和步骤
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
不会c嘎嘎3 小时前
C++ -- stack和queue
开发语言·c++·rpc
数据知道3 小时前
FastAPI基础项目:仿头条新闻的web项目,实现基本的新闻列表页和详情页查看功能
前端·python·fastapi·python项目
CodeByV3 小时前
【C++】C++11:其他重要特性
开发语言·c++