Subtitld 项目

打开保存ZIPXML项目

Subtitld 自己的文件:一个 ZIP 包,开头是包含字幕、译文、说话人和配音的 USF 文档,随后是列出包内其他所有内容的清单、一张你上次停下位置的图片,以及(如果你需要)视频和它的声音。一个文件即可移动、备份或交给他人,而且每一部分都能用标准工具读取。

概览

扩展名.usfx
媒体类型application/x-usfx,属于 application/zip 的一种。在 Apple 系统上为 org.subtitld.usfx。
规范本页。包采用 PKWARE 的 ZIP(APPNOTE.TXT);文档为带有 Subtitld 元素的 USF 1.0。
包ZIP,成员用 Deflate(RFC 1951)压缩并以 CRC-32 校验;超过 2 GiB 的文件使用 Zip64
第一个成员subtitles.usf:在 Subtitld 保存的每个项目中,它的名称都位于第 30 字节
时间精度字幕:毫秒。配音:精确秒数,需要多少位小数就写多少位。
文本编码UTF-8,XML 和成员名称中都是
样式沿用 USF:斜体、粗体、下划线、删除线、颜色和九个位置
说话人每人各有名字、颜色、图片和声音
语言原文和每种译文各一个块,使用 ISO 639-2 代码和名称
内含媒体配音和说话人图片;按需包含视频、其声音、分离出的人声与背景,以及时间轴波形

文件结构

.usfx 就是一个 ZIP。任何压缩工具都能打开它;如果工具坚持,把扩展名改成 .zip 即可。

Tears of Steel.usfx
subtitles.usf
manifest.xml
thumbnail.png
assets/speakers/Celia.png
assets/dubs/77a03e27.wav
assets/video/Tears_of_Steel.mkv
assets/waveform.npy
assets/audio/vocals.flac
  1. 1 · 文档

    项目本身,以 USF 表示:每条字幕、译文、说话人和配音。始终存在,始终排在第一。

  2. 2 · 清单

    文件由什么生成、视频在哪里、声音如何混合,以及其他每个成员的媒体类型。

  3. 3 · 图片

    文件管理器显示的项目画面:你停下时的那一帧、它的字幕,以及那里的时间轴。

  4. 4 · 资源

    每段配音的音频和说话人的头像;视频、其声音和波形仅在你需要时加入。

文档说明发生什么;资源是它要播放的内容。subtitles.usf 中的配音用包内路径 assets/dubs/77a03e27.wav 指向自己的片段,因此整个包可以移到另一个文件夹、磁盘或电脑上,依然完整。

成员

路径 内容 压缩 何时写入
subtitles.usf 文档:字幕、译文、说话人、声音、配音 Deflate 始终,第一个
manifest.xml 清单 Deflate 始终,第二个
thumbnail.png 项目的 512 × 384 PNG 卡片 Stored 有图可生成时
assets/speakers/<speaker>.png 说话人的图片,每边最多 256 像素 Deflate 除非你关闭
assets/dubs/<uid>.<ext> 一条配音录音的音频,以录音的 ID 命名 Deflate 始终,每条有音频的录音
assets/dubs/<uid>__<n>__<file> 录音被分割或拉伸后的片段,以及拉伸所依据的未改动源文件 Deflate 录音被分割或拉伸时
assets/video/<file> 视频 Deflate 你需要时
assets/waveform.npy 时间轴的波形峰值,一个 NumPy 数组 Deflate 你需要时
assets/audio/original.flac 视频的声音 Deflate 你需要时
assets/audio/vocals.flac、background.flac 分离成人声和其余部分的声音 Deflate 你需要时

assets/ 中的名称由字母、数字、-、_ 和 . 组成;其他字符都变成 _,所以 Tears of Steel.mkv 存为 Tears_of_Steel.mkv。ASCII 以外的字母会保留,名称按 APPNOTE 的要求以 UTF-8 写入并加上相应标记(ZIP 头的第 11 位)。

为什么图片不压缩存放。 PNG 本身已经压缩过;再用 Deflate 只会浪费时间。不压缩存放时,它的字节在文件中就是一个完整的 PNG,缩略图生成器无需解压即可读出。

开头的字节

每个项目的开头都一样:一个 ZIP 本地文件头,然后是第一个成员的名称。

