Skip to content

[Bug]: Hebrew subtitles render with LTR base direction, sentence-final punctuation on the wrong side #665

Description

@ReemX

Pre-flight checks

  • I searched existing issues and this is not a duplicate.
  • I can reproduce this on the latest desktop release or latest main build.
  • This issue is limited to a single bug (not multiple unrelated problems).
  • This is not a source/stream-specific error (the issue happens regardless of which source is used).

App version / commit

0.1.23-alpha (installer reports app-version=1.1.23)

Install method

GitHub Release desktop installer/package

Platform

Windows (desktop)

Desktop hardware

Custom PC, Ryzen 7 7800X3D, RTX 3080

OS version

Windows 11 Pro 26200

Area (tag)

Subtitles (download, styling, sync)

Bug description

Hebrew subtitles render with a left-to-right base paragraph direction. The Hebrew words themselves shape correctly right-to-left, but punctuation that ends a sentence — period, question mark, exclamation mark — is placed at the right edge of the line instead of the left.

A line whose logical text is יופי. נראה לי שזה טוב. displays with the closing period at the start of the visible line rather than at its end. Every line ending in punctuation is affected, which makes subtitles noticeably harder to read across a whole film.

This is not a font or encoding problem. The subtitle files are valid UTF-8 and carry no bidi control characters — one 85 KB Ktuvit .srt contains a single U+200F in the entire file — so the rendering depends entirely on the base direction the player resolves.

Android is not affected. PlayerSubtitleRtlFix.kt in composeApp/src/androidMain/ is applied to sidecar subtitles from PlayerSidecarSubtitles.kt. Desktop has no equivalent, because it renders through the native mpv/libass bridge rather than media3.

Root cause

libass resolves the base paragraph direction from the ASS style's Encoding field:

ass_resolve_base_direction(int enc):
    enc == -1  ->  FRIBIDI_PAR_ON    // auto-detect per paragraph
    otherwise  ->  FRIBIDI_PAR_LTR   // "for compatibility with VSFilter"

That value reaches the shaper through ass_render.c:

state->font_encoding = style->Encoding;      // ass_reset_render_context()
info->font_encoding  = state->font_encoding; // parse_events()

When mpv converts an SRT to ASS it writes an ordinary charset number, never -1, so the base direction is always forced to LTR. Strong RTL runs still shape correctly, but trailing neutral characters resolve against the LTR paragraph base and land on the wrong side — exactly the observed artifact.

FriBidi and HarfBuzz are compiled in: MPVKit runs BuildFribidi() and BuildHarfbuzz() before BuildASS(). Nothing is missing from the build, the value is simply never set.

Steps to reproduce

  1. Play any movie on desktop.
  2. Load an external Hebrew .srt subtitle (any addon, or a local file — I used Ktuvit and Wizdom subtitles).
  3. Wait for a subtitle line that ends in a period or question mark.
  4. Look at which side of the line the punctuation sits on.

Expected behavior

Sentence-final punctuation appears at the left edge of the line, as it does in other players and on the Android build.

Actual behavior

Sentence-final punctuation appears at the right edge of the line.

Frequency

Always

Did this work before?

No, it never worked

Media details (optional)

  • Content type: movie
  • Title: Michael (2026), tt11378946
  • Subtitles: external Hebrew .srt (Ktuvit and Wizdom addons; also reproducible with a local file)

Logs (required for crash reports)

Anything else? (optional)

Suggested fix

Set Encoding=-1 on the ASS style via sub-ass-style-overrides, which restores per-paragraph auto-detection. mpv gates that list on the override level being non-zero:

if (shared_opts->ass_style_override[sd->order])
    ass_set_style_overrides(ctx->ass_library, opts->ass_style_override_list);

applySubtitleStyle already sets sub-ass-override to either scale or force, both non-zero, so the override would apply in both subtitle modes.

The three desktop bridges need slightly different handling:

  • native/windows/player_bridge.cpp and native/macos/player_bridge.mm already build a change-list sub-ass-style-overrides command, but the libass branch passes clr and clears the list. The encoding override has to be set there too, not only alongside Bold in the custom-style branch.
  • native/linux/player_bridge.cpp never touches sub-ass-style-overrides at all — it only sets sub-ass-override and the individual sub-* properties — so it needs the override added rather than edited.

Auto-detection is per paragraph, so Latin subtitles are unaffected, and the same change fixes Arabic and Persian.

I can open a PR for this if you'd like.

Screenshot

Image

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions