Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
PYTHONPATHNoPath to the src directory when running from an uninstalled checkout (e.g., /path/to/aegisub-mcp/src).
AEGISUB_MCP_OUTNoDirectory where tools that write standalone files (text exports, drawings, fonts) put them. Defaults to ./aegisub-mcp-out.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
ass_add_lineA

Append a new line.

start_ms/end_ms accept milliseconds or time strings such as "0:00:01.50"; they are clamped into the document timebase and end < start is an error. Lines are appended at the end of the document and the returned index is its 0-based position. The call is snapshot-backed, so :func:ass_undo reverts it.

Returns {"doc_id", "index", "line": <line dict>}.

ass_add_linesA

Append several lines in one call.

lines is a list of dicts using the :func:ass_add_line keys (start_ms/end_ms/text/style/actor/effect/layer/ comment/margin_l/margin_r/margin_v) plus an optional index — the 0-based position the new line is inserted at. Insertion positions refer to the line list as it is being built (the previous inserts of this same call are already in place); lines without index go to the end. All times accept milliseconds or time strings.

Returns {"doc_id", "indices": [<0-based index per input dict>], "count", "lines": [<line dict>, ...]}.

ass_closeA

Close a document (optionally saving it first).

Args: doc_id: document to close; the current one when omitted. save: save before closing (requires a known path).

Returns {"doc_id", "closed", "saved", "path", "remaining", "current"}. Line indices are 0-based.

ass_delete_linesA

Delete the selected lines.

selection is any selection spelling; None means every line (use with care). Returns {"doc_id", "deleted": [<0-based indices>], "count", "remaining"}. Snapshot-backed.

ass_document_infoA

Detailed information about one document.

Args: doc_id: document to describe; the current one when omitted.

Returns the document summary (see :func:ass_open) extended with section_kinds, section_headers, section_order (aliases of each other, in file order), style_names, font_names, graphic_names, event_count, comment_count, dialogue_count, duration_ms (sum of dialogue durations), span_ms (last end minus first start), start_ms/end_ms of the first/last line, info (Script Info keys), format_order, wrapping, attachments and extradata_count. Line indices are 0-based.

ass_duplicate_linesA

Duplicate the selected lines.

Args: selection: selection spelling; None = all lines. offset_ms: shift the copies in time by this many ms. insert_after: put each copy right after (True) or right before (False) its source.

Copies keep every field of the source (style, actor, effect, layer, margins, comment flag, tags). Returns {"doc_id", "indices": [<0-based index of each copy>], "count", "lines": [...]}. Snapshot-backed. Indices are 0-based.

ass_export_textA

Export selected lines as plain text, TSV, SRT or an ASS fragment.

Args: selection: selection spelling; None = every line. doc_id: document to export; the current one when omitted. format: "txt" (one line of plain text each), "tsv" (start, end, style, actor, effect, kind, plain text), "srt" (SubRip) or "ass-fragment" (the raw Dialogue:/Comment: lines). line_separator: replacement for the ASS in-line hard break \N (and \n) in txt/tsv output. The default keeps the ASS escape for lossless re-import; pass "\n" for one output line per visual line, or any other string ("|") to flatten explicitly. srt always uses real newlines (required by the format) and ass-fragment keeps the original text untouched. output_path: file to write; when omitted the file is written into workspace.output_dir (created if needed).

txt and srt skip comment lines; tsv and ass-fragment keep them. Returns {"doc_id", "path", "format", "media_type", "text", "bytes", "line_count", "selection": [<0-based indices>]} where line_count is the number of exported records (SRT blocks, TSV rows, text lines, events).

ass_find_replaceA

Find and replace text across the selected lines.

Args: pattern: literal text (regex=False) or a Python regular expression (regex=True, compiled with re.UNICODE; back-references like \1 work in replacement). replacement: replacement string (used literally when regex=False). selection: selection spelling; None = every line. doc_id: document to edit; the current one when omitted. regex: treat pattern as a regular expression. case_sensitive: False makes the match case-insensitive. fields: which fields to touch; defaults to ["text"]. Any of text, start, end, style, actor/name, effect. dry_run: count only, change nothing. limit: stop after this many replacements in total.

Returns {"doc_id", "pattern", "replacement", "fields", "regex", "dry_run", "total": <replacements made>, "changed": [<0-based indices>], "lines": [{"index", "counts": {field: n}, "count", "text"}...], "limit"}. Snapshot-backed unless dry_run. Indices are 0-based.

ass_get_lineB

Full detail for a single line (0-based index).

Returns the ass_list_lines line dict extended with:

  • fields — every field of the line exactly as stored.

  • tags_summary — override-tag counts/structure.

  • drawing{"active", "state", "segments", "drawing_segments", "prefix", "path", "suffix"}.

  • karaoke{"has_karaoke", "kinds", "total_ms", "syllables"} with per-syllable start/end times resolved against the line timing.

  • style/resolved_style — the style name, and the same name only when that style actually exists in the document (else None).

  • timing{"start_ms", "end_ms", "duration_ms", "start", "end", "cps", "characters", "lines"}.

Line indices are 0-based.

ass_get_selectionA

Read back the session selection.

Returns {"doc_id", "selection": [<0-based indices>], "count", "lines": [<line dicts, same shape as ass_list_lines>]} and drops indices that are out of range for the current document. Line indices are 0-based.

ass_import_srtA

Import a SubRip (.srt) file as new ASS lines.

Handles , and . decimal separators, multi-line blocks, CRLF and a UTF-8 BOM; multi-line subtitle bodies become \N hard breaks.

Args: path: the .srt file to read. doc_id: document to append to; the current one when omitted. style: ASS style for the imported lines. offset_ms: shift every imported line in time.

Returns {"doc_id", "path", "count", "indices": [<0-based indices>], "lines": [<dict>], "skipped": <blocks without a timestamp>, "style", "offset_ms"}. Snapshot-backed.

ass_list_documentsB

List the open documents.

Returns {"documents": [{"doc_id", "path", "dirty", "lines", "encoding", "current"}...], "ids": [...], "current", "count"}. Line indices are 0-based.

ass_list_linesA

List lines with paging.

Args: selection: selection spelling (see module docstring); None means all lines. doc_id: document to read; the current one when omitted. offset: number of selected lines to skip (0-based). limit: maximum number of lines to return; None returns the rest. include_tags_summary: add a tags_summary dict (tag counts, drawing flag, karaoke/clip/transform lists) per line. include_plain_text: include the plain_text field (tags stripped).

Returns {"doc_id", "total", "offset", "limit", "returned", "indices", "lines"} where lines are event_dict style dicts: index, kind, start, end, start_ms, end_ms, duration_ms, style, actor, effect, layer, margin_l/r/v, text (raw, tags included), plain_text, comment, drawing, cps. Indices are 0-based.

ass_merge_linesA

Merge the selected lines into the earliest one.

The selected lines are sorted by start (then end) time and their raw texts are joined with separator (default the literal \N) into the earliest-starting line, which keeps its style, actor, effect, layer and margins; its span becomes the union of the merged spans. The other selected lines are removed. Needs at least two lines.

Returns {"doc_id", "index": <0-based index of the merged line *after* the merge>, "removed": [<pre-merge 0-based indices of the lines folded away>], "removed_count", "start_ms", "end_ms", "text", "line": <dict>}. Snapshot-backed.

ass_move_linesA

Move the selected lines to another position in the line order.

target_index is the 0-based slot in the remaining lines (the selection is removed first) where the moved block is inserted; it is clamped to the list length, so a large value appends. Returns {"doc_id", "moved": [old 0-based indices], "target_index", "selection": [new 0-based indices], "count"}. Snapshot-backed.

ass_new_documentA

Create (and register) an empty ASS document.

Args: play_res_x / play_res_y: PlayResX / PlayResY of the new script. doc_id: optional registry id; one is generated when omitted. script_type: "v4.00+" (ASS, default) or "v4.00" (SSA).

Returns the same summary as :func:ass_document_info minus the document statistics: doc_id, path, dirty, script_type, play_res_x, play_res_y, fps, lines, dialogue, comments, styles, sections, encoding, has_bom, newline. Line indices are 0-based.

ass_openA

Open an ASS/SSA file and make it the current document.

Args: path: file to open. A missing file raises ToolError. doc_id: optional registry id for the opened document. encoding: force this encoding instead of the automatic sniff; the file must decode with it or ToolError is raised.

Returns the document summary (doc_id, path, dirty, script_type, play_res_x, play_res_y, fps, lines, dialogue, comments, styles, sections, encoding, has_bom, newline). Line indices are 0-based.

ass_redoB

Redo the last undone change.

Returns {"doc_id", "redone", "undo_depth", "redo_depth", "dirty"}; redone is False when the redo stack is empty. Line indices are 0-based.

ass_saveA

Write a document to disk and report exactly what was written.

Args: doc_id: document to save; the current one when omitted. path: destination; the document's known path when omitted. encoding: encoding override for this write. bom: force the UTF-8/UTF-16 BOM on/off (None keeps the current one). newline: "\n" / "\r\n" / "\r" override. create_backup: copy the previous file to <path>.bak first.

Returns {"doc_id", "path", "bytes_written", "sha256", "changed", "encoding", "has_bom", "newline", "backup"}. changed is true when the bytes written differ from whatever was on the destination beforehand (a brand new destination counts as changed); has_bom is derived from the bytes actually written. Line indices are 0-based.

ass_save_allA

Save every open document that has a known path.

Returns {"saved": [<result of ass_save>, ...], "count", "skipped": [doc_id, ...], "errors": [{"doc_id", "error"}, ...], "current"}. Documents created in memory and never saved are reported in skipped rather than failing the whole call. Line indices are 0-based.

ass_selectA

Update the session selection used by "selection".

Args: spec: any selection spelling accepted by base.resolve_indices"all"/None, "0-4,7", 5, [0, 2], {"style": "Default"}, {"text_contains": "hi"}, ... mode: "set" (replace), "add", "remove" or "toggle".

Returns {"doc_id", "mode", "selection": [<0-based indices>], "count"}. Line indices are 0-based.

ass_select_documentA

Make doc_id the current document (clearing the session selection).

Returns the document summary; see :func:ass_open. Line indices are 0-based.

ass_set_commentA

Convert lines between Dialogue: and Comment:.

Args: selection: selection spelling; None = every line. comment: True converts to Comment:, False back to Dialogue:. drop: delete the selected lines instead of converting them.

Returns {"doc_id", "changed": [<0-based indices>], "count", "comment", "dropped"}. Snapshot-backed. Indices are 0-based.

ass_sort_linesA

Sort the selected lines in place (document.reorder_events).

Args: selection: selection spelling; None = every line. keys: sort keys, applied in order; defaults to ["start", "end"]. One of start, end, duration, style, actor/ name, effect, layer, text, comment. reverse: sort descending.

Only the selected lines move: they are re-shuffled among the slots they already occupy, so unselected lines keep their positions.

Returns {"doc_id", "sorted", "selection": [<0-based indices sorted>], "count", "moved": {old 0-based index: new 0-based index}}.

ass_split_lineA

Split one line in two at a time strictly inside its span.

index is 0-based. at_ms accepts milliseconds or a time string and must satisfy start < at_ms < end, otherwise ToolError. The cut sits at the visible character whose position matches the time fraction (each half keeps at least one visible character). The first half keeps the original line's text up to the cut; the second half is a new line right after it that repeats every override block preceding the cut (so {\i1} prefixes survive) and keeps style, actor, effect, layer and margins.

Returns {"doc_id", "index", "second_index", "at_ms", "first": <dict>, "second": <dict>}. Snapshot-backed.

ass_statsC

Statistics for a document.

Returns {"doc_id", "path", "lines", "dialogue", "comments", "characters", "words", "total_duration_ms", "average_cps", "mean_line_cps", "min_line_cps", "max_line_cps", "slowest": {"index", "cps"} | None, "style_histogram", "style_names", "actor_histogram", "actors", "over_cps_25", "lines_over_25", "empty_lines", "drawing_lines", "karaoke_lines", "duration_ms", "span_ms"}. average_cps is total characters over total dialogue duration; the per-line figures only consider lines with a positive duration. Indices are 0-based.

ass_undoB

Undo the last snapshot-backed change of a document.

Returns {"doc_id", "undone", "undo_depth", "redo_depth", "dirty"}. undone is False when the undo stack is empty. Line indices are 0-based.

ass_undo_historyA

List the undo steps available for a document.

The workspace keeps raw document snapshots and no labels, so the entries are positional: step 1 is the oldest available snapshot, the highest step is what :func:ass_undo would restore next. Each entry carries a summary of the snapshot (line count, first start time, line count difference from the current state) so it is still possible to tell the steps apart.

Returns {"doc_id", "undo_depth", "redo_depth", "entries": [{"step", "label", "lines", "first_start"}...], "redo_entries": [...], "next_undo", "next_redo"}. Line indices are 0-based.

ass_update_lineA

Change individual fields of one line; only the fields you pass change.

index is 0-based. Times accept milliseconds or time strings. comment=True turns the line into a Comment:, False back into a Dialogue:. The call is snapshot-backed (:func:ass_undo reverts it).

Returns {"doc_id", "index", "changed": [field names], "line": <dict>}.

ass_update_linesA

Change the same fields on every selected line.

Args: selection: selection spelling (see module docstring); None = all. doc_id: document to edit; the current one when omitted. start_ms/end_ms/text/style/actor/effect/layer/comment/margin_l/margin_r/ margin_v: applied only to the lines you pass, as in :func:ass_update_line. pad_ms: widen every selected line by this many ms on both sides (start -= pad_ms, end += pad_ms); negative values trim it. clamp_to_avoid_overlap: after the edit, pull each selected line's times back so it no longer overlaps an unselected line (best effort; worst case a 10 ms line is left in the gap).

Returns {"doc_id", "changed": [<0-based indices>], "count", "clamped": [<0-based indices>], "fields": [field names], "lines": [<dict>, ...]}. Snapshot-backed. Line indices are 0-based.

ass_list_stylesA

List every style in the document.

Args: doc_id: document id or None for the current document. include_usage: add used (bool) and usage ({total, dialogue, comment}) to each style, counted over all event lines (comments included). include_bbox: render the sample text :data:BBOX_SAMPLE in each style with libass and add bbox — the ink box in script coordinates ({x, y, x1, y1, width, height}) — plus bbox_error when the measurement could not be produced (for example no ffmpeg/libass).

Returns: {"doc_id", "count", "styles": [ ... ]} where each style dict holds every ASS field exactly as stored (Name, Fontname, Fontsize ... Encoding, colours verbatim), plus index, section (its [V4+ Styles]/[V4 Styles] header) and extra_fields (vendor columns not in the ASS vocabulary).

ass_get_styleA

Inspect one style, including the font it really resolves to.

Args: name: style name (case-insensitive). doc_id: document id or None for the current document. include_glyphs: add a glyphs report (approximate Aegisub "characters not in font" check via fontconfig) for sample_text. sample_text: text used for the glyph report. When omitted it defaults to the plain text of the lines that use the style (capped at 2000 characters).

Returns: {"doc_id", "name", "style", "values", "font", "used", "usage"}style holds every ASS field as stored, values the typed view (numbers/flags, colours verbatim), font the fontconfig resolution: {requested, bold, italic, available, resolved, resolved_style, file, index, substituted, candidates, error}. With include_glyphs adds glyphs ({font, missing, fallbacks, covered, checked, coverage_source}) and glyph_sample_text.

ass_add_styleA

Create a style, or replace an existing one of the same name.

Every value is stored verbatim (/48.0 becomes 48); colour values given as #RRGGBB are converted into the spelling the document already uses for that field. Values are validated before anything is written: font_size must be positive, alignment 1-9, border_style 1 or 3, margins whole numbers and colours a spelling the document can store.

overwrite=True replaces the style: every field is (re)written from the arguments, so fields left at their defaults go back to those defaults rather than keeping the old value — use ass_update_style to change a few fields and leave the rest alone. relative_to is Aegisub's style-manager setting: it is only written when the document's Format line already declares a RelativeTo column, otherwise it is listed in ignored_fields (adding the column would desynchronise the Format line).

Returns: {"doc_id", "name", "created", "style", "fields_set", "ignored_fields"}created is False when overwrite=True replaced an existing style.

ass_update_styleA

Change only the fields that are given, on an existing style.

Field names may be written in any spelling: the snake_case parameters above, the ASS column names (Fontname=, Fontsize=, PrimaryColour=, MarginV= ...) or any case/space variant — all of them resolve to the same canonical field. Passing new_name (or Name=) renames the style and, when update_lines semantics apply, repoints the lines that used it (this always happens, exactly like ass_rename_style).

Args: name: the style to update (case-insensitive). doc_id: document id or None for the current document. aliases: any further field=value pairs, e.g. Fontsize=60.

Returns: {"doc_id", "name", "previous_name", "changed", "style", "lines_updated", "ignored_fields"}. changed maps each field to {"from", "to"} in stored form; ignored_fields lists values the document's Format line cannot hold (for example ScaleX in a [V4 Styles] file).

ass_remove_styleA

Delete a style, refusing to orphan lines that still use it.

Args: name: style to delete. doc_id: document id or None for the current document. reassign_to: when the style is still used, the name of the style the lines should be moved to before the style is deleted. Without it a used style is an error.

Returns: {"doc_id", "name", "removed", "reassigned", "reassign_to"} where reassigned is the number of lines repointed.

ass_rename_styleA

Rename a style and (by default) rewrite the lines that reference it.

Args: old_name: existing style name. new_name: new name; must not collide with another style. doc_id: document id or None for the current document. update_lines: when true, every line whose Style field equals old_name is rewritten to new_name.

Returns: {"doc_id", "old_name", "new_name", "lines_updated", "style"}.

ass_copy_styleB

Copy a style, field for field, under a new name.

Args: source: style to copy. new_name: name for the copy. doc_id: document id or None for the current document. overwrite: replace an existing style of that name instead of failing.

Returns: {"doc_id", "source", "name", "created", "style", "ignored_fields"}.

ass_reorder_stylesB

Reorder the style section.

Args: order: the complete list of style names in their new order (a comma-separated string is also accepted). It must be a permutation of the styles already present — missing, unknown or duplicated names are reported in the error. doc_id: document id or None for the current document.

Returns: {"doc_id", "order", "count"} with the resulting order.

ass_style_usageA

Count how styles and actors are used, and name the unused styles.

Args: doc_id: document id or None for the current document. by: which grouping goes in counts: "style", "actor" or "effect".

Returns: {"doc_id", "by", "counts", "by_style", "by_actor", "unused_styles", "unknown_styles", "lines"}. Each count bucket is {"name", "total", "dialogue", "comment"}; unknown_styles maps a style referenced by lines but absent from the style section to its line count.

ass_style_for_lineA

Effective style values for one line, inline override tags included.

The line's Style field is resolved to a style; on top of it the inline override tags that change font, size, colour, weight, scale, spacing, border, shadow or alignment are applied, so the caller sees what libass would actually use. \r/\rStyle restarts from a (possibly other) style, exactly as the renderer does.

Args: index: 0-based line index in doc.events() order (comments included). None uses the session selection (ass_select), falling back to its first index. doc_id: document id or None for the current document.

Returns: {"doc_id", "index", "style", "style_found", "style_definition", "resolved", "runs", "overrides", "transforms", "warnings", "text", "plain_text"}. resolved is the typed effective state at the first visible character; runs splits the visible text into {start, end, values, sources, style} spans with identical values; overrides lists the field names that inline tags modified; transforms lists the \t(...) tags (animation is reported, not folded into the numbers); colours stay in the spelling the tag/style used.

ass_check_font_substitutionA

Compare every style's requested font with the family fontconfig resolves.

Args: doc_id: document id or None for the current document.

Returns: {"doc_id", "fontconfig", "checked", "substituted", "substitutions", "styles"}. Each entry of styles is {"name", "requested", "bold", "italic", "resolved", "resolved_style", "file", "substituted", "candidates", "available", "error"}; substitutions lists the style names whose request is substituted, which is exactly the set of styles that will not render in the font they ask for.

ass_get_script_infoC

Read [Script Info] in file order.

Returns: {"doc_id", "count", "items", "ordered", "values", "duplicates"}. items is one {"index", "key", "value", "raw", "duplicate"} per key line, in the order they appear in the file (raw is the line exactly as stored, so odd spacing such as Title : x survives); ordered is [[key, value], ...]; values the last-wins map; duplicates lists keys that appear more than once.

ass_set_script_infoA

Set, insert or reposition-free-update a [Script Info] key.

Args: key: the key, e.g. "Title". Matching is case- and space-insensitive, and the spelling already in the file is kept. value: new value (converted with str; must not contain a newline). doc_id: document id or None for the current document. before: when the key does not exist yet, insert it immediately before this existing key instead of appending at the end.

Returns: {"doc_id", "key", "value", "created", "index"} where index is the entry position inside [Script Info].

ass_remove_script_infoB

Remove a [Script Info] key.

Returns {"doc_id", "key", "removed"} (removed is False when the key was not present).

ass_set_play_resC

Set PlayResX/PlayResY.

Returns {"doc_id", "play_res_x", "play_res_y", "previous_x", "previous_y"}.

ass_set_wrap_styleB

Set WrapStyle using Aegisub's numeric codes.

Args: style: 0 smart, 1 end of line, 2 no wrapping, 3 bottom of line only. The names are accepted too. doc_id: document id or None for the current document.

Returns: {"doc_id", "wrap_style", "name", "previous"}.

ass_set_scaled_border_and_shadowC

Set ScaledBorderAndShadow (yes/no).

Returns {"doc_id", "value", "enabled", "previous"}.

ass_set_timing_infoA

Set the timing source keys Aegisub stores in [Script Info].

fps writes FPS, video_file writes Video File and timecodes_file writes Timecodes File. Passing an empty string for a path removes that key. Calling the tool with no arguments at all changes nothing and just reports the three keys as they currently are (changes is then empty), which is the only way to read timing info.

Returns: {"doc_id", "fps", "video_file", "timecodes_file", "changes"} with the resulting values (None when a key is absent) and a changes map of key -> {"from", "to"}.

ass_list_attachmentsA

List [Fonts]/[Graphics] attachments with a magic-byte sniff.

Args: doc_id: document id or None for the current document. kind: "font"/"fonts" or "image"/"graphics" to restrict the listing; None returns both sections.

Returns: {"doc_id", "count", "attachments": [ ... ]}. Each attachment has name, kind (the declared kind), section, key, data_lines, encoded_chars, decoded (is the payload valid base64), size (exact decoded size, or encoded*3/4 when the data is not valid base64 — size_exact says which), sha256, magic (first 8 decoded bytes, hex), sniff (font, image or unknown from the magic bytes), line_indices and error.

ass_add_attachmentA

Attach a file to the document, base64-encoded, like Aegisub's attach menu.

Args: path: file to attach (read from disk; the only filesystem input of this module besides the workspace itself). doc_id: document id or None for the current document. name: name stored in the document; defaults to the file's basename. kind: "font" or "image"; inferred from the magic bytes, falling back to the file extension, when omitted.

Returns: {"doc_id", "name", "kind", "section", "path", "bytes", "base64_lines", "sniff", "replaced"}. An attachment with the same name in the same section is replaced (replaced is True).

ass_extract_attachmentA

Decode an attachment and write it into the workspace output directory.

Args: name: attachment name as stored in the document (case-insensitive). doc_id: document id or None for the current document. output_path: destination; a relative path is resolved inside workspace.output_dir.

Returns: {"doc_id", "name", "kind", "path", "bytes", "sha256", "sniff"}.

ass_remove_attachmentB

Remove an attachment (its name line and every payload line).

Returns {"doc_id", "name", "removed", "lines_removed"}.

ass_list_extradataC

List [Aegisub Extradata] entries.

Returns: {"doc_id", "count", "entries"} where each entry is {"index", "head", "label", "id", "value", "raw", "layout"} (index is the position inside the section, label the record's numeric label, id the stored identifier).

ass_set_extradataA

Set or remove one [Aegisub Extradata] entry (idempotent).

Args: ident: the entry identifier (matched case-insensitively). value: value to store; may be omitted when remove=True. doc_id: document id or None for the current document. remove: delete every entry with this identifier instead of setting it.

Returns: {"doc_id", "id", "value", "removed", "created"}; setting an existing identifier updates it in place (never duplicates it).

ass_validateA

Structural validation of the whole document.

Args: doc_id: document id or None for the current document.

Returns: {"doc_id", "ok", "counts", "issues"}. ok is True when no issue has severity error. Every issue is {"code", "severity", "kind", "index", "message"}; kind says what index refers to:

``script_info``  index into the ``[Script Info]`` entries
``style``        index into ``doc.styles()``
``line``         0-based line index in ``doc.events()`` order
``section``      index into ``doc.sections``
``document``     ``index`` is ``None``

Codes: ``duplicate_style_name`` (error), ``duplicate_script_info_key``
(warning), ``missing_script_type`` (warning), ``missing_style`` (error),
``end_before_start`` (error), ``zero_duration`` (warning),
``invalid_timestamp`` (error), ``comments_only_style`` (warning),
``unknown_section`` (warning), ``unknown_record`` (warning),
``malformed_line`` (warning), ``malformed_raw_line`` (warning) and
``field_count_mismatch`` (error).
ass_align_lines_to_silenceA

Move line starts out of audio silence (ffmpeg silencedetect).

A line whose start falls inside a silent interval is pushed to the end of that interval: shift_only="both" (default) moves the end by the same delta, "start" keeps the end where it is and shortens the line. Lines that start on audio are left alone and are not reported as suggestions.

Args: selection: lines to consider. video: media file; defaults to the workspace video/audio. noise_db: silence threshold in dBFS. min_silence_s: shortest silence to act on. doc_id: document id. dry_run: when True (the default) the plan is returned and nothing is written. shift_only: None/"both" or "start". max_shift_ms: ignore suggestions longer than this.

Returns: The same plan as :func:ass_align_to_silence plus "dry_run" and "applied" so the caller always knows whether the document changed.

ass_align_to_silenceA

Report audio silence intervals and the per-line shifts they suggest.

This tool never modifies the document — it exists so an agent can look before it leaps; :func:ass_align_lines_to_silence applies the same plan when dry_run=False.

Args: selection: lines to consider. video: media file; defaults to the workspace video/audio. noise_db: silence threshold in dBFS (passed to silencedetect). min_silence_s: shortest silence to report. doc_id: document id. shift_only: None/"both" shift start and end together, "start" moves only the start (shortening the line). max_shift_ms: ignore suggestions longer than this.

Returns: {"doc_id", "path", "noise_db", "min_silence_s", "shift_only", "max_shift_ms", "silences": [{"start_ms", "end_ms", "duration_ms"}], "suggestions": [{"index", "start_ms", "end_ms", "new_start_ms", "new_end_ms", "delta_ms", "silence", "reason"}], "count", "applied": False, "note"}

ass_check_overlapsA

List overlapping pairs of lines, ordered by start time.

With layer_strict=True (the default) only lines on the same layer are compared — two lines on different layers are meant to coincide. Comment lines never render, so they are not considered.

Args: selection: lines to compare. doc_id: document id. layer_strict: compare within each layer only. tolerate_ms: ignore overlaps of this length or less.

Returns: {"doc_id", "layer_strict", "tolerate_ms", "count", "pairs": [{"a", "b", "layer", "a_start_ms", "a_end_ms", "b_start_ms", "b_end_ms", "overlap_ms"}]}

ass_cpsB

Characters per second for every selected line plus the totals.

Args: selection: lines to measure; None/[] means every line. doc_id: document id.

Returns: {"doc_id", "count", "lines": [{"index", "kind", "characters", "duration_ms", "cps", "drawing", "start_ms", "end_ms"}], "totals": {"characters", "duration_ms", "average_cps", "max_cps", "worst_index"}}

ass_fix_timingA

Compute timing fixes and apply them only when dry_run is False.

Only end times move. A line shorter than min_duration_ms is extended to it; a line reading faster than max_cps/target_cps is extended so that its characters per second fall back to that figure. With avoid_overlap an extension stops at the next line's start on the same layer, minus keep_gaps_ms; when there is no room the fix is reported as blocked instead of being applied.

Args: selection: lines to fix. doc_id: document id. dry_run: True (default) returns the plan and writes nothing. min_duration_ms: shortest acceptable duration. target_cps: extend faster lines until they read at this rate. max_cps: same, used when target_cps is not given. avoid_overlap: stop extensions at the neighbouring line. keep_gaps_ms: keep this much room before the neighbouring line.

Returns: {"doc_id", "dry_run", "applied", "parameters": {...}, "count", "changes": [{"index", "field", "old_ms", "new_ms", "old", "new", "reason"}], "blocked": [{"index", "reason", "wanted_ms", "limit_ms"}]}

ass_frame_from_msA

Convert a time in milliseconds to a frame number.

Args: ms: milliseconds (int/float) or a time string. fps: frame rate; resolution order is fps argument, workspace video (dict fps or a probed video path), workspace script config, document FPS in [Script Info], then a ToolError explaining what is missing. doc_id: document id (only used as an fps source).

Returns: {"ms", "seconds", "fps", "fps_source", "frame", "frame_exact", "frame_floor", "frame_ceil"}

ass_frame_from_timecodesA

The start time of frame according to the loaded timecodes file.

Honours workspace.timecodes (written by :func:ass_read_timecodes) and falls back to the document/workspace frame rate when no file is loaded; method says which of the two was used. Both coordinates are returned so the pair is unambiguous.

Args: frame: frame number. doc_id: document id, only needed for the fps fallback.

Returns: {"frame", "ms", "seconds", "method", "input", "input_kind", "exact_ms"}

ass_load_keyframesA

Load video keyframes into workspace.keyframes (milliseconds).

Args: path: media file. When omitted the workspace video is probed with render.keyframes. doc_id: document id; only used as an fps source when frames is given. frames: explicit frame numbers to convert (needs an fps). times_ms: explicit keyframe times in milliseconds — used verbatim.

Returns: {"source", "path", "count", "keyframes_ms", "first_ms", "last_ms"}

ass_ms_from_frameB

Convert a frame number to its start time in milliseconds.

See :func:ass_frame_from_ms for the fps resolution order.

Returns: {"frame", "fps", "fps_source", "ms", "seconds"}

ass_ms_from_timecodesB

The frame that contains ms according to the loaded timecodes file.

Mirror of :func:ass_frame_from_timecodes; method reports whether the loaded timecodes or the fallback frame rate was used, and ms is the start time of the resulting frame.

Args: ms: milliseconds (int/float) or a time string. doc_id: document id, only needed for the fps fallback.

Returns: {"ms", "frame", "frame_exact", "frame_floor", "frame_ceil", "frame_start_ms", "method", "input", "input_kind"}

ass_qcA

QA a selection of lines and return a structured issue report.

cps is the number of visible characters — override tags are stripped and line breaks are not counted — divided by the line's duration in seconds. Drawing lines ({\\p1}) carry no readable text, so they are skipped by the text checks and reported once each as an info issue with code drawing_line.

Args: selection: lines to check; None/[] means every line. doc_id: document id. cps_warn: cps at or above which a line is cps_high (warning). cps_max: cps at or above which a line is cps_extreme (error). min_duration_ms: shorter lines are too_short (warning). max_duration_ms: longer lines are too_long (warning). max_chars: more visible characters is too_many_chars (warning). max_lines: more \N-breaks is too_many_lines (warning). check_overlaps: report overlap_same_layer for lines on the same layer (different layers never overlap by design). check_gaps: report gap_tiny for same-layer gaps below 100 ms. check_empty: report empty_text. check_styles: report style_missing for styles the document lacks. check_tags: report unclosed_override_block and unknown_tag.

Returns: {"doc_id", "parameters": {...}, "summary": {"lines_checked", "dialogue", "comments", "drawings", "issues", "errors", "warnings", "infos", "by_code": {...}, "ok", "clean"}, "issues": [{"code", "severity", "index", "message", "details"}]}ok is False when any error was found, clean is True only when there are no issues at all. Issues are sorted by line index then code.

ass_read_timecodesA

Parse an Aegisub timecodes file (v1 or v2) and store it on the workspace.

Args: path: path to the .txt/.timecodes file.

Returns: {"path", "version", "default_fps", "fps_changes", "times_ms", "frame_count", "duration_ms", "segments": [{"start_frame", "end_frame", "start_ms", "end_ms", "fps"}]}. v1 files have default_fps and fps_changes ([{"start_frame", "end_frame", "fps"}]); v2 files have the per-frame times_ms list. The result is also kept on workspace.timecodes so the conversion tools can honour it.

Malformed input raises a ToolError naming the offending 1-based line number.

ass_reading_speedB

Reading speed of a single line.

Args: index: 0-based line index. doc_id: document id.

Returns: {"doc_id", "index", "kind", "start_ms", "end_ms", "duration_ms", "characters", "lines", "drawing", "cps", "plain_text"}cps is None when the line has no duration (and 0.0 when it has no text).

ass_scale_timesA

Scale the timing of the selection around an origin.

new = origin + (old - origin) * factor is applied to both the start and the end of every selected line, so the selection stretches or compresses while the origin stays put.

Args: selection: lines to scale (see :func:ass_shift_times). factor: multiplier, must be > 0. doc_id: document id. origin: None/"first" (start of the earliest selected line), "document" (0), or an explicit millisecond value / time string. round_ms: round the computed times to whole milliseconds before writing.

Returns: {"doc_id", "factor", "origin_ms", "origin", "round_ms", "indices", "count", "changes": [{"index", "start_ms", "end_ms", "new_start_ms", "new_end_ms"}], "span": {"before": {"min_start_ms", "max_end_ms", "span_ms"}, "after": {...}}}

ass_set_durationsA

Pull every selected line into the [min_ms, max_ms] duration window.

Only the edge named by mode moves — "stretch" (default) keeps the start and moves the end, "start" keeps the end and moves the start. A line is never moved past the neighbouring line on the same layer unless allow_overlap is True, and never past keep gaps (there are none by default, so the limit is the neighbour's exact start).

Args: selection: lines to adjust. min_ms: minimum duration; shorter lines are lengthened. max_ms: maximum duration; longer lines are shortened. mode: "stretch"/"end" (move the end) or "start" (move the start). doc_id: document id. dry_run: when True (the default) nothing is written — the response shows exactly what would happen. allow_overlap: allow the new edge to cross the neighbouring line.

Returns: {"doc_id", "dry_run", "applied", "mode", "window": {"min_ms", "max_ms"}, "allow_overlap", "count", "changes": [{"index", "field", "old_ms", "new_ms", "old", "new", "reason"}], "blocked": [{"index", "reason", "wanted_ms", "limit_ms"}]}

ass_set_timesA

Set the start, end and/or duration of one line.

Args: index: 0-based line index. doc_id: document id. start_ms: new start — milliseconds or a time string such as "0:00:01.50". end_ms: new end (milliseconds or time string). Mutually exclusive with duration_ms. duration_ms: new duration; the end becomes start + duration_ms.

At least one of the three must be given. An end before the start is rejected with a ToolError naming both values.

Returns: {"doc_id", "index", "before": {"start_ms", "end_ms", "duration_ms"}, "after": {...}, "changed": ["Start", ...], "applied"}

ass_shift_timesA

Shift the start/end of lines by offset_ms (negative shifts allowed).

Args: selection: which lines to shift — None/"all", an index, a list of indices, "0-4,7" or any selector base.resolve_indices takes. offset_ms: milliseconds to add; negative values move lines earlier. doc_id: document id; defaults to the current document. clamp: when True the shift is limited so that no selected line starts before zero. The same (reduced) offset is applied to every line so the relative timing of the selection is preserved. only_selected: when False every line in the document is shifted and selection is ignored.

Returns: {"doc_id", "requested_offset_ms", "applied_offset_ms", "clamped", "only_selected", "indices", "count", "applied", "changes": [{"index", "start_ms", "end_ms", "new_start_ms", "new_end_ms", "start", "end"}]} where the new_* values are what the document actually holds after the write (ASS stores centiseconds).

ass_snap_to_framesB

Snap line start/end times to whole video frames.

Args: selection: lines to snap. fps: frame rate (see :func:ass_frame_from_ms). mode: "nearest" (default), "floor" or "ceil". doc_id: document id. which: "both" (default), "start" or "end".

Returns: {"doc_id", "fps", "fps_source", "mode", "which", "considered", "count", "changes": [{"index", "field", "old_ms", "new_ms", "old", "new", "frame"}], "applied"}new_ms is what the document now holds, count is the number of fields that actually moved and considered the number that were inspected.

ass_snap_to_keyframesA

Snap line times to keyframes loaded in workspace.keyframes.

Args: selection: lines to snap. which: "both" (default), "start" or "end". mode: "nearest" (default), "previous" or "next". forward_only: only consider keyframes at or after the line's time (never move a time earlier). Contradicts mode="previous". max_distance_ms: skip a snap when it would move the time further than this. doc_id: document id.

Returns: {"doc_id", "mode", "which", "forward_only", "max_distance_ms", "keyframes_loaded", "considered", "count", "applied", "changes": [{"index", "field", "old_ms", "new_ms", "old", "new", "delta_ms", "keyframe_ms"}], "skipped": [{"index", "field", "old_ms", "reason"}]}count/changes only list fields that really moved.

ass_write_timecodesA

Write an Aegisub timecodes file (v1 or v2) into workspace.output_dir.

Args: path: destination. Relative names land inside workspace.output_dir; an absolute path is honoured as given. Defaults to <script name>.timecodes. fps: frame rate. Resolution order: this argument, the default rate of a loaded timecodes file, workspace video/script info, the document's FPS; a ToolError explains what is missing. v2: True (default) writes a v2 file (one timestamp per frame), False writes v1 (default rate plus overrides). doc_id: document id.

Returns: {"path", "version", "fps", "fps_source", "frame_count", "lines", "bytes", "preview": [first lines], "used_timecodes"}

ass_karaoke_auto_timingsA

Propose karaoke timings for one line from its duration (no write by default).

Arguments

index the line to analyse (0-based). mode "syllable" (split on marker), "char", "word" or "regex" (needs pattern). weights "char_class" (default), "char" or "even". start_ms / end_ms span to distribute; both default to the line's own times. apply False (default) only proposes the timings and the text that would be written. True writes the generated \k tags to the line (after workspace.snapshot). apply is a documented extension of the required signature: the default is a pure read. kind tag kind to generate when applying.

Returns

{"doc_id", "index", "mode", "split_mode", "weights", "kind", "applied", "start_ms", "end_ms", "span_ms", "span_cs", "syllables": [{"index", "text", "weight", "duration_cs", "duration_ms"}], "durations_cs", "duration_sum_cs", "line_duration_cs", "discrepancy_cs", "matches_line_duration", "message", "proposed_text", "old_text"}

ass_karaoke_exportA

Export syllable timings for other karaoke tools.

Writes into workspace.output_dir and returns both the path and the content. The file is named <document stem>_karaoke.<ext>.

Arguments

selection lines to export. None (default) exports every line that carries karaoke tags; pass a selection to override. format "srv2" (default) tab-separated interchange with a # header and columns line, syl, start_ms, end_ms, dur_cs, dur_ms, kind, text; "txt" human-readable <line_index> <h:mm:ss.cc> <h:mm:ss.cc> <text> rows; "csv" the same columns as srv2 as RFC 4180 CSV. include_untimed also export lines without karaoke tags as a single syllable covering the line span.

Returns

{"doc_id", "format", "path", "filename", "content", "lines": n, "syllables": n, "bytes": n, "output_dir": "..."}

ass_karaoke_generateA

Generate \k tags across a line's syllables.

Arguments

selection / index Which lines to rewrite. index names one line; otherwise selection goes through :func:base.resolve_indices (None uses the session selection, and an error is raised when nothing is selected). start_ms / end_ms Span the durations are distributed over. When omitted the line's own Start/End are used. The line's own times are never modified by this tool (use :func:ass_karaoke_retime for that). mode marker / char / word / regex (pattern for regex). kind karaoke tag kind: k, kf, ko or kt. link how tags attach to the syllables: "none" one tag per syllable, placed before the syllable's own override tags; "syl" the same count but each tag is placed after the syllable's leading override tags; "char" one tag per visible character (combining marks stay with their base character) — the line span is split per character. weights "char" (visible length), "char_class" (CJK/Latin/space weighting, see karaoke.syllable_weights_by_char_class) or "even". replace_existing True (default) rewrites existing karaoke tags. False leaves any line that already carries karaoke tags untouched and reports it in skipped. min_cs minimum duration per syllable; a span too short to honour it raises :class:ToolError instead of silently producing zeros. snap_to_line Clamp an explicitly requested span to the line's own span when it would overshoot (reported in snapped).

Returns

{"text_source": "line", "doc_id", "mode", "kind", "link", "weights", "min_cs", "exact_sum_guaranteed": True, "sum_within_one_cs": True, "lines": [{"index", "start_ms", "end_ms", "span_ms", "span_cs", "snapped", "skipped", "reason", "durations_cs", "duration_sum_cs", "line_duration_cs", "discrepancy_cs", "matches_line_duration", "message", "syllables", "text", "old_text"}], "lines_changed": n}

The centisecond durations always sum to exactly span_cs (and therefore to the line duration when the span came from the line) — better than the one-centisecond tolerance required, so exact_sum_guaranteed is always True when a line is written.

ass_karaoke_getA

Read the parsed karaoke of one line.

Arguments

index 0-based line index (doc.events() order) of an open document. doc_id open document id (defaults to the current one).

Returns

{"text_source": "line", "index", "doc_id", "line": {...}, "has_karaoke_tags", "untimed", "timings_source", "syllables": [{"index", "text", "raw", "prefix", "kind", "duration_cs", "duration_ms", "start_ms", "end_ms", "start", "end"}], "duration_sum_cs", "duration_sum_ms", "line_duration_cs", "discrepancy_cs", "matches_line_duration", "message", "kind_counts", "kinds"}

start_ms/end_ms are absolute (the line's Start is time zero internally, the line's own start is added). discrepancy_cs is sum(syllable durations) - line duration in centiseconds and is reported explicitly, never corrected here.

ass_karaoke_removeA

Remove \k/\kf/\ko/\kt tags (karaoke.remove_karaoke).

drop_markers=True also removes the | syllable markers — they are kept by default because they are the source data :func:ass_karaoke_generate re-splits on.

keep_times controls the line's own times. True leaves Start/ End exactly as they were; False (default) retightens the line's End to Start + karaoke extent when the karaoke extent is shorter than the line (the trailing silence the karaoke timing defined is dropped).

Returns {"doc_id", "drop_markers", "keep_times", "lines": [{"index", "old_text", "text", "removed_tags": n, "old_start_ms", "old_end_ms", "start_ms", "end_ms", "times_retightened"}], "lines_changed": n, "tags_removed": n}.

ass_karaoke_retimeA

Retime existing karaoke to a new span, reporting each syllable before/after.

Arguments

selection lines to retime (None uses the session selection). mode "proportional" keeps the existing relative syllable lengths, "even" gives every syllable the same duration. new_start_ms / new_end_ms new span. When given, the line's own Start/End are set to them as well (that is what "retime the line" means); when both are omitted the line's current span is used, optionally moved by shift_ms or stretched by factor. shift_ms move the whole span by this many ms (no length change). factor scale the span length about the line start. min_cs minimum syllable duration; an impossible span raises :class:ToolError. dry_run compute and report without writing anything.

Returns

{"doc_id", "mode", "dry_run", "lines": [{"index", "old_text", "text", "old_start_ms", "old_end_ms", "start_ms", "end_ms", "span_cs", "times_changed", "syllables": [{"index", "text", "kind", "before_cs", "after_cs", "before_ms", "after_ms", "delta_cs"}], "duration_sum_cs", "line_duration_cs", "discrepancy_cs", "matches_line_duration", "message"}], "lines_changed": n, "reported_not_stretched": True}

The new centisecond durations sum to the new span exactly; any mismatch against the line duration is reported rather than stretched.

ass_karaoke_scaleA

Multiply every karaoke duration by factor (never below min_cs).

scale_times=True also scales the line's own span about its start (the Start time is kept). Returns the same shape as :func:ass_karaoke_shift with factor instead of shift_ms; rounding/clamping is reported via duration_sum_cs/discrepancy_cs/message, never stretched.

ass_karaoke_set_kindA

Convert karaoke tags to another kind (k, kf, ko, kt).

only_matching limits the conversion to syllables whose current kind is that value (e.g. only k -> kf). Durations, text and every other override tag are untouched. Returns {"doc_id", "kind", "only_matching", "lines": [{"index", "old_text", "text", "before_kinds", "after_kinds", "changed"}], "lines_changed": n, "tags_converted": n}.

ass_karaoke_set_timingsA

Write explicit per-syllable durations or absolute times.

Arguments

timings list of durations, list of {"duration_ms"|"duration_cs"|"start_ms"+ "end_ms"} dicts, or — when unit is an absolute spelling — one start time per syllable, a list of [start, end] pairs, or n + 1 boundary times. unit "ms" / "cs" (values are durations), or "absolute_ms" / "absolute_cs" / "absolute" / "times" (values are absolute times). Absolute times must be monotonically increasing. kind force every syllable to this kind; None (default) keeps each syllable's existing kind. strict True (default): a count mismatch raises :class:ToolError naming both counts. False pads with zeros / drops extras. mode/marker/pattern how to split a line that has no karaoke tags yet.

Returns

{"text_source": "line", "doc_id", "unit", "kind", "strict", "lines": [{"index", "syllable_count", "timing_count", "padded", "old_text", "text", "syllables": [{"text", "kind", "duration_cs", "duration_ms"}], "duration_sum_cs", "line_duration_cs", "discrepancy_cs", "matches_line_duration", "message"}], "lines_changed": n, "reported_not_stretched": True}

Durations that do not add up to the line duration are reported in discrepancy_cs/message; they are never stretched to fit.

ass_karaoke_shiftB

Shift every karaoke duration by shift_ms (negative trims, floor 0).

shift_times=True also moves the line's own Start/End by the same amount. Returns {"doc_id", "shift_ms", "shift_times", "lines": [{"index", "old_text", "text", "syllables": [{"index", "text", "kind", "before_cs", "after_cs"}], "duration_sum_cs", "line_duration_cs", "discrepancy_cs", "matches_line_duration", "message", "start_ms", "end_ms"}], "lines_changed": n, "clamped_cs": n} — durations never go below zero, so the sum is reported rather than stretched.

ass_karaoke_splitA

Split a karaoke line into syllables.

Arguments

text / index + doc_id Either the raw override text, or a 0-based line index in an open document. "text_source" in the result says which was used. mode "marker" (split on marker, the usual | workflow), "char" (one visible character per syllable, combining marks stay attached to their base character), "word" (whitespace) or "regex" (split on pattern). marker the marker string for mode="marker". Default "|". pattern regex for mode="regex" (a capture-free split pattern). keep_bom keep a leading U+FEFF in the text (default: strip it).

Returns

{"text_source": "text"|"line", "index", "doc_id", "mode", "marker", "pattern", "had_bom", "plain_text", "expected_visible_text", "reconstruction": {"ok", "reconstructed", "expected"}, "syllable_count", "syllables": [{"index", "raw", "text", "prefix", "prefix_tags", "leading_whitespace", "trailing_whitespace", "char_count"}]}

ass_karaoke_stylesA

List (and optionally create) the conventional karaoke template styles.

The standard set is Karaoke, Karaoke_2 and Karaoke_3 — the usual Aegisub template trio — built from one base: Arial 60, white PrimaryColour, blue SecondaryColour (so \kf/\ko sweeps from blue into white), black outline, &H80000000& shadow, bold, BorderStyle 1, Outline 2, Shadow 1, margins 10, Encoding 1. The variants differ only in Alignment: Karaoke bottom (2), Karaoke_2 top (8), Karaoke_3 middle (5), which is how the tutorial template sets stack their layers. prefix renames the trio (prefix, prefix_2, prefix_3).

create=False reports what exists without touching the document.

Returns {"doc_id", "prefix", "created": [names], "existing": [names], "styles": [{"name", "exists", "created_now", "values": {...}}]}.

ass_karaoke_templateA

Expand a karaoke template line into one generated line per syllable.

This is a documented subset of the Aegisub karaoke templater. Only $syl (the syllable text) and $sdur (the syllable duration in centiseconds) are substituted; the template's other override tags (including \t transforms) are copied verbatim into every generated line. Classes, code/once/mixin lines, the rest of the variable set and the effect library are not supported — see unsupported in the result.

Arguments

selection the lines whose syllables are expanded. template_line / template_index the template: a raw string, or a 0-based line index. With a real template index the generated lines are inserted directly after it and copy its Layer, Actor, Effect, kind, style and margins; with a raw string they are appended after the last selected line. style style for the generated lines (default: the template's style, else the Karaoke style). mode how to split the target line when it has no karaoke tags yet (marker / char / word / regex). replace_existing remove previously generated lines (same Effect marker) that sit immediately after the template line before inserting. create_styles create the standard karaoke styles when style names one that does not exist yet. dry_run report the lines that would be generated without inserting.

Returns

{"doc_id", "template_subset": True, "subset_of_aegisub_karaoke_templater": True, "supported": [...], "unsupported": [...], "variables_used": [...], "unknown_variables": [...], "template": {...}, "dry_run", "inserted_count": n, "replaced_lines": n, "generated": [{"target_index", "syllable_index", "text", "syllable", "duration_cs", "start_ms", "end_ms", "start", "end", "layer", "actor", "style", "kind", "inserted_index"}], "generated_indices": [...]}

ass_add_typesettingA

Build a leading override block out of named typesetting pieces.

Args: selection: selection spelling; every selected line receives the block. doc_id: document to edit; the current one when omitted. pos: [x, y] -> \pos(x,y). an: alignment, a plain integer 1..9 (7 -> \an7). Booleans, floats and out-of-range values are rejected; \q-style alignments are not part of this helper. move: \move animation. Accepted shapes: six numbers [x1, y1, x2, y2, t1, t2] (times in milliseconds), four numbers, two pairs, a pair (expanded to a zero-length move), or a dict with x1/y1/x2/y2 and optional t1/t2. fade: \fad/\fade. A pair [in_ms, out_ms] produces \fad(in,out); a 4-tuple [a1, a2, a3, t] produces \fade(a1,a2,a3,t,t,t,t); the full 7-tuple [a1, a2, a3, t1, t2, t3, t4] is passed through verbatim; a dict with a1/a2/a3/t1..t4 also works. clip: \clip/\iclip. A rectangle [x0, y0, x1, y1], a drawing path string ("m 0 0 l 100 0 100 100" — it must contain a drawing command, otherwise the string is rejected instead of being handed to libass as a no-op), [scale, path], or a dict {"rect": [...]} / {"drawing": "...", "scale": n} with "inverse": true to emit \iclip. org: [x, y] -> \org(x,y). extra_tags: any additional override text (braces and the leading backslash are optional) appended to the block in the order given. reset_first: put \r at the front of the block so the line starts from the style's values before the new tags are applied. in_place: write the block back to the document (snapshot-backed, True by default). With False the planned lines are returned but the document is left alone.

Returns {"doc_id", "tag_string", "override", "reset_first", "count", "changed", "written", "in_place", "lines": [{"index", "before", "text", "changed"}], "text"}. tag_string is the exact tag string produced (without the braces, e.g. \an8\pos(100,200)); text is the new line text when the selection resolved to a single line, otherwise None (the per-line texts are in lines). The block is merged into the line's existing first block so duplicated tags are updated instead of stacking.

ass_apply_tag_to_blockA

Append tags to one override block (or to every block) of a line.

Args: index / text / doc_id: the line to edit (0-based index of doc_id or a raw string). block: 0-based override block number, or "all" to touch every block. Blocks are numbered in line order and, unlike plain indices, count comment blocks too. When the line has no block at all, block=0 and "all" create one at the start of the line. override: the tags to append (braces and the leading backslash are optional; braces inside the payload are rejected). doc_id: document holding index. in_place: write back to the document (snapshot-backed). The raw-string mode never writes.

Returns {"source", "index", "doc_id", "block", "blocks_total", "applied_blocks", "override", "text", "plain_text", "changed", "written"} with applied_blocks listing the block numbers that actually received the tags. The text inside every block is preserved verbatim and the closing brace is always kept.

ass_convert_tagsA

Normalise legacy SSA override tags to their ASS spelling (or back).

Only the inside of override blocks is touched; visible text is never rewritten, and blocks are rebuilt with their braces intact.

Args: selection: selection spelling (None = every line). doc_id: document to edit; the current one when omitted. mode: "to_ass" (default) rewrites the legacy spellings to ASS — \a1..\a11 (SSA alignment) become the matching \an1.. \an9 and \K becomes \kf. "to_ssa" does the opposite (\an back to \a, \kf back to \K). dry_run: report the replacements without writing (no snapshot).

Returns {"doc_id", "mode", "dry_run", "count", "changed", "written", "replacement_count", "lines": [{"index", "before", "after", "changed", "replacements": [{"from", "to", "kind", "note"}]}]}; every replacement is listed individually so the caller can audit the conversion. changed is the number of lines whose text differs (also in dry_run), written the number actually stored. Snapshot-backed unless dry_run is set.

Indices: override blocks are addressed by position, so no plain-character index and no raw offset is taken as input. Because only tag names inside the braces change, both index maps of the visible characters are preserved exactly: the plain-character index of every visible character and the raw index of every character in the line are the same before and after.

ass_insert_tag_atA

Insert a bare override block at a plain character position.

Args: index / text / doc_id: the line to edit (0-based index of doc_id or a raw string). plain_index: plain (visible character) index the block is inserted at; 0 puts it before the first visible character, plain_len puts it at the end of the line. Override blocks do not count, so for "ab{\i1}cd" plain index 2 is between b and c. override: the tags to insert (braces and the leading backslash are optional, braces inside the payload are rejected). after: insert after the character at plain_index instead of before it. With after=True and plain_index == plain_len the block is appended at the very end. doc_id: document holding index. in_place: write back to the document (snapshot-backed). The raw-string mode never writes.

Returns {"source", "index", "doc_id", "plain_index", "after", "override", "text", "plain_text", "changed", "written"}. plain_index is always a plain index; the raw offsets of the new block are not reported because the insertion shifts them.

ass_karaoke_tags_onlyA

List only the karaoke tags of a line, with their arguments.

Handy right before retiming: it shows the syllable tags in order together with the visible text each one covers.

Args: text / index / doc_id: the line to inspect (raw string, or 0-based index inside doc_id).

Returns {"source", "index", "doc_id", "has_karaoke", "count", "tags", "plain_text", "syllable_text", "times_ms", "total_ms"}. Each entry of tags is {"name" (canonical: k, kf, ko or kt), "argument", "raw", "plain_index", "block", "text"} where plain_index is the plain (visible character) index at which the tag takes effect, block is the 0-based override block it lives in and text is the visible text that follows it up to the next karaoke tag (the syllable it times). syllable_text concatenates those chunks and total_ms sums the integer arguments (\kt/\ko are not durations and are counted in count but reported as None in times_ms). Read-only; no snapshot.

ass_parse_textA

Tokenise an ASS line into its ordered segments and override tags.

Args: text: a raw ASS Text field (tags included). Use this to inspect a line that is not (yet) in a document. index: 0-based line index in doc_id; the line's current Text field is read from the document. doc_id: document holding index; the current document when omitted. Passing both text and index is an error.

Returns a dict with:

source/index/doc_id which input was used ("text" or "line"). raw, raw_length the line exactly as stored (raw index space). plain_text, plain_length, characters the visible text and, for every visible character, its index in both spaces: {"plain_index": i, "char": c, "raw_index": r}. segments ordered alternating text runs and {...} blocks. A text segment carries text plus start/end (raw indices) and plain_start/plain_end. A block segment carries raw (including braces), inner, block (0-based block number), is_override (True when it holds at least one \ tag; a block without tags is an ASS comment block), the same four offset fields (plain_start == plain_end for blocks) and tags. tags the flat list of every tag in line order, each with name (canonical, lowercase, e.g. kf for \K), argument ("" for valueless tags), raw (exact source text), paren (whether the argument was written in parentheses), is_override and block. Every parsed tag has is_override True because only backslash-prefixed content is parsed as a tag; see the block-level flag for comment blocks. summary per-line flags: blocks, tags, tag_names (histogram), tag_groups (histogram by tag family), has_drawing (drawing mode is still active at the end of the line, i.e. an unmatched \p), drawing_state, has_karaoke, has_transform, has_clip and plain_length. Use tag_names["p"]/tag_groups["drawing"] to detect a drawing that is switched off again by \p0.

Read-only; no snapshot. start/end are raw indices, everything with plain in its name is a plain (visible character) index.

ass_plain_textA

Strip every override tag, optionally keeping some tags or tag groups.

Args: text / index / doc_id: the line to inspect (raw string or a 0-based line index of doc_id); exactly one source must be given. keep: "" (or None) removes everything. Otherwise a comma/space separated string or a list whose items are either a tag name ("pos", "\an", "1c") or a tag group name. Group names win over tag names and expand to the whole family, e.g. "clip" keeps \clip and \iclip, "fade" keeps \fad and \fade. Known groups: transform, fade, clip, drawing, karaoke, layout, color, style, reset, animation.

Returns {"source", "index", "doc_id", "text", "plain_text", "keep_names", "keep_groups", "kept", "changed"} where text is the stripped line (it still contains the kept override blocks), plain_text is the fully tag-free visible text and kept lists the kept tags that were actually present. Read-only; no snapshot, no indices.

ass_remove_tagA

Delete every occurrence of the named override tags.

Args: selection / index / text / doc_id: the usual three input modes. names: a single tag name ("pos") or a list (["pos", "move"]); a comma separated string works too. Group names are accepted and expand to the whole family ("clip" removes \clip and \iclip). in_place: write back to the document (snapshot-backed). The raw-string mode never writes.

Returns, single-line: {"source", "index", "doc_id", "names", "removed", "removed_count", "text", "plain_text", "changed", "written"} where removed lists every deleted occurrence as {"name", "argument", "raw", "block"}; selection mode returns {"source": "selection", "doc_id", "names", "count", "changed", "written", "in_place", "removed_count", "lines": [{"index", "before", "after", "removed", "changed"}]}. Removing a tag never touches the visible text and never leaves an unclosed block: emptied blocks are dropped entirely.

ass_set_tagA

Insert (or update) a single override tag on a line or a selection.

Args: selection: selection spelling; when given, text/index must be omitted and every selected line is processed. index / text / doc_id: single-line source (0-based index inside doc_id, or a raw string). name: tag name without the backslash, e.g. "fad", "an", "pos", "fscx" or "1c". A leading backslash is allowed. arg: the tag argument, e.g. "200,200" for \fad(200,200) or "8" for \an8. Parentheses are added automatically when the argument needs them. Braces and line breaks are rejected, and so is an out-of-range alignment for \an (1..9) or legacy \a (1..11) — those are ignored by libass when wrong, so they are caught here instead. value: alias for arg; when not None it wins (handy for numeric callers, e.g. value=8). doc_id: document for index/selection. where: where to put the tag. "prepend" merges the tag into a new leading block (the line's existing first block is reused so that repeated tags are updated rather than duplicated); "after_first_block" (default) appends the tag to the end of the line's first override block, creating that block when the line has none; "prepend_block" always inserts a brand new leading block; "append" adds a block at the very end of the line; "wrap" wraps every visible character and restores the previous value afterwards (see :func:ass_wrap_range). only_if_missing: leave the line untouched when a tag with this name is already present anywhere in the line. in_place: write back to the document (single-line and selection mode). Snapshot-backed; the raw-string mode never writes.

Indices: this tool edits whole override blocks, so it never takes character offsets. Neither the plain-character index of the visible text nor the raw index of the line is used or reported — but every where mode leaves both maps of the visible characters unchanged (a tag is only ever inserted between characters).

Returns {"source", "index", "doc_id", "name", "argument", "tag", "where", "text", "changed", "written", "skipped", "reason", "warnings"} for a single line (tag is the exact tag text produced, e.g. \fad(200,200)) or {"source": "selection", "doc_id", "name", "tag", "where", "count", "changed", "written", "in_place", "lines": [...]} for a selection. duration-style arguments are passed through verbatim.

ass_strip_tagsA

Remove override tags from a line, a raw string or a whole selection.

This is the bulk "clean up the tags" tool. The keyword names mirror asscore.tags.strip_tags (keep, keep_groups, remove_groups, keep_drawing, keep_karaoke); remove and keep_clip are additions of this tool, where remove lists individual tag names to drop before stripping and keep_clip is shorthand for keep_groups=["clip"].

Args: selection: a selection spelling (None = every line, "0-4", {"style": "Default"} ...). When given, every selected line is processed and text/index must be omitted. text: raw ASS line to process instead of a selection. index: 0-based index of the line to process inside doc_id. doc_id: document used by selection/index; the current one when omitted. keep: tag names (string or list) to keep, e.g. "pos,an". Group names are accepted too and move into keep_groups. keep_groups: tag group names to keep (transform, fade, clip, drawing, karaoke, layout, color, style, reset, animation). remove: tag names to delete outright before stripping (string or list). remove_groups: tag group names to delete outright. keep_drawing: keep \p/\pbo tags so drawings survive. keep_karaoke: keep \k-family tags so karaoke timings survive. keep_clip: keep \clip/\iclip. in_place: write the result back to the document (line/selection mode only). Snapshot-backed; the raw-string mode never writes.

Returns, in single-line mode: {"source", "index", "doc_id", "text", "plain_text", "changed", "written", "keep_names", "keep_groups", "remove_names", "remove_groups", "keep_drawing", "keep_karaoke", "keep_clip"}; in selection mode: {"source": "selection", "doc_id", "count", "changed", "written", "in_place", "lines": [{"index", "before", "after", "changed"}]}. Plain text is always tag-free; kept tags stay inside their override blocks.

ass_swap_an_posA

Mirror \an vertically and move \pos so the text does not budge.

The classic Aegisub helper: \an1 <-> \an7, \an2 <-> \an8, \an3 <-> \an9 (the middle row 4/5/6 has no vertical mirror and is left alone). Because the anchor flips between the bottom and the top of the line, \pos has to move by exactly the line height for the rendered text to stay where it was.

The shift is computed from the real rendered size of the line, measured with :func:asscore.measure.measure_line_render in script pixels (see :func:_measure_geometry). height is the vertical anchor separation (\an7 ink top minus \an1 ink top, both anchored at the same y), i.e. the line height, and width is the advance width (left side bearing + ink width + right side bearing, from the \an7/\an9 probes), i.e. the horizontal anchor separation. One numpad row or column step moves the anchor by half of the corresponding separation, and the anchor must move the other way for the ink to stay put::

row_step = new_row - row      # numpad grid, +-2 for a top/bottom swap
col_step = new_col - col      # +-2 for a left/right swap
dy = -(height / 2) * row_step # row 2 is the top of the screen
dx = +(width  / 2) * col_step # col 2 is the right of the screen

For \an7 -> \an1 that is row_step = -2 and dy = +height: the anchor walks down by the line height so the text stays on its screen row. A purely vertical mirror never changes the column, so dx stays 0 there; horizontal=True additionally mirrors the column (\an7 <-> \an9, \an1 <-> \an3, \an4 <-> \an6) and then dx = +width for \an7 -> \an3 (numpad columns grow rightwards, so the new right-edge anchor has to sit one width further right), which is what makes the width term reachable.

The measured text is the line with its own \pos/\move/\an removed, rendered with the line's style and the document's PlayRes, so the height matches what libass does for the real line.

Indices: \an/\pos are located inside the override blocks, so no plain-character index and no raw offset is taken as input. selection refers to event indices (doc.events() order, 0-based), and the plain text of every line survives byte for byte, so both index maps are unchanged.

Args: selection: selection spelling; the lines to rewrite. doc_id: document to edit; the current one when omitted. margin_mode: how to treat lines that have no \pos. By default they are skipped (there is nothing to shift). With margin_mode=True the vertical margin is rewritten instead: MarginV = PlayResY - MarginV - height, which keeps an alignment/margin-positioned line in place across the mirror. dry_run: compute and report the plan without writing (no snapshot). horizontal: also mirror the alignment column (180 degree mirror) and shift x by the advance width. Off by default, matching the Aegisub helper.

Returns {"doc_id", "dry_run", "margin_mode", "horizontal", "count", "changed", "written", "geometry_source", "lines": [...]}; each line entry has index, before, after, old_an, new_an, old_pos, new_pos, dx, dy, margin_v, measured ({"width", "height", "ink_width", "ink_height", "left_bearing"}), changed, skipped and reason. Retiming/render checks aside, the check to apply is that the ink bounding box before and after is identical — see the test suite, which asserts exactly that with :func:asscore.measure.measure_render.

ass_tag_summaryA

Per-line tag histogram and typesetting sanity check.

Args: index / text / doc_id: inspect a single line (0-based index of doc_id or a raw string). selection: inspect a set of lines (None means every line when no index/text is given). doc_id says which document.

Returns {"source", "doc_id", "index", "count", "missing_position", "lines": [...], "totals": {...}}. Every line entry carries index, raw, plain_text, visible_chars, blocks (number of override blocks), tags (total), tag_names (per-name histogram), tag_groups (the same count folded into tag families such as layout, color, karaoke, clip, transform), has_drawing (drawing mode still active at the end of the line) / drawing_state, has_karaoke/has_transform/has_clip, first_block_has_pos/first_block_has_move and missing_position (True when the line's first override block has neither \pos nor \move — the line is then placed by styles and margins only, which is the usual typesetting smell). missing_position at the top level lists the indices of those lines; totals sums tags, blocks and flags over the selection. Read-only; no snapshot. All indices are 0-based line indices, while visible_chars counts plain (visible) characters.

ass_wrap_rangeA

Wrap a character range in an override block, restoring the outer state.

Args: index / text / doc_id: the line to edit (0-based index of doc_id or a raw string). start: range start. A plain index when scope="plain" (the default), a raw index when scope="raw". end: range end, exclusive. Same index system as start; None means "to the end of the line" (the visible end / the raw end). override: the tags to apply, with or without the surrounding braces and with or without the leading backslash (r"\fscx200", "fscx200" and r"{\fscx200}" are all accepted). Braces and line breaks inside the payload are rejected. doc_id: document holding index. scope: "plain" (default) counts only visible characters — override blocks and the \N/\n line-break escapes are not counted, so the same start/end cover the same glyphs no matter how many tags precede them. "raw" counts every stored code point including braces, which is what you want when you already have offsets into the stored Text field. A raw boundary that would land inside an override block is refused with a ToolError instead of silently splitting the block. in_place: write back to the document (snapshot-backed). The raw-string mode never writes.

The tags that were in effect before start are re-emitted after end when they changed inside the range, so the override applies to exactly the requested characters. warnings reports tags that had no previous value to restore (there is nothing to restore for e.g. \an).

Returns {"source", "index", "doc_id", "scope", "start", "end", "raw_start", "raw_end", "plain_start", "plain_end", "override", "text", "plain_text", "changed", "written", "warnings"}. start/end are echoed in the requested index system; plain_start/plain_end are always plain (visible character) indices and raw_start/raw_end are always raw offsets into the stored line, so the two systems stay comparable no matter which one was passed in.

ass_get_drawingA

Inspect the drawing part of a line, or a raw drawing string.

Pass exactly one of:

  • index + doc_id -- the line at that 0-based index (doc.events() order) is split with tags.drawing_parts and its drawing is inspected;

  • text -- either a full override line (anything containing { or a \p tag, split the same way) or bare drawing path data.

scale is the \p level in effect for the drawing, so the returned commands, bbox, size, centre and path length are all in that scale space (units as written in the file); multiply by 2 ** (1 - scale) for script pixels. normalised_* mirrors of the bbox/size are provided already converted.

Returns a dict with source ("line" or "text"), doc_id, index, drawing (the raw drawing text), scale, has_drawing, commands (list of {kind, args, points, text}), bbox, normalised_bbox, size, normalised_size, center (aliased as centre), subpath_count, point_count, path_length, svg_path and clips (see :func:ass_get_clips for that shape).

ass_set_drawingA

Replace the drawing part of line index.

drawing_text is parsed and re-serialised with integer coordinates, so the stored text is canonical (this is the only rewriting applied). scale, when given, sets the line's \p level (\p<scale>); when omitted an existing \p tag is left alone and a missing one is added as \p1 so the drawing actually renders.

keep_tags=True keeps every non-drawing override tag and anything after the drawing (typically the closing {\p0}); keep_tags=False rebuilds the line as {\p<scale>}<drawing> and discards all other text.

Returns {doc_id, index, source, old_drawing, drawing, scale, text}.

ass_drawing_infoC

Parse a drawing only (no measurement, no line needed).

Coordinates are in the drawing's own scale space (see the module docstring). Returns {source, doc_id, index, drawing, scale, commands, subpaths: [{index, bbox, size, text, point_count}], bbox, size, center (aliased as centre), subpath_count, point_count, path_length}.

ass_transform_drawingA

Transform a drawing and return the new path data (or write it back).

Coordinates in and out are the drawing's own scale-space units (see the module docstring); results are serialised with integer coordinates.

action and the parameters it uses:

translate dx, dy -- shift every point. scale factor about (origin_x, origin_y); the origin defaults to the drawing's bbox centre so it stays put. scale_to_size dx, dy = target width/height; uniform scale (aspect ratio kept), drawing moved into (0,0,w,h). stretch_to_bbox dx, dy = target width/height; independent axes (aspect ratio deliberately broken). rotate angle_deg clockwise on screen about the origin (bbox centre by default). mirror axis="x" flips left/right, axis="y" top/bottom about the origin (bbox centre by default). center/centre_at_origin move the bbox centre to (origin_x, origin_y), default (0, 0). reverse reverse the winding of every subpath. flatten replace Bézier/B-spline curves with polylines. simplify Douglas-Peucker with tolerance factor (default 1.0). round snap coordinates to the nearest multiple of factor (factor=1.0 = integers, the default).

in_place=True with index writes the result back into the line (after workspace.snapshot) and returns the new line text; otherwise no document is touched and drawing holds the new path data.

Returns {source, doc_id, index, action, drawing, bbox, size, center, centre, changed, written} (centre duplicates center).

ass_drawing_bboxA

Bounding box / size / centre of a drawing.

Coordinates are in the drawing's own scale space (\p level). The box is the control polygon box -- Bézier/B-spline handles included -- matching what VSFilter/Aegisub use for positioning.

Returns {source, doc_id, index, scale, bbox, size, center, centre, normalised_bbox, normalised_size, point_count}.

ass_drawing_to_svgB

Export a drawing as a standalone .svg file in workspace.output_dir.

The path data is the drawing's absolute SVG equivalent (scale space units, one SVG user unit per drawing unit). padding grows the viewBox on every side so strokes near the edge are not clipped.

path is a filename (resolved inside workspace.output_dir) or an absolute path; the default is drawing.svg / drawing_<index>.svg and is overwritten if it exists.

Returns {path, d, view_box, width, height, bbox, source, doc_id, index}.

ass_svg_to_drawingA

Convert SVG path data (or a whole .svg file) into ASS drawing text.

Either d (one or more d attributes, space separated) or svg_path (the file is read, its viewBox becomes the origin shift and every <path d="..."> is converted). view_box given as "x y w h" or [x, y, w, h] shifts the paths so the box origin becomes (0, 0).

scale multiplies every coordinate; round_to is the number of decimal places kept when serialising (0 = integer coordinates).

SVG and ASS both grow y downwards, so nothing is flipped. Supported commands: M L H V C S Q T A Z (relative forms too); quadratics are promoted to cubics and arcs to cubic segments.

Returns {drawing, bbox, size, center, path_count, view_box, scale, round_to, source}.

ass_split_drawingA

Split a drawing into one string per subpath (no subpath is ever dropped).

Commands appearing before the first m/n form their own leading part, so split followed by :func:ass_join_drawings is lossless. Coordinates stay in the drawing's own scale space.

Returns {source, doc_id, index, scale, count, subpaths: [str, ...], parts: [str, ...] (alias of subpaths), point_counts: [int, ...], bboxes: [...]}.

ass_join_drawingsA

Join several drawing strings into one (the inverse of ass_split_drawing).

parts is a list of drawing strings (a single string is treated as a one-element list). Every part must parse; empty strings are rejected so a subpath can never be silently lost. Coordinates are serialised as integers.

Returns {drawing, count, subpath_count, point_count, bbox, size, doc_id}.

ass_get_clipsB

List every \clip / \iclip tag on a line (or in raw text).

Each entry has tag ("clip"/"iclip"), inverse, raw argument, kind ("rect" or "vector"), the explicit scale (None when omitted) and the effective_scale actually used, plus coords as authored in the clip's scale space and normalised coordinates converted to script resolution (multiplied by 2 ** (1 - effective_scale)). bbox is the box in scale space and normalised_bbox the same box in script resolution.

Returns {source, doc_id, index, line_scale, count, clips: [...]}.

ass_set_clipA

Set, add or remove a clip on the selected lines.

selection accepts anything :func:base.resolve_indices understands (None = the session selection, or every line when none is set).

Geometry is taken from the first one of these that is given: rect ("x1,y1,x2,y2" or a 4-list, in script resolution unless scale names the scale space those numbers are written in), drawing_text (ASS path data), svg_path / svg_d (SVG path data) or spec (a rectangle string, an ASS drawing, or an SVG path -- SVG is detected by its H/V/Q/T/A/Z command letters and the result is converted).

scale for a vector clip sets the explicit \clip(<scale>,...) level; when omitted a vector clip is written at level 1 (script resolution). inverse=True emits \iclip.

mode:

  • "replace" -- strip every existing clip from the line, then insert.

  • "add" -- keep existing clips, insert the new one at the front.

  • "remove" -- strip every clip tag (\clip and \iclip) from the lines; the geometry arguments are ignored.

Returns {doc_id, mode, kind, tag, scale, inverse, lines: [{index, changed, text, removed, clip}]}.

ass_remove_clipB

Strip clip tags from the selected lines.

include_inverse=True (the default) removes \iclip as well as \clip; False keeps inverse clips. Blocks left empty by the removal are dropped, which is expected for a remove operation.

Returns {doc_id, include_inverse, changed, lines: [{index, removed, changed, text}]}.

ass_convert_clip_scaleA

Rewrite vector clip coordinates from their current \p scale to target_scale.

A clip made at level S renders each unit at 2 ** (1 - S) script pixels; to keep the rendered result identical at level T every coordinate is multiplied by 2 ** (T - S) and rounded to the nearest integer. The clip is rewritten with an explicit \clip(T,...) level.

The tool proves the conversion by measuring the rendered ink bounding box with measure.measure_render before and after (at the midpoint of the first selected line) and reporting both, plus converged. Measurement failing (no ffmpeg) is reported as measurement.error instead of an exception.

Both measurements carry an empty flag: if the line renders no ink at all (for example a drawing with no \pos/\an whose geometry lands off-screen) then before and after are both empty and converged is true for a trivial reason. A caller that wants a real proof must check not measurement["before"]["empty"] first, which is what the test suite does.

dry_run=True computes everything and reports the measurement proof without touching the document.

Returns {doc_id, target_scale, dry_run, converted, lines: [{index, source_scale, target_scale, ratio, before_arg, after_arg, text}], measurement: {before, after, delta, converged, time_ms}}.

ass_scale_drawingA

Scale the drawing part of each selected line (and its clips) by factor.

This is the classic "make everything on the line bigger" helper. The drawing part and every clip are scaled about one common origin -- the drawing's bbox centre when the line has a drawing, otherwise the first clip's own centre -- so the drawing and its clips keep their relative position instead of each drifting toward its own centre. Coordinates remain in each clip's own scale space (the \clip(N,...) level is preserved) and are rounded to integers with the module's explicit half-away-from-zero rule.

include_clips=False scales only the \p drawing. dry_run=True reports the new text without touching the document.

Returns {doc_id, factor, include_clips, dry_run, changed, applied, lines: [{index, old_text, text, drawing_scaled, clips_scaled}]}.

ass_list_fontsB

List installed fonts, optionally filtered by a case-insensitive substring.

pattern is a plain substring matched against family, style and file -- not a fontconfig pattern expression. limit caps the number of entries.

Returns {pattern, limit, count, fonts: [{family, style, file, index, family_raw}, ...]}.

ass_match_fontB

What fontconfig actually resolves for family (with bold/italic).

The first candidate is the face that would be used; when its family differs from the request the subtitle will be rendered in a substitute.

Returns {requested, bold, italic, resolved, file, style, substituted, match, candidates}.

ass_fonts_with_charA

Which installed family names contain the glyph for char.

char must be exactly one character. Returns {char, codepoint, codepoint_hex, count, families} (families sorted case-insensitively).

ass_font_coverageB

Characters of text missing from family (default: the system sans).

The family is resolved through fontconfig first, so a substituted request is reported honestly. Missing characters come back with their codepoints, plus up to a few installed families that do cover them.

Returns {text, requested, default_used, bold, italic, resolved_family, substituted, missing: [{char, codepoint, codepoint_hex, fallbacks}], missing_count, missing_chars, missing_codepoints, covered, checked, coverage_source, font}. requested is the family the check actually ran against, so it is never None: when family is omitted it is the system default sans family and default_used is True.

ass_glyph_checkA

Missing glyphs for a line's text or for a style's font.

The string checked is text when given, otherwise the plain text of line index (override tags removed). The font comes from family when given, otherwise from the style named by style (its Fontname, Bold and Italic) -- when neither is given the style of the line is used, and failing that the system default sans.

Returns {source, doc_id, index, style, requested, resolved_family, substituted, bold, italic, text, missing: [{char, codepoint, codepoint_hex, fallbacks}], missing_count, missing_chars, missing_codepoints, checked, covered, coverage_source}.

ass_fonts_usedB

Every font the script asks for, plus embedded font attachments.

Styles contribute their Fontname (with the Bold/Italic flags that affect which face fontconfig resolves); the [Fonts] section contributes each fontname: attachment. Every family is resolved through fontconfig so families that are not installed and families that resolve to a substitute are flagged explicitly.

Returns {doc_id, families: [{family, installed, substituted, resolved, file, style, bold, italic, styles: [names], attachment}], attachments: [names], not_installed: [names], substituted: [names], used_count}.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

B3.3/5.0

Scored across 121 tools

Disambiguation2/5

With 121 tools, many cover overlapping inspection or editing tasks: get_drawing/drawing_info/drawing_bbox all inspect drawings, align_to_silence and align_lines_to_silence are near-duplicates, and multiple tagging tools (set_tag, apply_tag_to_block, add_typesetting, insert_tag_at) operate on the same line text. Detailed descriptions help, but the set still requires an agent to remember subtle scope distinctions.

Naming Consistency4/5

Every tool is snake_case and prefixed with ass_, and most follow a verb_noun pattern (ass_list_lines, ass_add_style, ass_remove_tag). Minor deviations are noun-only tools like ass_cps, ass_stats, ass_qc, ass_document_info, and compound forms such as ass_ms_from_frame, but they are rare and readable.

Tool Count1/5

121 tools is an extreme mismatch for a single MCP server; even a broad subtitle-editing domain does not justify this many separate entry points. The surface is far past the recommended 3–15 range and overwhelms tool selection.

Completeness5/5

The tool set covers the domain exhaustively: document lifecycle, line CRUD and bulk edits, style CRUD, Script Info, attachments, extradata, validation, timing/retiming, karaoke, typesetting tags, drawings, clips, fonts, and QC/export/import. No obvious CRUD or lifecycle gaps remain.