50 4B 03 04
14 00 00 00 08 00
4E 70 51 5D
64 CE 1E 0B
6F 3B 00 00 7F E2 00 00
0D 00 00 00
73 75 62 74 69 74 6C 65 73 2E 75 73 66
  1. 1 · 签名

    PK\3\4,第 0 到 3 字节:一个 ZIP 成员从这里开始。

  2. 2 · 文件头

    解压所需版本 2.0;无标志;方法 8,即 Deflate;MS-DOS 格式的时间和日期(此处为 2026 年 10 月 17 日 14:02:28);CRC-32;压缩后和未压缩的大小(15,215 和 57,983 字节)。

  3. 3 · 名称长度

    13,没有扩展字段。

  4. 4 · 名称

    subtitles.usf,从第 30 字节开始。

即使成员被压缩,名称也从不压缩。这让它成为一种签名:第 30 字节处名称为 subtitles.usf 的 ZIP 就是 Subtitld 项目。Haiku 的 Tracker 正是用这条规则 0.60 ("PK\003\004") [30] ("subtitles.usf" | "manifest.xml") 把项目与其他压缩包区分开,其优先级高于普通 ZIP 规则。ODF 和 EPUB 也用名为 mimetype 的第一个成员做同样的事。

视频可能超过 ZIP 最初的 4 GiB 上限。从 2 GiB 起,Subtitld 会写入 Zip64 的扩展字段和结尾记录(APPNOTE 第 4.5 节),成员超过 65,535 个时也是如此;Subtitld、它的 Windows 缩略图处理程序以及任何现代 unzip 都能读取。

清单

<?xml version="1.0" encoding="UTF-8"?>
<manifest version="1.0">
  <format>USFX</format>
  <generator>Subtitld</generator>
  <source path="/home/ana/Videos/Tears of Steel.mkv"
          basename="Tears of Steel.mkv"/>
  <voicemix volume="0.5000"/>
  <entry path="subtitles.usf" type="application/x-usf+xml"/>
  <entry path="thumbnail.png" type="image/png"/>
  <entry path="assets/speakers/Celia.png" type="image/png"/>
  <entry path="assets/dubs/77a03e27.wav" type="audio/wav"/>
</manifest>
元素 内容
<manifest version> 清单的版本,1.0
<format>、<generator> USFX,以及写入文件的程序:Subtitld
<source path basename> 保存项目时视频的完整路径,以及单独的文件名
<voicemix volume> 音乐/人声滑块的位置:0 只有背景,0.5 保持原声,1 只有人声,配音随背景一起淡出
<entry path type> 除清单本身外每个成员一项,各带媒体类型

类型有 application/x-usf+xml、image/png、audio/wav、audio/flac、audio/mpeg、video/mp4、video/x-matroska、video/quicktime、video/webm、video/ogg,波形和其他一切则为 application/octet-stream。

视频路径会随文件一起传出。 <source path> 是视频在保存项目的那台电脑上的位置,包括文件夹和用户名。分享项目前请记住这一点。如果路径无效,Subtitld 会在项目所在文件夹中按名称查找视频。

文档

subtitles.usf 是一个 Universal Subtitle Format 文件,UTF-8 编码,与 Subtitld 单独写出的 USF 相同,只是按 USF 自己的风格加上了项目所需的内容:在 USF 没有对应项的地方加入元素和属性。跳过未知内容的 USF 读取器照样能读出其中的字幕。

<USFSubtitles version="1.0"
              xmlns:subtitldcc="https://subtitld.cc/ns/usf/1">
  <metadata>
    <title>Tears of Steel</title>
    <author><name>Subtitld</name></author>
    <language code="eng">English</language>
    <subtitldcc:origin share-id="Xk3pQ9aZ2bT" version="3"/>
  </metadata>
  <styles>
    <style name="Celia">
      <fontstyle color="#e5484d"/>
      <dubbing engine="edge-tts" voice="en-GB-SoniaNeural"
               rate="0" pitch="0" fit_to_subtitle="true"/>
    </style>
  </styles>
  <subtitles>
    <language code="eng">English</language>
    <subtitle start="00:00:23.000" stop="00:00:24.500">
      <text speaker="Celia" style="Celia">You're a jerk, Thom.</text>
      <dubbing uid="77a03e27" engine="edge-tts"
               voice="en-GB-SoniaNeural" rate="0" pitch="0"
               path="assets/dubs/77a03e27.wav"
               start="23.0" end="24.5"/>
    </subtitle>
  </subtitles>
  <subtitles>
    <language code="por">Portuguese (Brazil)</language>
    <subtitle start="00:00:23.000" stop="00:00:24.500">
      <text speaker="Celia" style="Celia">Você é um idiota, Thom.</text>
    </subtitle>
  </subtitles>
