Skip to content

Feat: Add ASS/SSA subtitle support via jassub-embedded#138

Open
mrcanelas wants to merge 2 commits intoStremio:masterfrom
mrcanelas:feat/ass-subtitles
Open

Feat: Add ASS/SSA subtitle support via jassub-embedded#138
mrcanelas wants to merge 2 commits intoStremio:masterfrom
mrcanelas:feat/ass-subtitles

Conversation

@mrcanelas
Copy link
Copy Markdown
Contributor

@mrcanelas mrcanelas commented Apr 23, 2026

Summary

This PR adds ASS/SSA subtitle support to withHTMLSubtitles using @mrcanelas/jassub-embedded.
Non-ASS subtitles still use the existing HTML subtitle flow unchanged.

What changed

  • added ASS/SSA track detection
  • render ASS/SSA subtitles with @mrcanelas/jassub-embedded
  • kept the existing HTML subtitle path for SRT/VTT unchanged
  • added ASS instance lifecycle handling for create, destroy, and recreate on subtitle changes
  • ensured HTML and ASS subtitles never render at the same time
  • added guards for null-ish subtitle state when subtitles are disabled
  • kept everything inside stremio-video, with no stremio-web changes or external worker/wasm asset setup
  • Subtitle settings behavior

For ASS/SSA subtitles:

  • Delay is supported
  • Size is not applied
  • Vertical Position is not applied

This is intentional, since ASS/SSA subtitles usually define their own styling, positioning, and typesetting. Applying the HTML subtitle controls on top of them would likely break rendering.
For SRT/VTT subtitles, existing behavior remains unchanged.

Font note

ASS/SSA subtitles may depend on embedded fonts for correct rendering.
jassub expects font sources as URLs or binary font data. A possible follow-up improvement is to use stremio-server to extract MKV attachments and serve those fonts as URLs, so they can be passed into the ASS renderer when needed.
Without that, some ASS subtitles may still render with fallback fonts when the original attached fonts are not available.

Related issues

Closes #46, closes Stremio/stremio-features#232

How we tested (local addon example)

const express = require('express');

const KUSURIYA_S2_OP1_ASS = 'https://jassub.pages.dev/subtitles/Kusriya%20S2%20OP1v3.ass';
const KUSURIYA_S2_OP1_WEBM = 'https://v.animethemes.moe/KusuriyaNoHitorigotoS2-OP1-NCBD1080.webm';

const PORT = Number(process.env.PORT) || 7700;
const HOST = process.env.HOST || '127.0.0.1';

const app = express();

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    if (req.method === 'OPTIONS') {
        res.sendStatus(204);
        return;
    }

    next();
});

const manifest = {
    id: 'com.stremio.test.jassub',
    version: '0.0.1',
    name: 'Test Jassub Subtitles Support',
    description: 'Test Jassub Subtitles Support',
    resources: ['catalog', 'stream', 'subtitles'],
    types: ['series'],
    catalogs: [
        {
            type: 'series',
            id: 'demo',
            name: 'Demo Catalog',
        },
    ],
    idPrefixes: ['tt'],
};

app.get('/manifest.json', (req, res) => {
    res.type('application/json');
    res.json(manifest);
});

const kusuriyaS2Op1Meta = {
    id: 'tt26743760',
    type: 'series',
    name: 'The Apothecary Diaries (S2 OP1)',
    poster: 'https://images.metahub.space/poster/small/tt26743760/img',
    background: 'https://images.metahub.space/background/medium/tt26743760/img',
    logo: 'https://images.metahub.space/logo/medium/tt26743760/img',
};

app.get('/catalog/:type/:id.json', (req, res) => {
    res.json({
        metas: [kusuriyaS2Op1Meta],
    });
});

app.get('/stream/:type/:id.json', (req, res) => {
    if (req.params.id.includes('tt26743760')) {
        return res.json({
            streams: [
                {
                    id: kusuriyaS2Op1Meta.id,
                    title: 'The Apothecary Diaries (S2 OP1)',
                    url: KUSURIYA_S2_OP1_WEBM,
                },
            ],
        });
    }
    return res.status(404).json({ error: 'Stream not found' });
});

app.get('/subtitles/:type/:id.json', (req, res) => {
    if (req.params.id.includes('tt26743760')) {
        return res.json({
            subtitles: [
                {
                    id: kusuriyaS2Op1Meta.id,
                    url: KUSURIYA_S2_OP1_ASS,
                    lang: 'eng',
                },
            ],
        });
    }
    return res.status(404).json({ error: 'Subtitles not found' });
});

app.listen(PORT, HOST, () => {
    process.stdout.write(`Addon: http://${HOST}:${PORT}/manifest.json\n`);
});
  1. Open The Apothecary Diaries (S2 OP1) and select the ASS subtitle track.
  2. Verify:
    • ASS subtitles render correctly
    • changing Delay affects ASS timing
    • changing Size does not affect ASS rendering
    • changing Vertical Position does not affect ASS rendering
    • disabling subtitles does not throw errors
  3. Also verify the existing subtitle flow still works with SRT/VTT:
    • rendering is unchanged
    • Delay, Size, and Vertical Position still work as before
  4. Test switching:
    • ASS -> ASS
    • ASS -> SRT
    • SRT -> ASS
    • ASS -> disabled
    • SRT -> disabled

Notes

This is a conservative patch focused only on adding ASS/SSA support safely while preserving the existing subtitle flow and minimizing unrelated changes.


subtitlesElement.style.display = 'none';
var urls = ensureAssetUrls();
assSubtitlesInstance = new JASSUB({
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing with custom fonts

For the example addon used in this PR, the KUSURIYA_S2_OP1_ASS subtitle may require additional fonts to render as intended.

To validate this case locally, test fonts can be temporarily passed directly into the JASSUB instance through the fonts option:

assSubtitlesInstance = new JASSUB({
    video: videoElement,
    subContent: text,
    workerUrl: urls.workerUrl,
    wasmUrl: urls.wasmUrl,
    modernWasmUrl: urls.wasmModernBinary,
    fonts: [
        'https://raw.githubusercontent.com/ThaUnknown/jassub/gh-pages/static/fonts/FOT-TsukuCOldMinPr6NR.OTF',
        'https://raw.githubusercontent.com/ThaUnknown/jassub/gh-pages/static/fonts/RoughFlowers.TTF'
    ],
    availableFonts: {
        'liberation sans': urls.defaultFontUrl
    },
    defaultFont: 'liberation sans',
    queryFonts: 'local'
});

This is only intended for the KUSURIYA_S2_OP1_ASS example used during testing. It is not part of the proposed production flow.

In a proper production setup, font attachments should ideally come from the media source itself. A possible follow-up would be to use stremio-server to extract MKV font attachments and expose them as URLs that can be passed to JASSUB.

kKaskak

This comment was marked as off-topic.

@kKaskak kKaskak self-requested a review April 28, 2026 11:25
@kKaskak kKaskak added the withHTMLSubtitles HTML subtitles wrapper label Apr 28, 2026
@kKaskak kKaskak modified the milestones: v0.0.78, v0.0.79 Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

withHTMLSubtitles HTML subtitles wrapper

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ASS Subtitle Support .ASS format support

2 participants