手头刚好有一个很契合当下合规趋势的小需求:给图片和视频加上欧盟 AI 内容标签。最近在梳理生成式 AI 落地流程时,发现内容溯源和 AI 披露已经从“可选项”变成了很多业务的“必答题”。今天就把一套可运行的标签添加方案拆开讲清楚,包含核心概念、环境准备、完整代码、运行验证和常见坑点。无论你是做内容平台、多媒体工具,还是想给自己的 AI 应用补齐合规能力,这篇都能直接照着做。
1. 背景与核心概念
1.1 什么是 EU AI content label
EU AI content label 是欧盟《人工智能法案》(EU AI Act)推进过程中提出的内容标识机制。它的目的很明确:当一段文字、图片、音频或视频由 AI 生成或深度伪造时,需要向用户提供清晰的标识信息,让用户知道“这不是传统意义上由人类直接创作的内容”。
这个标签不是某个单一平台的“已加 AI”按钮,而是需要嵌入到文件本身的一种元数据体系。也就是说,图片或视频文件在导出后,即使被下载、转发、二次上传,标签信息仍然保留在文件内部,便于校验和追溯。
从实现角度看,官方推荐的技术路径通常包括三类:
- 元数据标注:将 AI 生成信息写入 EXIF、XMP、IPTC 等标准元数据字段。
- 水印技术:在图像或视频画面中叠加可感知或不可感知的水印。
- 加密签名与溯源信息:基于 C2PA(Coalition for Content Provenance and Authenticity)等标准,记录内容的来源、编辑历史和生成工具。
本文的实战部分将以“元数据标注”为主线,并给出视频文件的标签添加方式。对于需要更强溯源能力的场景,我会补充 C2PA 的工程思路。
1.2 为什么现在需要关注内容标签
目前生成式 AI 工具已经非常普及。用户可以用 Stable Diffusion、Midjourney、各类视频生成模型在几分钟内生产出非常逼真的内容。随之而来的问题是:虚假信息、冒名内容、深度伪造视频开始增多。
在这种情况下,内容平台和工具开发者需要考虑几个问题:
- 用户看到的内容,是否由 AI 生成?
- 如果内容被用于新闻、医疗、法律等严肃场景,能否追溯生成链条?
- 平台如何向监管方证明自己已经执行了披露义务?
当监管要求逐步收紧后,AI 内容标签会成为基础能力。如果等到政策强制落地再补,整个内容管线的改造压力会大很多。
1.3 常见应用场景
这类标签能力的典型应用场景包括:
| 场景 | 说明 |
|---|---|
| AI 绘图工具 | 用户导出图片时自动写入 AI 生成标签 |
| 视频生成平台 | 成品视频的元数据中标注 AI 生成信息 |
| 社交媒体审核 | 平台检测并展示 AI 内容标识 |
| 新闻与版权系统 | 区分人类创作与 AI 合成内容,便于版权认定 |
| 企业素材库 | 在 DAM(数字资产管理系统)中标记 AI 资产 |
无论你是在做客户端工具还是后端处理服务,下面这套方案都能提供参考。
2. 环境准备与版本说明
2.1 运行环境
本文示例使用 Python 3.10+ 编写命令行工具。操作环境为 Ubuntu 22.04 / macOS,但 Windows 下同样适用,只需要注意命令行工具的安装方式。
需要说明的是:不同操作系统中,exiftool 和 ffmpeg 的安装命令不同,因此建议先在本地终端验证这两个命令是否可执行。
2.2 依赖工具清单
| 工具 | 用途 | 安装方式 |
|---|---|---|
| Python 3.10+ | 编写主逻辑 | 官网下载或包管理器安装 |
| ExifTool | 读写图片/视频元数据 | macOS:brew install exiftool;Ubuntu:sudo apt install libimage-exiftool-perl;Windows: 下载安装包 |
| FFmpeg | 处理视频元数据 | macOS:brew install ffmpeg;Ubuntu:sudo apt install ffmpeg;Windows: 下载安装包 |
| Pillow | Python 端图像快速验证 | pip install Pillow |
如果你只是测试图片标签,可以不安装 FFmpeg;视频部分才需要。
2.3 示例项目结构
为了保持逻辑清晰,我们把项目组织成下面这样:
eu-ai-label-tool/ ├── requirements.txt ├── README.md └── ai_label_tool.pyai_label_tool.py作为单文件命令工具,包含图片和视频两套处理函数。这样做的好处是便于复制、学习和二次改造;如果后续需要集成到 Django/FastAPI 服务中,再按模块拆分即可。
3. 核心实现原理
3.1 为什么选择 XMP / 通用元数据
EXIF、IPTC、XMP 是多媒体文件中常用的三类元数据标准。其中:
- EXIF 主要用于存储相机参数,比如光圈、快门、ISO。
- IPTC 常用于新闻图片的标题、作者、版权说明。
- XMP 是 Adobe 提出的可扩展元数据平台,灵活度最高,被很多内容平台接受。
给 AI 内容做标签时,首选 XMP 和 IPTC 字段。因为它们不依赖某个厂商私有格式,跨平台工具都能读取。
为了不编造并不确定的字段名,我们采用保守但通用的做法:
- 在
Description中加入 AI 生成声明。 - 在
Label或版权相关字段中写入标签信息。 - 在
Comment中写清楚“本内容由 AI 生成”。
如果你的业务对标准字段有严格规范,建议查阅欧盟 AI 内容标签官方技术说明或 C2PA 规范,按正式字段配置,而不是仅依赖自定义字段。
3.2 ExifTool 的工作方式
ExifTool 是目前最常用的元数据读写工具。它通过命令行参数指定要写入的标签和值,执行后可以覆盖写原文件或输出到新文件。
一个最简单的图片标签写入命令如下:
exiftool -Description="AI generated content according to EU AI Act" -Label="AI-generated" input.jpg默认情况下,ExifTool 会生成一个_original备份文件,避免原文件被破坏。在自动化脚本中,我们可以通过-overwrite_original压掉备份,也可以保留备份以便出问题时回滚。
3.3 FFmpeg 在视频侧的做法
视频文件和图片不同,它通常封装为 MP4/MOV 等容器格式。FFmpeg 可以不改编码、不做转码,仅通过-metadata参数修改容器级元数据。
ffmpeg -i input.mp4 -metadata title="AI generated content" -metadata comment="EU AI Act notice" -c copy output.mp4-c copy表示复制视频流和音频流,不做重新编码,速度非常快,也不会造成画质损失。
3.4 如何校验标签是否写入成功
写入完成后,必须回头读取元数据来验证结果。
- 图片用
exiftool output.jpg - 视频用
ffprobe -show_format output.mp4
这两条命令会输出文件内部的元数据信息。如果我们在输出中看到对应的字段值,说明标签已经成功嵌入。
4. 完整实战案例
下面我们实现一个可运行的命令行工具。它提供两个子命令:
add-image:给图片添加 EU AI 内容标签。add-video:给视频添加 EU AI 内容标签。verify:检查文件是否包含 AI 内容标签。
4.1 创建项目文件
首先创建项目目录:
mkdir eu-ai-label-tool cd eu-ai-label-tool然后创建ai_label_tool.py,把下面的代码复制进去。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ EU AI content label tool. 给图片和视频写入欧盟 AI 内容标签的示例工具。 """ import argparse import subprocess import sys from pathlib import Path def run_command(cmd: list) -> None: """执行外部命令,失败时抛异常。""" print("[CMD]", " ".join(cmd)) result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print("[STDERR]", result.stderr, file=sys.stderr) raise RuntimeError(f"Command failed: {' '.join(cmd)}") if result.stdout: print("[STDOUT]", result.stdout) def add_image_label(input_path: Path, output_path: Path, model_name: str = "unknown") -> None: """ 为图片文件写入 EU AI 内容标签。 说明: 这里通过 ExifTool 写入通用的 XMP/IPTC 字段。 如果业务有严格合规要求,需要按官方规范补充签名与溯源字段。 """ if not input_path.exists(): raise FileNotFoundError(f"Input image not found: {input_path}") output_path.parent.mkdir(parents=True, exist_ok=True) # 基于 XMP 的通用 AI 生成声明 description = "AI generated content (EU AI Act label)" label = "AI-generated" creator_tool = model_name cmd = [ "exiftool", f"-Description={description}", f"-Label={label}", f"-CreatorTool={creator_tool}", "-overwrite_original", "-o", str(output_path), str(input_path), ] run_command(cmd) def add_video_label(input_path: Path, output_path: Path, model_name: str = "unknown") -> None: """ 为视频文件写入 EU AI 内容标签。 说明: 使用 FFmpeg 的 metadata 机制,不做重新编码。 注意:不同容器和播放器对 comment 字段的支持程度不同。 """ if not input_path.exists(): raise FileNotFoundError(f"Input video not found: {input_path}") output_path.parent.mkdir(parents=True, exist_ok=True) cmd = [ "ffmpeg", "-y", "-i", str(input_path), "-metadata", f"title=AI generated content (EU AI Act label)", "-metadata", f"comment=AI-generated by {model_name}", "-c", "copy", str(output_path), ] run_command(cmd) def verify_label(file_path: Path) -> None: """检查文件是否包含 AI 内容标签。""" if not file_path.exists(): raise FileNotFoundError(f"File not found: {file_path}") suffix = file_path.suffix.lower() if suffix in {".jpg", ".jpeg", ".png", ".tiff", ".webp", ".heic"}: cmd = ["exiftool", str(file_path)] run_command(cmd) elif suffix in {".mp4", ".mov", ".mkv", ".avi"}: cmd = ["ffprobe", "-show_format", str(file_path)] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print("[STDERR]", result.stderr, file=sys.stderr) raise RuntimeError("ffprobe command failed") print("[STDOUT]") print(result.stdout) else: print(f"Unsupported file type: {suffix}") def main() -> None: parser = argparse.ArgumentParser(description="EU AI content label tool") subparsers = parser.add_subparsers(dest="command", required=True) parser_img = subparsers.add_parser("add-image", help="Add EU AI label to an image") parser_img.add_argument("input", type=Path, help="Input image path") parser_img.add_argument("output", type=Path, help="Output image path") parser_img.add_argument("--model", default="unknown", help="AI model name") parser_vid = subparsers.add_parser("add-video", help="Add EU AI label to a video") parser_vid.add_argument("input", type=Path, help="Input video path") parser_vid.add_argument("output", type=Path, help="Output video path") parser_vid.add_argument("--model", default="unknown", help="AI model name") parser_ver = subparsers.add_parser("verify", help="Verify AI label in a file") parser_ver.add_argument("file", type=Path, help="Image or video path") args = parser.parse_args() if args.command == "add-image": add_image_label(args.input, args.output, args.model) print("Image label added successfully.") elif args.command == "add-video": add_video_label(args.input, args.output, args.model) print("Video label added successfully.") elif args.command == "verify": verify_label(args.file) else: parser.print_help() if __name__ == "__main__": main()这段代码的核心逻辑如下:
- 使用
argparse解析子命令。 - 调用系统安装的
exiftool或ffmpeg完成标签写入。 run_command函数统一打印命令和输出,方便调试。- 输出路径的父目录会自动创建,避免目录不存在导致的失败。
- 图片处理时使用
-o输出到新文件,不直接覆盖原文件,更加安全。
4.2 安装第三方依赖
本项目实际只用到标准库,第三方依赖并非必须。如果你需要额外用 Pillow 做图像快速判断,可以创建requirements.txt:
Pillow==10.3.0安装命令:
pip install -r requirements.txt4.3 准备测试素材
为了验证效果,我们可以先用 FFmpeg 生成一张测试图片和一个测试视频。
生成一张纯色测试图片:
ffmpeg -f lavfi -i color=c=blue:s=640x360:d=1 -frames:v 1 test_input.jpg -y生成一个 3 秒测试视频:
ffmpeg -f lavfi -i testsrc=duration=3:size=640x360:rate=30 test_input.mp4 -y这样我们就有了一份不涉及版权问题的测试素材。
4.4 运行标签添加
给图片添加标签:
python3 ai_label_tool.py add-image test_input.jpg labeled_image.jpg --model "MyAITool-v1"给视频添加标签:
python3 ai_label_tool.py add-video test_input.mp4 labeled_video.mp4 --model "VideoGenModel-v2"执行后,当前目录会生成labeled_image.jpg和labeled_video.mp4。
4.5 验证结果
查看图片元数据:
python3 ai_label_tool.py verify labeled_image.jpg在输出中,你会看到类似下面的关键字段:
Description : AI generated content (EU AI Act label) Label : AI-generated Creator Tool : MyAITool-v1查看视频元数据:
python3 ai_label_tool.py verify labeled_video.mp4输出中的format区块会包含title和comment字段,证明标签写入成功。
4.6 扩展:批量处理目录
实际项目中,可能需要批量处理一个目录下的所有图片。可以在命令行工具基础上增加一个循环,或者用 Shell 脚本调用。
下面是一个简单的 Python 批量示例:
from pathlib import Path from ai_label_tool import add_image_label input_dir = Path("./images") output_dir = Path("./labeled_images") for image_path in input_dir.glob("*.jpg"): output_path = output_dir / image_path.name add_image_label(image_path, output_path, model_name="batch-model")这里的重点是每个文件都单独调用exiftool,优点是单文件失败不会影响整个批次;缺点是速度较慢,适合中小规模文件集。如果文件量非常大,可以考虑并发方案。
5. 常见问题与排查思路
5.1 常见问题对照表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
exiftool: command not found | ExifTool 未安装 | 根据系统安装 ExifTool |
ffmpeg: command not found | FFmpeg 未安装 | 根据系统安装 FFmpeg |
| 图片输出后元数据为空 | 输出路径或参数顺序错误 | 检查-o参数是否正确 |
视频title写入后某些播放器不显示 | 播放器不支持容器元数据 | 在应用层读取并展示标签 |
| PNG 元数据不支持某些 XMP 字段 | 文件格式本身限制 | 改用 JPG/TIFF 或使用 C2PA 方案 |
| 原文件被覆盖 | 没有使用-o且没有备份 | 改用-o输出新文件,保留原始素材 |
| 批量处理时权限报错 | 输出目录权限不足 | 使用可写目录并在脚本中创建父目录 |
5.2 图片写入后看不到标签
如果执行成功但看不到标签,先用exiftool input.jpg看原文件本身是否支持 XMP 写入。部分极简 PNG 文件或 WebP 文件在旧版本 ExifTool 下可能支持不完整。
解决办法是升级 ExifTool 到最新版本,或者转换格式后再写入。
exiftool -ver如果版本过旧,建议升级到 12.60 以上。具体版本以官方发布为准。
5.3 视频标签写入后校验不到
FFmpeg 的-metadata会写入容器级元数据,但不同容器的支持程度不同:
- MP4:支持
title、comment等字段。 - MOV:通常支持良好。
- MKV:字段名在不同工具下可能解析不一致。
- AVI:支持有限。
如果我们添加的是自定义字段,玩家可能忽略它。因此视频方案更推荐“容器元数据 + 画面内水印/片头声明”的组合。
5.4 如何避免原文件被破坏
在 ExifTool 中,最安全的方式是像本文示例一样使用-o参数输出到新文件。这样原文件始终保留,出问题时可以重新处理。
如果出于存储考虑想直接覆盖,可以在确认测试无误后再去掉-o,并使用-overwrite_original。
6. 最佳实践与工程建议
6.1 在内容生成管线中默认加标签
AI 内容标签不应是事后手动补的步骤,而应该是内容生成流水线的一部分。也就是说,当用户点击“生成图片”或“生成视频”时,系统在导出阶段就应该自动写入标签。
这样可以避免两个问题:
- 人工忘记添加标签。
- 用户下载后再补标签,链路不可控。
在工程实现上,可以在模型服务返回结果后,紧接着执行一个标签写入模块。这个模块可以是独立的微服务,也可以是一个异步任务。
6.2 组合使用元数据与水印
元数据最大的弱点是:文件一旦被截图、屏幕录制或转码,标签可能丢失。
因此,对于需要强披露的场景,建议组合使用:
- 元数据写入。
- 图像/视频边界水印。
- C2PA 签名溯源。
C2PA 是目前比较受关注的内容溯源标准。它可以记录内容从哪里来、经过哪些编辑、由什么工具生成。如果你的项目需要做到严格可信的溯源,建议调研 C2PA 官方 SDK,并将 C2PA 信息嵌入到文件中。
注意:C2PA 的 API 和工具链仍在快速演进,接入时要以官方仓库和文档为准。
6.3 日志与追溯
每次给内容打标签时,建议记录以下日志信息:
- 文件原始哈希值。
- 输出文件哈希值。
- 使用的模型名称与版本。
- 标签写入时间。
- 处理人/调用方标识。
这样可以形成一条可审计的内容处理链路。即使内容后续出现问题,也能快速定位是哪一批数据、哪一个环节出了问题。
下面是一个简单的日志字段示例:
{ "source_file": "/data/raw/abc.jpg", "output_file": "/data/labeled/abc.jpg", "source_md5": "d41d8cd98f00b204e9800998ecf8427e", "output_md5": "fbbc9b6e5f9c7f6f0f9c0b7d6f9f4f2e", "model_name": "stable-diffusion-xl", "label_time": "2025-01-15T10:30:00Z" }6.4 安全边界与权限控制
给内容加标签通常不会涉及复杂权限,但要注意以下几点:
- 不要允许用户任意删除或伪造“人类创作”标签,尤其在生产环境。
- 标签写入操作必须经过服务端校验,不能只靠前端传参。
- 如果标签用于审计或合规,需要保护原始文件不被篡改,必要时保存哈希链。
简单说:加标签是普通能力,但标签的“权威性”才是合规价值所在。
6.5 性能优化
ExifTool 和 FFmpeg 都是外部进程,每次调用都有一定的进程启动开销。在高并发场景下,建议:
- 使用进程池复用外部进程。
- 对图片批量处理时,一次 ExifTool 调用可以传入多个文件。
- 对视频处理,避免重复编码,始终使用
-c copy。 - 将耗时操作放入消息队列,避免阻塞 API 请求。
如果处理的文件量一天超过几十万,建议使用 C++/Go/Rust 实现底层处理模块,或直接调用对应库,而不是频繁拉起外部命令。
6.6 兼容性测试清单
上线前,至少对以下组合做一轮兼容性验证:
- JPG 与 PNG。
- MP4 与 MOV。
- 常用播放器是否读取元数据。
- 图片压缩后标签是否保留。
- 视频二次转码后标签是否保留。
- 平台审核系统是否能识别标签。
这些测试决定了你的标签方案是“给自己看”还是“给生态看”。
7. 总结与后续学习方向
这篇教程从一个实际需求出发,实现了给图片和视频添加 EU AI 内容标签的最小工具。你不仅了解了 AI 内容标签产生的背景,还掌握了基于 ExifTool 和 FFmpeg 的元数据写入方法,以及如何通过verify子命令验证标签是否生效。
如果你想继续深入,可以从下面几个方向入手:
- 完整阅读 EU AI Act 中关于生成式 AI 透明度义务的条款,理解合规边界。
- 学习 C2PA 技术规范,了解如何生成带签名和证书链的内容溯源信息。
- 调研主流内容平台对 AI 内容标签的识别规则,设计更通用的标签策略。
- 把这个命令行工具改造成 FastAPI 服务,集成到现有的内容生产系统中。
动手在本地图片和视频上试一遍,再用exiftool和ffprobe查看输出文件,你会更直观地理解“内容标签”在文件层面到底做了什么。这对后续做 AI 产品、内容平台或合规系统都很有帮助。