</USFSubtitles>

Subtitld 为 USF 添加的内容

位置 元素或属性 内容
<metadata> <subtitldcc:origin share-id version> 项目来源的 subtitld.cc 字幕,以便把改动发布为它的下一个版本。位于自己的命名空间 https://subtitld.cc/ns/usf/1 中,仅在存在时写入。
<styles> <style name> 每个说话人一个,以其名字命名。没有 Default 样式,因为它会在播放器中改变每条字幕的样式。
<style> <fontstyle color> 说话人的颜色,#RRGGBB
<style> <dubbing engine voice rate pitch fit_to_subtitle> 说话人的声音:引擎、声音、−100 到 100 的语速和音调,以及配音是否适配字幕时长
<text> speaker、style 谁说的,以及其样式
<text> alignment USF 的九个位置之一,从 TopLeft 到 BottomRight;底部居中则不写
<subtitle> <dubbing> 字幕配音的一条录音,每条一个元素
<dubbing> uid engine voice rate pitch 录音的 ID,以及制作它所用的声音
<dubbing> path、raw_path 它在包中的音频,以及拉伸录音所依据的源文件
<dubbing> start、end 它在时间轴上的播放位置,以秒为单位
<dubbing> <subclip type path raw_path start end offset rate> 被分割或拉伸的录音的各个片段

在普通的 .usf 中,说话人图片和配音音频可以以 base64 形式放在 XML 里的 <avatar> 和 <data> 中。.usfx 从不这样做:它们是独立的成员,比 base64 小四分之一,而且无需解析 XML 即可读取。

时间

字幕的 start 和 stop 是 USF 时间,四舍五入到最接近的毫秒。配音的时间是完整写出的纯秒数,如 18.01579117916244:能读回为同一个 64 位数的最短小数,因此拖动放置的片段会精确回到原来的位置。

旧版 Subtitld 写入的时间早一毫秒,并把从零开始的时间写成 23:59:59.999。Subtitld 会把该值读作零,其余那一毫秒保持不变。

录音与子片段

一条字幕可以有多条录音。第一个 <dubbing> 是播放的那条;其他是备选,在你选中之前保持静音,选中后会移到最前面。

被分割或拉伸的录音保存一个 <subclip> 列表,像音频编辑器中的片段一样排布:

属性 含义
start、end 源文件中播放的部分,以从文件开头算起的秒数表示
offset 该部分的起点,以从录音的 start 算起的秒数表示
rate 拉伸到的速度:20 表示 120 %
path、raw_path 播放的音频,以及拉伸所依据的未改动音频

子片段从录音的 start 加上它的 offset 处开始播放,持续 end 减 start 秒。空隙是静音;重叠部分会混音。源文件从不修改,因此每次分割和拉伸都可以撤销,拉伸也总是从 raw_path 重新生成,绝不在拉伸结果上再拉伸。

译文

每种语言都有自己的 <subtitles> 块,用 ISO 639-2 代码命名,元素中还写有语言名称,以此区分巴西葡萄牙语和葡萄牙的葡萄牙语。每个块在相同时间包含每条字幕;只有第一个块包含配音。Subtitld 按开始时间,或按重叠最多的字幕,把译文与字幕对应起来,因此其他工具写出的块不必严格对齐。

打开项目

大型项目也能立即打开:Subtitld 先读取编辑器需要的内容,其余部分在你工作时读取。

编辑器出现之前
  • subtitles.usf,读取字幕、说话人和声音
  • manifest.xml,读取视频和混音
  • assets/speakers/,头像,在主线程之外解码
  • 包内的视频,只解压一次
你工作时
  • 首先是配音,以便你播放
  • 然后是波形
  • 最后是分离的声音

