Python library for reading and writing Annotation PRO .antx files (XML annotation format used by the Annotation PRO application).
ANTX files are XML documents that store speech or audio annotations: layers, segments (with labels and time bounds), and project configuration. They are produced and consumed by Annotation PRO.
Time in ANTX is stored in samples, not in seconds. Each annotation file has a samplerate (e.g. 44100 Hz) in its configuration. Segment start and duration in the file are sample counts. To work in seconds you can use the samplerate from the annotation and the helper functions provided by this library (see below).
- Python 3.9+
From PyPI:
pip install annotationpro-formatFrom source (development):
pip install -e .from annotationpro_format import deserialize_annotation
with open("project.antx", "r", encoding="utf-8") as f:
xml_content = f.read()
annotation = deserialize_annotation(xml_content)
samplerate = int(annotation.configuration["Samplerate"])
for layer in annotation.layers:
print(f"Layer: {layer.name}")
for seg in layer.segments:
print(f" {seg.start} – {seg.duration} (samples) – {seg.label}")Segment start and duration are stored in samples. You can set them directly when you work in sample counts:
from annotationpro_format import Annotation, Layer, Segment, serialize_annotation
annotation = Annotation(samplerate=44100)
layer = Layer("Transcription")
# start=0, duration=66150 means 0 to 1.5 seconds at 44100 Hz
layer.segments.append(Segment("hello", start=0.0, duration=66150.0))
annotation.layers.append(layer)
xml_str = serialize_annotation(annotation)
with open("output.antx", "w", encoding="utf-8") as f:
f.write(xml_str)To avoid manual sample math, use the seconds helpers. They use the annotation’s samplerate to convert between seconds and samples.
Writing segments with start/duration in seconds:
from annotationpro_format import (
Annotation,
Layer,
Segment,
serialize_annotation,
set_segment_start_seconds,
set_segment_duration_seconds,
)
annotation = Annotation(samplerate=44100)
layer = Layer("Transcription")
# Create segment (start/duration in samples will be set by helpers)
seg = Segment("hello", start=0.0, duration=0.0)
set_segment_start_seconds(annotation, seg, 0.0) # 0 seconds
set_segment_duration_seconds(annotation, seg, 1.5) # 1.5 seconds
layer.segments.append(seg)
# Another segment: from 2.5 s to 4.0 s (duration 1.5 s)
seg2 = Segment("world", start=0.0, duration=0.0)
set_segment_start_seconds(annotation, seg2, 2.5)
set_segment_duration_seconds(annotation, seg2, 1.5)
layer.segments.append(seg2)
annotation.layers.append(layer)
with open("output.antx", "w", encoding="utf-8") as f:
f.write(serialize_annotation(annotation))Reading segment times in seconds:
from annotationpro_format import (
deserialize_annotation,
get_segment_start_seconds,
get_segment_duration_seconds,
)
with open("project.antx", "r", encoding="utf-8") as f:
annotation = deserialize_annotation(f.read())
for layer in annotation.layers:
for seg in layer.segments:
start_sec = get_segment_start_seconds(annotation, seg)
duration_sec = get_segment_duration_seconds(annotation, seg)
print(f"{start_sec:.2f}s – {start_sec + duration_sec:.2f}s: {seg.label}")Low-level conversion (seconds ↔ samples):
from annotationpro_format import seconds_to_samples, samples_to_seconds
samplerate = 44100
samples = seconds_to_samples(1.5, samplerate) # 66150.0
seconds = samples_to_seconds(66150, samplerate) # 1.5- Annotation – Root object:
configuration(dict, includesSamplerate),layers(list). - Layer – One annotation layer:
name,id,segments(list-likeSegmentCollection), plus display options (colors, height, etc.). - Segment – One segment:
label,start,duration(in samples), and optional fields (feature,language,group, etc.). - SegmentCollection – List of segments for a layer; setting
id_layeron the collection is handled when you append segments. - AudioFile – Model for audio file references (not used in the current serialization).
Publishing the library to PyPI is fully automated via GitHub Actions.
-
A release is published when a tag matching
vX.Y.Zis pushed to GitHub, e.g.:git tag v0.1.1 git push origin v0.1.1
-
Before creating the tag, bump the version in
pyproject.toml:[project] version = "0.1.1"
-
The tag name (e.g.
v0.1.1) and the version inpyproject.toml(e.g.0.1.1) must match.
When the tag is pushed:
- The workflow in
.github/workflows/publish-pypi.ymlruns. - It checks out the code at that tag.
- It builds the package with
python -m build(sdist + wheel). - It uploads the artifacts in
dist/to PyPI usingtwine upload dist/*and thePYPI_API_TOKENsecret configured in the repository.
If the workflow succeeds, the new version is immediately available on PyPI as annotationpro-format.
MIT – see LICENSE.