视频时间码 SMPTE
HH:MM:SS:FF 时间码 ↔ 秒 / 帧数 / 毫秒互转 · 8 种帧率
时间戳/计时水印/帧号叠加
HH:MM:SS:FF 时间码 ↔ 秒 / 帧数 / 毫秒互转 · 8 种帧率
SMPTE(电影电视工程师协会)时间码格式 HH:MM:SS:FF,FF 是帧序号(0 到 fps-1)。
Drop-frame:29.97 / 59.94 帧率为补偿与实际秒数的差,每分钟丢 2 帧(10 分钟例外),用 ; 表示(如 00:01:30;12)。
应用:剪辑软件(Premiere / Final Cut / DaVinci)/ 广播业 / 同步多机位拍摄。
了解工具定位 · 使用场景 · 对比优势
剪辑师在长视频中需要快速定位多个关键帧(如采访中的问答节点、赛事进球瞬间)。手动打点耗时且容易遗漏。本工具通过叠加时间码水印,让每个画面自带精确到帧的时间标记,剪辑时直接按时间码跳转,无需反复拖拽进度条,大幅提升粗剪效率。
体育教练或视频分析员在比赛录像中标记战术执行点(如防守失误、进攻路线)。传统方法需在播放器外手动记录时间,回看时难以对齐。本工具直接在画面上叠加计时水印,分析时可根据水印时间快速定位到任意战术环节,方便团队复盘讨论。
科研人员录制高速实验(如液滴碰撞、材料断裂),后续需逐帧分析关键事件。视频文件帧率不固定或播放器无法显示帧号,导致难以引用具体帧。本工具在视频上叠加帧号水印,每一帧都有唯一编号,论文中可直接引用帧号作为证据,避免歧义。
安防人员或律师需要从监控录像中提取特定时间段的证据片段(如盗窃发生时间)。监控录像时间长、时间点模糊,手动截取容易出错。本工具在画面上叠加实时时间戳水印,取证时可直接根据水印时间精确剪切所需片段,确保时间线准确可信。
在线课程制作者在录播教程中添加操作步骤的时间标记,方便学员跳转。手动添加章节标记繁琐且易错。本工具在视频上叠加计时水印,学员观看时可直接根据水印时间快速定位到关键知识点(如“第 3 分 20 秒的参数设置”),提升学习效率。
| 维度 | 本工具 | 竞品 A(Adobe Premiere Pro) | 传统方法(手动记录) |
|---|---|---|---|
| 数据隐私 | 纯浏览器端处理,视频不上传服务器 | 视频需导入本地软件,不涉及网络传输 | 视频文件本地存储,无网络风险 |
| 处理速度 | 1-3 秒完成叠加 | 需渲染导出,速度取决于视频长度和特效复杂度(通常数分钟) | 逐帧观看并手动记录,速度极慢(1 分钟视频约需 10-15 分钟) |
| 离线可用 | 完全离线,依赖浏览器 WASM 能力 | 需安装软件,离线可用 | 完全离线,无需任何软件 |
| 大小限制 | 受浏览器内存限制,建议 2GB 以下视频 | 无严格限制,取决于硬件配置 | 无限制,但人工处理长视频不现实 |
| 收费 | 免费 | 付费订阅(约 ¥200/月) | 免费(仅需时间) |
| 注册 | 无需注册,打开即用 | 需注册 Adobe 账号 | 无需注册 |
| 平台 | 任何现代浏览器(Windows/macOS/Linux) | 仅 Windows/macOS | 任何平台,仅需播放器 |
| 精确度 | 毫秒级时间戳,帧号精确到单帧 | 毫秒级时间戳,支持帧号 | 依赖人工反应速度,误差通常在 0.5-2 秒 |
| 批量处理 | 单次处理一个视频 | 支持批量导出 | 无法批量,需逐个处理 |
| 输出格式 | 直接叠加在视频上,输出 MP4 | 支持多种输出格式和编码 | 输出为文本笔记,无视频叠加 |
上手步骤 · 输入输出 · 避坑提示
| 输入 | 输出 | 说明 |
|---|---|---|
| input.mp4 00:00:00 00:00:10 00:00:20 00:00:30 | 输出视频(input_timestamp.mp4),画面左上角叠加白色半透明时间戳:00:00:00 → 00:00:10 → 00:00:20 → 00:00:30 | 典型场景:为视频片段逐帧添加时间码水印 |
| input.mp4 00:01:30 00:01:35 00:01:40 | 输出视频(input_timestamp.mp4),时间戳从 00:01:30 开始,每 5 秒一个标记点 | 常见用法:标注视频内特定时间区间 |
| input.mp4 00:00:00.500 | 输出视频(input_timestamp.mp4),时间戳精确到毫秒:00:00:00.500 | 边界 case:毫秒级精度输入,适用于高帧率素材 |
| input.mp4 99:59:59 | 输出视频(input_timestamp.mp4),时间戳显示 99:59:59 | 边界 case:超长视频(>24 小时)的时间码上限 |
| input.mp4 -00:00:01 | 错误提示:时间戳不能为负数 | 易错 case:用户误输入负值时间 |
| input.mp4 00:00:60 | 错误提示:秒数超出范围(0-59) | 易错 case:用户习惯性输入 60 秒(应为 01:00) |
| input.mp4 00:00:00 00:00:00 | 输出视频(input_timestamp.mp4),仅保留一个去重后的时间戳:00:00:00 | 边界 case:重复时间戳自动去重 |
01:23:45;1201:23:45:12SMPTE 时间码标准使用冒号作为分隔符;分号通常用于表示丢帧时间码,非丢帧场景下混用会导致解析失败或偏移
输入 25fps 视频,时间码写 00:00:01:30(30fps 的帧号)00:00:01:06(25fps 下第 31 帧)时间码的帧段(最后两位)依赖于视频实际帧率;不同帧率下相同时间码对应不同帧位置,需先确认视频帧率
29.97fps 下写 00:00:01:00 → 00:00:01:0129.97fps 下 00:00:01:00 之后应为 00:00:01:02(跳过 01 帧)NTSC 29.97fps 丢帧时间码每分钟跳过前两帧(除整 10 分钟外)以补偿帧率偏差,直接连续计数会偏移实际时间
24fps 视频输入 00:00:00:3000:00:00:23(24fps 最大帧号 23)帧段值必须小于帧率(0 ~ fps-1);30 ≥ 24 会被工具拒绝或自动截断,导致时间偏移
-00:00:01:00 或 999:99:99:9900:00:00:00 ~ 23:59:59:23(24fps 下最大)时间码通常从 00:00:00:00 开始且不超过 24 小时;负值或超长值在 FFmpeg 中会报错或静默截断
00:00:01.500(毫秒)00:00:01:12(25fps 下 1.5 秒 = 第 37.5 帧,取整为 37 帧即 12)时间码工具期望帧号而非毫秒;毫秒输入会被当作帧号处理,导致时间偏移(如 500 帧远超正常范围)
字幕时间码基于剪辑前素材,叠加到已剪辑正片导出正片后再提取时间码,或使用正片起始时间偏移量剪辑/变速/拼接会改变原始时间码;直接复用剪辑前时间码会导致字幕与画面错位
25fps 视频用 00:00:01:00 定位水印,导出 50fps 时位置偏移用绝对时间(秒)或帧号定位,导出时保持帧率一致帧率变化会改变帧号与时间的对应关系;水印位置若依赖帧号,帧率不同时位置会偏移
公式推导 · 流程图解 · 依据出处
T = H × 3600 + M × 60 + S + F / frame_rate
T — 总时间码(秒)H — 小时数(0-23)M — 分钟数(0-59)S — 秒数(0-59)F — 帧序号(0 到 frame_rate-1)frame_rate — 帧率(如 24, 25, 30, 60)视频帧率为 25 fps,时间码为 01:23:45:12。则 H=1, M=23, S=45, F=12。T = 1×3600 + 23×60 + 45 + 12/25 = 3600 + 1380 + 45 + 0.48 = 5025.48 秒。即从 0 帧开始到该帧的精确时间偏移。
适用于 SMPTE 非丢帧时间码(Non-Drop Frame),帧率固定且为整数(如 24/25/30/60 fps)。不适用于丢帧时间码(Drop Frame,NTSC 29.97 fps)或可变帧率视频,此时需额外补偿。
3 种主流语言 · 复制即用
import subprocess
import json
# 使用 FFmpeg 叠加时间码水印到视频
input_video = "input.mp4"
output_video = "output_timestamp.mp4"
# drawtext 滤镜:显示当前时间戳(格式 HH:MM:SS.ms)
# fontfile 需替换为系统实际字体路径
filter_complex = (
"drawtext=text='%{pts\\:gmtime:0\\:%H\\:%M\\:%S.\\%3N}':"
"fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf:"
"fontsize=24:fontcolor=white:box=1:boxcolor=black@0.5:"
"x=10:y=10"
)
cmd = [
"ffmpeg", "-i", input_video,
"-vf", filter_complex,
"-codec:a", "copy", # 保持原音频不重新编码
output_video
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"FFmpeg error: {result.stderr}")
else:
print(f"Generated: {output_video}")
package main
import (
"fmt"
"os/exec"
"strings"
)
func main() {
input := "input.mp4"
output := "output_frame_number.mp4"
// 使用 FFmpeg 叠加帧号(从 0 开始计数)
filter := fmt.Sprintf(
"drawtext=text='Frame: %%{n}':fontsize=28:fontcolor=yellow:box=1:boxcolor=black@0.5:x=w-tw-10:y=10",
)
cmd := exec.Command("ffmpeg",
"-i", input,
"-vf", filter,
"-codec:a", "copy",
output,
)
var stderr strings.Builder
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
fmt.Printf("FFmpeg failed: %v\n%s", err, stderr.String())
return
}
fmt.Printf("Output: %s\n", output)
}
// 浏览器端:使用 Canvas 逐帧叠加时间码(模拟 FFmpeg 行为)
const video = document.createElement('video');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
video.src = 'input.mp4';
video.addEventListener('loadeddata', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 每帧绘制时间戳
function drawTimestamp() {
ctx.drawImage(video, 0, 0);
// 格式化当前时间(秒 → HH:MM:SS.mmm)
const sec = video.currentTime;
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = Math.floor(sec % 60);
const ms = Math.floor((sec % 1) * 1000);
const timeStr = `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}.${String(ms).padStart(3,'0')}`;
ctx.font = '24px monospace';
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.strokeText(timeStr, 10, 40);
ctx.fillText(timeStr, 10, 40);
requestAnimationFrame(drawTimestamp);
}
video.play();
drawTimestamp();
});
8 个高频疑问