时间轴上的每段配音在音频到达前显示为斜线填充,到达后即可播放。

成员会解压到 Subtitld 自己的文件夹中,从不放在项目旁边:文档、头像和配音放入一个以项目路径的 MD5 命名的缓存文件夹。工作期间到达的内容以 1 MiB 的块流式写入临时文件,完成后再重命名到位,因此中途崩溃不会留下写了一半的文件。下次打开同一项目时,已有文件只有在大小与成员完全一致时才会被重用;否则重新解压。

视频按以下顺序查找:

  1. 包内 assets/video/ 中的视频;
  2. <source path> 中的路径,如果文件仍在;
  3. 项目文件夹中名称与 <source basename> 相同的文件;
  4. 否则,Subtitld 会请你指定。

不是 ZIP、被截断或未通过 CRC 校验的文件不会打开任何内容:Subtitld 会提示原因,并把它从最近列表中移除。早期的项目也能打开:没有清单的,或 USF 以其他名称放在 ZIP 根目录的。

保存项目

保存绝不会让你现有的项目面临风险。

  1. 快照。 项目按你保存那一刻的状态复制,因此写入期间你可以继续编辑。保存、自动保存和备份轮流进行:同一时间只有一个在写入。
  2. 新文件。 包被写入项目旁边的 <project>.usfx.tmp。
  3. 不缺任何内容。 音频尚未进入缓存(仍在传入或已被清除)的配音,会从即将被替换的文件中复制。没有视频时保存的项目保留原有的图片。
  4. 校验。 重新打开新文件,并验证每个成员的 CRC-32。
  5. 替换。 只有这时它才通过一次重命名(os.replace)替换项目:项目的名称下永远不会是半个项目。

任何一步失败,临时文件都会被删除,磁盘上仍是你原来的项目,你的编辑也仍在 Subtitld 中打开着。项目的配音仍在传入时,保存会等待它们。

包含哪些内容

全局设置中的 USFX 选择项目还要携带哪些内容,适用于你保存的每个项目:

  • 说话人头像说话人的脸。开启。
  • 时间轴波形缓存波形,这样项目在另一台电脑上打开时无需重新计算。关闭。
  • 原始音频轨视频的声音,FLAC 格式。关闭。
  • 处理后的音频(人声 + 背景)分离成人声和其余部分的声音,FLAC 格式,这样无需再次运行分离。关闭。
  • 原始视频文件视频本身:一个包含一切的文件,和视频一样大,甚至更大。关闭。

每一项都在 Subtitld 有内容可放时才加入:比如分离的声音,只有运行过分离之后才有。字幕、译文、说话人、声音和每段配音始终包含在内。Deflate 能把配音的 WAV 缩小约六分之一;视频和 FLAC 几乎不会变小。

自动保存与备份

  • 自动保存项目文件像保存一样,以同样安全的方式写入你的 .usfx 本身。适用于以 USFX 保存的项目。
  • 自动备份有需要保留的改动时,在 Subtitld 的备份文件夹中(与你自己的文件分开)写入一个完整的 .usfx,命名为 <project>_<YYYYMMDDHHMMSS>.usfx。超过最多保留的数量(默认 20,除非你另行选择)时,最旧的会被删除。

在文件管理器中

缩略图是一张 Subtitld 风格的卡片:项目停留处的视频帧、帧上的字幕,下方是播放头周围十二秒的时间轴,带有波形和按说话人颜色显示的字幕。512 × 384 的 4:3 比例,比宽画幅更能填满文件管理器的方形格子。没有视频时,用 Subtitld 的标志代替画面。

系统 类型 提供的功能
Linux shared-mime-info 中的 application/x-usfx,属于 application/zip,按 *.usfx 匹配 专属图标;安装 AUR 包后,由 freedesktop.org 缩略图生成器 subtitld-thumbnailer 生成缩略图
Windows 安装程序和 MSIX 包中的文件类型 图标;一个外壳缩略图提供程序(UsfxThumbnail.dll,IThumbnailProvider),自行读取 ZIP 的中央目录(包括 Zip64),并用 WIC 按资源管理器要求的尺寸解码 PNG
macOS 类型 org.subtitld.usfx,遵循 public.zip-archive 专属图标
Haiku application/x-usfx,使用上面的识别规则 图标,且 Tracker 能区分项目与 ZIP

在这四个系统上双击项目都会在 Subtitld 中打开。两个缩略图生成器都只读取 thumbnail.png,拒绝超过 8 MiB 的文件,并且不接受任何不以 PNG 签名 89 50 4E 47 0D 0A 1A 0A 开头的内容。Windows 版只接受像 Subtitld 写入的那样不压缩存放的图片。Linux 版只使用 Python 标准库,因为文件管理器会在沙盒中同时运行许多缩略图生成器。

自己读取 .usfx

项目中没有任何东西是 Subtitld 独有的。查看其内容:

unzip -l "Tears of Steel.usfx"
unzip -p "Tears of Steel.usfx" manifest.xml

用 Python 标准库读取所有语言的字幕:

import zipfile
import xml.etree.ElementTree as ET

def text_of(element):
    """A <text>'s words, <br/> as a line break."""
    parts = [element.text or '']
    for child in element:
        parts.append('\n' if child.tag == 'br' else text_of(child))
        parts.append(child.tail or '')
    return ''.join(parts)

with zipfile.ZipFile('Tears of Steel.usfx') as usfx:
    usf = ET.fromstring(usfx.read('subtitles.usf'))

for block in usf.iter('subtitles'):
    language = block.find('language')
    if language is not None:
        print('#', language.get('code'), language.text)
    for subtitle in block.iter('subtitle'):
        text = subtitle.find('text')
        print(subtitle.get('start'), subtitle.get('stop'),
              text.get('speaker'), text_of(text))

对于他人提供的项目,请用 defusedxml 代替 xml.etree 进行解析,处理任何不是你写的 XML 都应如此。

自己制作一个

Subtitld 能打开的最小项目是一个包含 subtitles.usf 的 ZIP。没有清单时,Subtitld 会请你指定视频。要制作一个文件管理器也能识别的项目,请照 Subtitld 的写法:

  • 先 subtitles.usf,再 manifest.xml;
  • 每个成员都用 Deflate 压缩,只有 thumbnail.png 不压缩存放;
  • 配音放在 assets/dubs/ 下,在文档中以包内路径引用;
  • 全部使用 UTF-8。

Subtitld 只保留它认识的内容。 保存时会写入本页列出的成员,以及根据 Subtitld 对旧文档的理解生成的文档。其他工具添加到包中的文件,或它在 USF 中加入的自有元素,在 Subtitld 保存项目后就会消失。请把你自己的数据另行存放。

对比

USFX 的构造与办公套件和电子书的文档包相同:一个 ZIP、一个 XML 主文档、一份清单,以及旁边的媒体。

ODF (.odt) EPUB Office Open XML (.docx) USFX
标准 OASIS ODF 1.3,第 2 部分 W3C EPUB 3.3,其 OCF ECMA-376 第 2 部分,OPC 本页
工具如何识别 第一个成员 mimetype,不压缩 第一个成员 mimetype,不压缩:application/epub+zip [Content_Types].xml 第一个成员的名称 subtitles.usf,位于第 30 字节
清单 META-INF/manifest.xml,每个文件一个类型 META-INF/container.xml,然后是包文档 [Content_Types].xml 和 _rels/ manifest.xml,每个成员一个类型
主文档 content.xml 包文档及其 spine word/document.xml subtitles.usf
媒体 Pictures/ 包内任意位置 word/media/ assets/
预览 Thumbnails/thumbnail.png 封面图片 可选的缩略图部件 thumbnail.png,不压缩

主文档本身就是一种开放格式:把 .usfx 中的 subtitles.usf 取出来,它就是一个 USF 文件,而 USF 自 2002 年起就有公开规范。

小知识

30

每个项目第一个成员名称开始的字节,Haiku 也凭此把 Subtitld 项目与其他 ZIP 区分开。

512 × 384

项目图片的尺寸:4:3,因为文件管理器以方格显示文件。

26.10

这一版本让 .usfx 在 Linux、Windows、macOS 和 Haiku 上拥有专属图标,并在 Windows 资源管理器和 Linux 文件管理器中显示其图片。

相关格式

开始一个项目

在 Subtitld 中打开视频,制作字幕,然后保存:这就是一个 .usfx。

下载 Subtitld