Subtitld project

OpenSaveZIPXMLProject

Subtitld's own file: a ZIP package with a USF document of the subtitles, their translations, speakers and dubs at its head, a manifest of everything else inside, a picture of where you left off, and, when you ask, the video and its sound. One file to move, back up or hand over, and every part of it readable with standard tools.

At a glance

Extension.usfx
Media typeapplication/x-usfx, a kind of application/zip. On Apple systems, org.subtitld.usfx.
SpecificationThis page. The package is PKWARE's ZIP (APPNOTE.TXT); the document is USF 1.0 with Subtitld's elements.
PackageZIP, its members compressed with Deflate (RFC 1951) and checked by CRC-32; Zip64 for files past 2 GiB
First membersubtitles.usf: its name is at byte 30 of every project Subtitld saves
Time precisionSubtitles: milliseconds. Dubs: the exact seconds, in as many decimals as they take.
Text encodingUTF-8, in the XML and in member names
StylingUSF's: italic, bold, underline, strikeout, colour, and nine places
SpeakersA name, a colour, a picture and a voice each
LanguagesThe original and each translation, a block each, by ISO 639-2 code and name
Media insideDubs and speakers' pictures; the video, its sound, its separated voices and background, and the timeline's waveform when asked

Anatomy of a file

A .usfx is a ZIP. Any archive tool opens it, renamed .zip if the tool insists.

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 · Document

    The project itself, in USF: every subtitle, translation, speaker and dub. Always present, always first.

  2. 2 · Manifest

    What made the file, where the video was, how the sound was mixed, and a media type for every other member.

  3. 3 · Picture

    What file managers show of the project: the frame where you left off, its subtitle, and the timeline there.

  4. 4 · Assets

    The audio of each dub and the speakers' faces; the video, its sound and the waveform only when you ask.

The document says what happens; the assets are what it plays. A dub in subtitles.usf names its clip by its path in the package, assets/dubs/77a03e27.wav, so the package can move to another folder, disk or computer and still be whole.

The members

Path What it holds Compression Written
subtitles.usf The document: subtitles, translations, speakers, voices, dubs Deflate Always, first
manifest.xml The manifest Deflate Always, second
thumbnail.png A 512 × 384 PNG card of the project Stored When there is a picture to make
assets/speakers/<speaker>.png A speaker's picture, at most 256 pixels a side Deflate Unless you turn it off
assets/dubs/<uid>.<ext> The audio of a dub take, named by the take's ID Deflate Always, for every take with audio
assets/dubs/<uid>__<n>__<file> A split or stretched piece of a take, and the untouched source it was stretched from Deflate When a take was split or stretched
assets/video/<file> The video Deflate When you ask
assets/waveform.npy The timeline's waveform peaks, a NumPy array Deflate When you ask
assets/audio/original.flac The video's sound Deflate When you ask
assets/audio/vocals.flac, background.flac The sound separated into voices and the rest Deflate When you ask

Names in assets/ are made of letters, digits, -, _ and .; anything else becomes _, so Tears of Steel.mkv is stored as Tears_of_Steel.mkv. Letters outside ASCII are kept, the name written in UTF-8 and flagged as such (bit 11 of the ZIP header), as APPNOTE asks.

Why the picture is stored, not compressed. A PNG is compressed already; Deflate would only cost time. Stored, its bytes are in the file exactly as a PNG, so a thumbnailer reads them out without inflating anything.

The first bytes

Every project starts the same way: a ZIP local file header, then the name of its first member.

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 · Signature

    PK\3\4, bytes 0 to 3: a ZIP member starts here.

  2. 2 · Header

    Version 2.0 needed to extract; no flags; method 8, Deflate; the time and date, MS-DOS style (17 October 2026, 14:02:28 here); the CRC-32; the compressed and uncompressed sizes (15,215 and 57,983 bytes).

  3. 3 · Name length

    13, and no extra field.

  4. 4 · Name

    subtitles.usf, from byte 30.

The name is never compressed, even when the member is. That makes it a signature: a ZIP whose name at byte 30 is subtitles.usf is a Subtitld project. Haiku's Tracker tells projects from other archives by exactly that rule, 0.60 ("PK\003\004") [30] ("subtitles.usf" | "manifest.xml"), ranked above the plain ZIP rule. ODF and EPUB do the same with a first member named mimetype.

A video can be larger than ZIP's original limit of 4 GiB. From 2 GiB on, Subtitld writes Zip64's extra fields and end records (APPNOTE section 4.5), as it does past 65,535 members; Subtitld, its Windows thumbnail handler and any current unzip read them.

The manifest

<?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>
Element What it holds
<manifest version> The manifest's version, 1.0
<format>, <generator> USFX, and what wrote the file: Subtitld
<source path basename> The video's full path when the project was saved, and its file name alone
<voicemix volume> Where the Music/Voice slider was: 0 the background only, 0.5 the sound as it is, 1 the voices only, the dubs fading out with the background
<entry path type> One for each member but the manifest itself, each with its media type

