Skip to content

Support injectable charset detection for standalone SubRip subtitles - #3368

Open
hoyahozz wants to merge 1 commit into
androidx:mainfrom
hoyahozz:fix/2247-srt-charset
Open

Support injectable charset detection for standalone SubRip subtitles#3368
hoyahozz wants to merge 1 commit into
androidx:mainfrom
hoyahozz:fix/2247-srt-charset

Conversation

@hoyahozz

@hoyahozz hoyahozz commented Aug 12, 2026

Copy link
Copy Markdown

Summary

External standalone and raw-text SubRip inputs without a supported UTF byte order mark currently fall back to UTF-8. As a result, subtitles encoded with legacy character encodings such as GB18030, EUC-KR, or Shift_JIS may be decoded incorrectly.

This change introduces an injectable CharsetDetector contract in the extractor text package and passes it through DefaultSubtitleParserFactory to applicable SubripParser instances. Applications can provide their own charset detection implementation without requiring Media3 to depend on a specific detection library.

This follows the extension-point approach proposed in google/ExoPlayer#3644.

Behavior

  • Existing behavior is unchanged when no detector is configured or when the detector returns null: a UTF-8 or UTF-16 byte order mark still wins, and everything else falls back to UTF-8.
  • A byte order mark takes precedence over the detector, which is not called at all in that case.
  • The detector is only used for SubRip Formats whose containerMimeType is null or a text MIME type, i.e. standalone .srt input.
    • SubRip muxed into a container (Matroska/WebM) keeps the current UTF-8 behavior, because a single sample may hold only a small part of the subtitle stream.
  • CharsetDetector.detect() receives exactly the offset and length passed to SubtitleParser.parse().
    • That range is not guaranteed to be a complete file, so implementations have to tolerate partial input.
  • Scoping is done by DefaultSubtitleParserFactory. SubripParser itself always uses whatever detector it is constructed with.

This PR intentionally does not provide a charset detection implementation. Applications remain responsible for choosing and configuring one.

Example - injecting a detector for an external SRT

The following example assumes that appCharsetDetector wraps a detection library selected by the application.

CharsetDetector charsetDetector =
    (data, offset, length) -> appCharsetDetector.detect(data, offset, length);

DefaultMediaSourceFactory mediaSourceFactory =
    new DefaultMediaSourceFactory(context)
        .setSubtitleParserFactory(new DefaultSubtitleParserFactory(charsetDetector));

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(mediaSourceFactory)
        .build();

MediaItem.SubtitleConfiguration subtitleConfiguration =
    new MediaItem.SubtitleConfiguration.Builder(subtitleUri)
        .setMimeType(MimeTypes.APPLICATION_SUBRIP)
        .setLanguage("ko")
        .build();

MediaItem mediaItem =
    new MediaItem.Builder()
        .setUri(videoUri)
        .setSubtitleConfigurations(Collections.singletonList(subtitleConfiguration))
        .build();

player.setMediaItem(mediaItem);
player.prepare();
player.play();

When the external SRT is opened, DefaultMediaSourceFactory creates a standalone subtitle Format. DefaultSubtitleParserFactory then creates a SubripParser with the configured detector. If the input has no supported UTF BOM, the parser asks the detector to inspect the exact requested byte range and decodes that range using the returned charset.

The same detector is also used for raw-text SubRip inputs. It is not passed to SubRip samples embedded in Matroska or WebM containers.

Before / After

The same BOM-free subtitle files were played on the same physical device. Without a detector, the existing UTF-8 fallback produces replacement characters. Injecting a detector that returns the matching charset restores the original Korean, Chinese, and Japanese text.

Language and encoding Before : no detector After : matching detector
Korean (EUC-KR) 02-euc-kr-without-detector 03-euc-kr-with-detector
Chinese (GB18030) 05-zh-gb18030-before 06-zh-gb18030-after
Japanese (Shift_JIS) 07-ja-shiftjis-before 08-ja-shiftjis-after

Fixes #2247

Standalone SubRip subtitles without a byte order mark are decoded as
UTF-8, which corrupts files that use legacy character encodings.

Add an injectable CharsetDetector to DefaultSubtitleParserFactory and
use it when parsing standalone SubRip files. Keep BOM precedence, UTF-8
fallback, and embedded Matroska/WebM subtitle behavior unchanged.

Issue: androidx#2247
@tonihei

tonihei commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the contribution. @icbaker I think that matches the idea in google/ExoPlayer#3644 (comment) and could probably be merged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants