示例成品 · 平台演示,按左边这组点选真跑出来的
缺两个文件我没法直接给出偏移表:你的 **Premiere XML** 和 **DaVinci CSV** 现在不在对话里。
下面给你一个可直接跑通的脚本,拿来文件后一条命令出表。
## 运行方式
```bash
pip install openpyxl
python align_tc.py timeline.xml color.csv --fps 23.976 --out aligned_tc.xlsx
```
如果 CSV 列名不标准,可指定列名:
```bash
python align_tc.py timeline.xml color.csv --id-col "Shot ID" --tc-col "Color Timecode"
```
## 输出规则
- 23.976 按非丢帧处理,时间码按 **24 名义时基** 计算,不会出现小数帧
- XML 源时间码取:`文件起始时间码 + 入点`
- 偏移帧数:`调色时间码 - XML源时间码`
- `0` = 对齐
- `正数` = 调色时间码比 XML 晚
- `负数` = 调色时间码比 XML 早
- 偏移 ≠ 0、缺失、重复 ID,整行标红高亮
- 默认按绝对偏移从大到小排序,最离谱的排最上面
## 脚本 `align_tc.py`
```python
import argparse
import csv
import re
import sys
from xml.etree import ElementTree as ET
try:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
except ImportError:
print("缺少 openpyxl,请先执行:pip install openpyxl")
sys.exit(1)
def normalize_id(s):
if not s:
return ""
s = str(s).strip()
s = re.sub(r"\s+", "", s)
return s.lower()
def normalize_col(s):
if not s:
return ""
s = str(s).strip()
s = re.sub(r"[^0-9a-zA-Z\u4e00-\u9fff]", "", s)
return s.lower()
def get_timebase(fps):
fps = float(fps)
if abs(fps - 23.976) < 0.01 or abs(fps - 24.0) < 0.01:
return 24.0
if abs(fps - 25.0) < 0.01:
return 25.0
if abs(fps - 29.97) < 0.01:
return 30.0
return round(fps)
def tc_to_frames(tc, tb=24.0):
if tc is None:
return None
tc = str(tc).strip()
parts = re.split(r"[:;]", tc)
if len(parts) == 4:
hh, mm, ss, ff = map(int, parts)
elif len(parts) == 3:
hh, mm, ss = map(int, parts)
ff = 0
else:
raise ValueError(f"无法解析时间码:{tc}")
tb_int = int(round(tb))
return (hh * 3600 + mm * 60 + ss) * tb_int + ff
def frames_to_tc(frames, tb=24.0):
if frames is None:
return ""
neg = frames < 0
frames = abs(int(frames))
tb_int = int(round(tb))
ff = frames % tb_int
total_seconds = frames // tb_int
ss = total_seconds % 60
mm = (total_seconds // 60) % 60
hh = total_seconds // 3600
tc = f"{hh:02d}:{mm:02d}:{ss:02d}:{ff:02d}"
return "-" + tc if neg else tc
def parse_premiere_xml(path, tb):
tree = ET.parse(path)
root = tree.getroot()
records = []
for clip in root.iter("clipitem"):
clip_id = clip.get("id", "").strip()
name = ""
name_el = clip.find("name")
if name_el is not None and name_el.text:
name = name_el.text.strip()
file_el = clip.find("file")
if file_el is None:
file_el = clip.find(".//file")
file_name = ""
if file_el is not None:
fn_el = file_el.find("name")
if fn_el is not None and fn_el.text:
file_name = fn_el.text.strip()
file_tc_str = ""
file_tc_frames = None
if file_el is not None:
tc_str_el = file_el.find("timecode/string")
if tc_str_el is not None and tc_str_el.text:
file_tc_str = tc_str_el.text.strip()
file_tc_frames = tc_to_frames(file_tc_str, tb)
else:
tc_frame_el = file_el.find("timecode/frame")
if tc_frame_el is not None and tc_frame_el.text:
file_tc_frames = int(tc_frame_el.text.strip())
file_tc_str = frames_to_tc(file_tc_frames, tb)
in_frames = 0
in_el = clip.find("in")
if in_el is not None and in_
点左边「开工 · 直接出成品」,出一份你自己的版本(文字免费)