The types are application/x-usf+xml, image/png, audio/wav, audio/flac, audio/mpeg, video/mp4, video/x-matroska, video/quicktime, video/webm, video/ogg, and application/octet-stream for the waveform and anything else.

The video's path travels with the file. <source path> is where the video was on the computer that saved the project, folders and user name included. Keep that in mind before you share a project. Where the path leads nowhere, Subtitld looks for the video by its name in the project's folder.

The document

subtitles.usf is a Universal Subtitle Format file, UTF-8, as Subtitld writes it on its own, with what a project needs added in USF's own style: elements and attributes where USF has none. A USF reader that skips what it does not know reads its subtitles.

<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>

What Subtitld adds to USF

Where Element or attribute What it holds
<metadata> <subtitldcc:origin share-id version> The subtitld.cc subtitle the project came from, so changes can be published as its next version. In its own namespace, https://subtitld.cc/ns/usf/1, written only when there is one.
<styles> <style name> One for each speaker, named after them. No Default style, which would restyle every subtitle in a player.
<style> <fontstyle color> The speaker's colour, #RRGGBB
<style> <dubbing engine voice rate pitch fit_to_subtitle> The speaker's voice: the engine, the voice, rate and pitch from −100 to 100, and whether dubs are fitted to their subtitle's length
<text> speaker, style Who says it, and their style
<text> alignment One of USF's nine places, TopLeft to BottomRight; none for the bottom centre
<subtitle> <dubbing> A take of the subtitle's dub, one element each
<dubbing> uid engine voice rate pitch The take's ID, and the voice it was made with
<dubbing> path, raw_path Its audio in the package, and the source a stretched take was made from
<dubbing> start, end Where it plays on the timeline, in seconds
<dubbing> <subclip type path raw_path start end offset rate> The pieces of a take that was split or stretched

In a plain .usf, speakers' pictures and dubs' audio can travel inside the XML, as base64 in <avatar> and <data>. A .usfx never does that: they are members of their own, a quarter smaller than in base64, and read without parsing the XML.

Times

A subtitle's start and stop are USF times, rounded to the nearest millisecond. A dub's times are plain seconds written in full, 18.01579117916244: the shortest decimal that reads back as the same 64-bit number, so a clip placed by dragging comes back exactly where it was.

Older Subtitld versions wrote times a millisecond early, and a start at zero as 23:59:59.999. Subtitld reads that value as zero, and leaves the millisecond as it is.

Takes and subclips

A subtitle can have several takes. The first <dubbing> is the one that plays; the others are alternates, silent until you choose one, which then moves to the front.

A take that was split or stretched keeps a list of <subclip>s, placed like clips in an audio editor:

Attribute Meaning
start, end The part of the source file that plays, in seconds into the file
offset Where that part starts, in seconds from the take's start
rate The speed it was stretched to: 20 is 120 %
path, raw_path The audio that plays, and the untouched audio it was stretched from

A subclip plays from the take's start plus its offset, for end minus start seconds. Gaps are silence; overlaps are mixed. The sources are never changed, so every split and stretch can be undone, and a stretch is always remade from raw_path, never from a stretch of a stretch.

Translations

Each language has its own <subtitles> block, named by its ISO 639-2 code and, in the element, the language's name, which tells Brazil's Portuguese from Portugal's. Every block holds every subtitle at the same times; only the first holds dubs. Subtitld matches a translation to its subtitle by start time, or by the one it overlaps most, so blocks written by other tools need not line up.

Opening a project

Big projects open at once: Subtitld reads first what the editor needs, and the rest while you work.

Before the editor shows
  • subtitles.usf, read for the subtitles, speakers and voices
  • manifest.xml, for the video and the mix
  • assets/speakers/, the faces, decoded off the main thread
  • A bundled video, extracted once
While you work
  • The dubs, first, so you can play them
  • The waveform, then
  • The separated sound, last

Each dub on the timeline is hatched until its audio lands, then plays.

Members are extracted into Subtitld's own folders, never beside the project: the document, faces and dubs into a cache folder named after the MD5 of the project's path. What comes in while you work streams in 1 MiB chunks to a temporary file, renamed into place when complete, so a crash halfway leaves nothing half-written. The next time the same project opens, a file already there is used again only if it is exactly the member's size; otherwise it is extracted again.

The video is found in this order:

  1. a video inside the package, in assets/video/;
  2. the path in <source path>, if it is still there;
  3. a file named as in <source basename>, in the project's folder;
  4. otherwise, Subtitld asks for it.

A file that is not a ZIP, is cut short, or fails a CRC check opens nothing: Subtitld says so and takes it off the recent list. Earlier projects open too: one without a manifest, or with its USF under another name at the root of the ZIP.

Saving a project

Save never puts the project you have at risk.

  1. A snapshot. The project is copied as it is when you save, so you can keep editing while it is written. Saves, autosaves and backups take turns: one writes at a time.
  2. A new file. The package is written to <project>.usfx.tmp beside the project.
  3. Nothing missing. A dub whose audio is not in the cache yet, still streaming in or cleared, is copied from the file about to be replaced. A project saved without a video keeps the picture it had.
  4. Checked. The new file is opened again and every member's CRC-32 verified.
  5. Swapped. Only then does it replace the project, in one rename (os.replace): the project's name never holds half a project.

If any step fails, the temporary file is deleted, the project on disk is the one you had, and your edits are still open in Subtitld. While a project's dubs are still streaming in, Save waits for them.

What goes inside

Global settings, then USFX, chooses what else a project carries, for every project you save:

  • Speaker portrait imagesThe speakers' faces. On.
  • Timeline waveform cacheThe waveform, so a project opens on another computer without computing it again. Off.
  • Original audio trackThe video's sound, as FLAC. Off.
  • Processed audio (vocals + background)The sound separated into voices and the rest, as FLAC, so separation need not run again. Off.
  • Original video fileThe video itself: one file with everything, as big as the video and more. Off.

Each goes in when Subtitld has one to put in: the separated sound, for one, only once separation has run. The subtitles, translations, speakers, voices and every dub are always inside. Deflate takes about a sixth off a dub's WAV; a video and FLAC hardly shrink.

Autosave and backups

  • Autosave the project fileWrites your .usfx itself, as Save does, the same safe way. For a project saved as USFX.
  • Automatic backupsA complete .usfx in Subtitld's backup folder, apart from your own file, named <project>_<YYYYMMDDHHMMSS>.usfx, when there are changes to keep. Past Keep at most, 20 unless you choose, the oldest are deleted.

In file managers

The thumbnail is a card in Subtitld's look: the video's frame where the project was left, the subtitle on it, and under it the twelve seconds of timeline around the playhead, the waveform and the subtitles in their speakers' colours. 4:3 at 512 × 384, so it fills a file manager's square cell better than a wide frame. Without a video, Subtitld's mark stands in for the frame.

System The type What it gives
Linux application/x-usfx in shared-mime-info, a kind of application/zip, matched by *.usfx An icon of its own; with the AUR package, thumbnails from subtitld-thumbnailer, a freedesktop.org thumbnailer
Windows A file type in the installer and the MSIX package An icon; a shell thumbnail provider (UsfxThumbnail.dll, IThumbnailProvider) that reads the ZIP's central directory itself, Zip64 included, and decodes the PNG with WIC at the size Explorer asks
macOS The type org.subtitld.usfx, conforming to public.zip-archive An icon of its own
Haiku application/x-usfx, with the sniffer rule above An icon, and Tracker telling a project from a ZIP

Double-clicked, a project opens in Subtitld on all four. Both thumbnailers read only thumbnail.png, refuse one over 8 MiB, and take nothing that does not start with PNG's signature, 89 50 4E 47 0D 0A 1A 0A. The Windows one takes it only stored, as Subtitld writes it. The Linux one uses Python's standard library alone, as file managers run thumbnailers sandboxed and many at once.

Reading a .usfx yourself

Nothing in a project is Subtitld's alone. To look inside:

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

To read its subtitles, in every language, with Python's standard library:

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 null:
        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))

For a project from someone else, parse with defusedxml in place of xml.etree, as with any XML you did not write.

Making one

The smallest project Subtitld opens is a ZIP holding a subtitles.usf. With no manifest, Subtitld asks for the video. To make one that file managers know too, follow what Subtitld writes:

  • subtitles.usf first, then manifest.xml;
  • every member compressed with Deflate, but thumbnail.png, stored;
  • dubs under assets/dubs/, named in the document by their path in the package;
  • UTF-8 everywhere.

Subtitld keeps what it knows. A save writes the members on this page, and a document made from what Subtitld understood of the old one. A file another tool adds to the package, or an element of its own in the USF, is gone once Subtitld saves the project. Keep your own data beside it.

How it compares

USFX is built as the document packages of office suites and e-books are: a ZIP, a main document in XML, an inventory, the media beside them.

ODF (.odt) EPUB Office Open XML (.docx) USFX
Standard OASIS ODF 1.3, Part 2 W3C EPUB 3.3, its OCF ECMA-376 Part 2, OPC This page
How a tool knows it A first member mimetype, stored A first member mimetype, stored: application/epub+zip [Content_Types].xml Its first member's name, subtitles.usf, at byte 30
Inventory META-INF/manifest.xml, a type for each file META-INF/container.xml, then the package document [Content_Types].xml and _rels/ manifest.xml, a type for each member
Main document content.xml The package document and its spine word/document.xml subtitles.usf
Media Pictures/ Anywhere in the package word/media/ assets/
Preview Thumbnails/thumbnail.png A cover image An optional thumbnail part thumbnail.png, stored

The main document is an open format in its own right: take a .usfx's subtitles.usf out and it is a USF file, a format with a public specification since 2002.

Trivia

30

The byte where every project's first member name starts, and how Haiku tells a Subtitld project from any other ZIP.

512 × 384

The size of a project's picture: 4:3, because file managers show files in squares.

26.10

The release that gave .usfx an icon of its own on Linux, Windows, macOS and Haiku, and its picture in Windows Explorer and Linux file managers.

Related formats

Start a project

Open a video in Subtitld, make its subtitles, and save: it is a .usfx.

Download Subtitld