From 3720b3f17d4951ab839418b5cbfd1a85eb74adba Mon Sep 17 00:00:00 2001
From: wm4 <wm4@nowhere>
Date: Tue, 24 Dec 2013 17:46:14 +0100
Subject: [PATCH] player: add --secondary-sid for displaying a second subtitle
 stream

This is relatively hacky, but it's Christmas, so it's ok. This does two
things: 1. allow selecting two subtitle tracks, and 2. include a hack
that renders the second subtitle always as toptitle. See manpage
additions how to use this.
---
 DOCS/man/en/input.rst   |   1 +
 DOCS/man/en/options.rst |  22 +++++++
 options/options.c       |   1 +
 options/options.h       |   1 +
 player/command.c        |  49 +++++++++++-----
 player/core.h           |   7 ++-
 player/dvdnav.c         |   4 +-
 player/loadfile.c       |  51 +++++++++++-----
 player/osd.c            |   4 +-
 player/playloop.c       |   6 +-
 player/sub.c            | 125 +++++++++++++++++++++++++---------------
 player/video.c          |   3 +-
 sub/osd.c               |  27 +++++----
 sub/osd.h               |  16 ++---
 sub/osd_libass.c        |   9 ++-
 15 files changed, 220 insertions(+), 106 deletions(-)

diff --git a/DOCS/man/en/input.rst b/DOCS/man/en/input.rst
index aca35177a6..a5de6df18d 100644
--- a/DOCS/man/en/input.rst
+++ b/DOCS/man/en/input.rst
@@ -595,6 +595,7 @@ Name                            W Comment
 ``video-unscaled``              x see ``--video-unscaled``
 ``program``                     x switch TS program (write-only)
 ``sid``                         x current subtitle track (similar to ``--sid``)
+``secondary-sid``               x secondary subtitle track (``--secondary-sid``)
 ``sub``                         x alias for ``sid``
 ``sub-delay``                   x see ``--sub-delay``
 ``sub-pos``                     x see ``--sub-pos``
diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst
index a1f48d32a1..b2f9d30b68 100644
--- a/DOCS/man/en/options.rst
+++ b/DOCS/man/en/options.rst
@@ -2017,6 +2017,28 @@ OPTIONS
     Specify the screen width for video output drivers which do not know the
     screen resolution, like ``x11`` and TV-out.
 
+``--secondary-sid=<ID|auto|no>``
+    Select a secondary subtitle stream. This is similar to ``--sid``. If a
+    secondary subtitle is selected, it will be rendered as toptitle (i.e. on
+    the top of the screen) alongside the normal subtitle, and provides a way
+    to render two subtitles at once.
+
+    there are some caveats associated with this feature. For example, secondary
+    subtitles are never shown on the terminal if video is disabled.
+
+    .. note::
+
+        Styling and interpretation of any formatting tags is disabled for the
+        secondary subtitle. Internally, the same mechanism as ``--no-ass`` is
+        used to strip the styling.
+
+    .. note::
+
+        If the main subtitle stream contains formatting tags which display the
+        subtitle at the top of the screen, it will overlap with the secondary
+        subtitle. To prevent this, you could use ``--no-ass`` to disable
+        styling in the main subtitle stream.
+
 ``--show-profile=<profile>``
     Show the description and content of a profile.
 
diff --git a/options/options.c b/options/options.c
index ea1de3e3f6..5693a34cd0 100644
--- a/options/options.c
+++ b/options/options.c
@@ -301,6 +301,7 @@ const m_option_t mp_opts[] = {
     OPT_TRACKCHOICE("aid", audio_id),
     OPT_TRACKCHOICE("vid", video_id),
     OPT_TRACKCHOICE("sid", sub_id),
+    OPT_TRACKCHOICE("secondary-sid", sub2_id),
     OPT_FLAG_STORE("no-sub", sub_id, 0, -2),
     OPT_FLAG_STORE("no-video", video_id, 0, -2),
     OPT_FLAG_STORE("no-audio", audio_id, 0, -2),
diff --git a/options/options.h b/options/options.h
index 1d997d363b..4889201b93 100644
--- a/options/options.h
+++ b/options/options.h
@@ -147,6 +147,7 @@ typedef struct MPOpts {
     int audio_id;
     int video_id;
     int sub_id;
+    int sub2_id;
     char **audio_lang;
     char **sub_lang;
     int audio_display;
diff --git a/player/command.c b/player/command.c
index 3d15d96676..e59a2d50e8 100644
--- a/player/command.c
+++ b/player/command.c
@@ -980,14 +980,23 @@ static int mp_property_balance(m_option_t *prop, int action, void *arg,
     return M_PROPERTY_NOT_IMPLEMENTED;
 }
 
-static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
-                                int direction, struct track *track)
+static struct track* track_next(struct MPContext *mpctx, int order,
+                                enum stream_type type, int direction,
+                                struct track *track)
 {
     assert(direction == -1 || direction == +1);
     struct track *prev = NULL, *next = NULL;
     bool seen = track == NULL;
     for (int n = 0; n < mpctx->num_tracks; n++) {
         struct track *cur = mpctx->tracks[n];
+        // One track can be selected only one time - pretend already selected
+        // tracks don't exist.
+        for (int r = 0; r < NUM_PTRACKS; r++) {
+            if (r != order && mpctx->current_track[r][type] == cur)
+                cur = NULL;
+        }
+        if (!cur)
+            continue;
         if (cur->type == type) {
             if (cur == track) {
                 seen = true;
@@ -1005,11 +1014,12 @@ static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
 }
 
 static int property_switch_track(m_option_t *prop, int action, void *arg,
-                                 MPContext *mpctx, enum stream_type type)
+                                 MPContext *mpctx, int order,
+                                 enum stream_type type)
 {
     if (!mpctx->num_sources)
         return M_PROPERTY_UNAVAILABLE;
-    struct track *track = mpctx->current_track[0][type];
+    struct track *track = mpctx->current_track[order][type];
 
     switch (action) {
     case M_PROPERTY_GET:
@@ -1034,12 +1044,13 @@ static int property_switch_track(m_option_t *prop, int action, void *arg,
 
     case M_PROPERTY_SWITCH: {
         struct m_property_switch_arg *sarg = arg;
-        mp_switch_track(mpctx, type,
-            track_next(mpctx, type, sarg->inc >= 0 ? +1 : -1, track));
+        mp_switch_track_n(mpctx, order, type,
+            track_next(mpctx, order, type, sarg->inc >= 0 ? +1 : -1, track));
         return M_PROPERTY_OK;
     }
     case M_PROPERTY_SET:
-        mp_switch_track(mpctx, type, mp_track_by_tid(mpctx, type, *(int *)arg));
+        track = mp_track_by_tid(mpctx, type, *(int *)arg);
+        mp_switch_track_n(mpctx, order, type, track);
         return M_PROPERTY_OK;
     }
     return mp_property_generic_option(prop, action, arg, mpctx);
@@ -1102,14 +1113,14 @@ static int property_list_tracks(m_option_t *prop, int action, void *arg,
 static int mp_property_audio(m_option_t *prop, int action, void *arg,
                              MPContext *mpctx)
 {
-    return property_switch_track(prop, action, arg, mpctx, STREAM_AUDIO);
+    return property_switch_track(prop, action, arg, mpctx, 0, STREAM_AUDIO);
 }
 
 /// Selected video id (RW)
 static int mp_property_video(m_option_t *prop, int action, void *arg,
                              MPContext *mpctx)
 {
-    return property_switch_track(prop, action, arg, mpctx, STREAM_VIDEO);
+    return property_switch_track(prop, action, arg, mpctx, 0, STREAM_VIDEO);
 }
 
 static struct track *find_track_by_demuxer_id(MPContext *mpctx,
@@ -1623,7 +1634,13 @@ static int property_osd_helper(m_option_t *prop, int action, void *arg,
 static int mp_property_sub(m_option_t *prop, int action, void *arg,
                            MPContext *mpctx)
 {
-    return property_switch_track(prop, action, arg, mpctx, STREAM_SUB);
+    return property_switch_track(prop, action, arg, mpctx, 0, STREAM_SUB);
+}
+
+static int mp_property_sub2(m_option_t *prop, int action, void *arg,
+                            MPContext *mpctx)
+{
+    return property_switch_track(prop, action, arg, mpctx, 1, STREAM_SUB);
 }
 
 /// Subtitle delay (RW)
@@ -1997,6 +2014,7 @@ static const m_option_t mp_properties[] = {
 
     // Subs
     M_OPTION_PROPERTY_CUSTOM("sid", mp_property_sub),
+    M_OPTION_PROPERTY_CUSTOM("secondary-sid", mp_property_sub2),
     M_OPTION_PROPERTY_CUSTOM("sub-delay", mp_property_sub_delay),
     M_OPTION_PROPERTY_CUSTOM("sub-pos", mp_property_sub_pos),
     M_OPTION_PROPERTY_CUSTOM("sub-visibility", property_osd_helper),
@@ -2109,6 +2127,7 @@ static struct property_osd_display {
     { "angle", "Angle" },
     // subs
     { "sub", "Subtitles" },
+    { "secondary-sid", "Secondary subtitles" },
     { "sub-pos", "Sub position" },
     { "sub-delay", "Sub delay", .osd_id = OSD_MSG_SUB_DELAY },
     { "sub-visibility", "Subtitles" },
@@ -2714,12 +2733,13 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
     }
 
     case MP_CMD_SUB_STEP:
-    case MP_CMD_SUB_SEEK:
-        if (mpctx->osd->dec_sub) {
+    case MP_CMD_SUB_SEEK: {
+        struct osd_object *obj = mpctx->osd->objs[OSDTYPE_SUB];
+        if (obj->dec_sub) {
             double a[2];
-            a[0] = mpctx->video_pts - mpctx->osd->video_offset + opts->sub_delay;
+            a[0] = mpctx->video_pts - obj->video_offset + opts->sub_delay;
             a[1] = cmd->args[0].v.i;
-            if (sub_control(mpctx->osd->dec_sub, SD_CTRL_SUB_STEP, a) > 0) {
+            if (sub_control(obj->dec_sub, SD_CTRL_SUB_STEP, a) > 0) {
                 if (cmd->id == MP_CMD_SUB_STEP) {
                     opts->sub_delay += a[0];
                     osd_changed_all(mpctx->osd);
@@ -2742,6 +2762,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
             }
         }
         break;
+    }
 
     case MP_CMD_OSD: {
         int v = cmd->args[0].v.i;
diff --git a/player/core.h b/player/core.h
index cad18a6b36..c016dcf4f7 100644
--- a/player/core.h
+++ b/player/core.h
@@ -35,6 +35,7 @@
 #define INITIALIZED_ACODEC  1024
 #define INITIALIZED_VCODEC  2048
 #define INITIALIZED_SUB     4096
+#define INITIALIZED_SUB2    8192
 #define INITIALIZED_ALL     0xFFFF
 
 
@@ -208,7 +209,7 @@ typedef struct MPContext {
 
     struct dec_video *d_video;
     struct dec_audio *d_audio;
-    struct dec_sub *d_sub;
+    struct dec_sub *d_sub[2];
 
     // Uses: accessing metadata (consider ordered chapters case, where the main
     // demuxer defines metadata), or special purpose demuxers like TV.
@@ -439,9 +440,9 @@ void handle_force_window(struct MPContext *mpctx, bool reconfig);
 void add_frame_pts(struct MPContext *mpctx, double pts);
 
 // sub.c
-void reset_subtitles(struct MPContext *mpctx);
+void reset_subtitles(struct MPContext *mpctx, int order);
 void uninit_subs(struct demuxer *demuxer);
-void reinit_subs(struct MPContext *mpctx);
+void reinit_subs(struct MPContext *mpctx, int order);
 void update_osd_msg(struct MPContext *mpctx);
 void update_subtitles(struct MPContext *mpctx);
 
diff --git a/player/dvdnav.c b/player/dvdnav.c
index e90a65e035..bc14e7c35f 100644
--- a/player/dvdnav.c
+++ b/player/dvdnav.c
@@ -222,8 +222,8 @@ void mp_nav_get_highlight(struct osd_state *osd, struct mp_osd_res res,
 
     nav->hi_elem = sub;
     int sizes[2] = {0};
-    if (mpctx->d_sub)
-        sub_control(mpctx->d_sub, SD_CTRL_GET_RESOLUTION, sizes);
+    if (mpctx->d_sub[0])
+        sub_control(mpctx->d_sub[0], SD_CTRL_GET_RESOLUTION, sizes);
     if (sizes[0] < 1 || sizes[1] < 1) {
         struct mp_image_params vid = {0};
         if (mpctx->d_video)
diff --git a/player/loadfile.c b/player/loadfile.c
index 8bf29f22f3..99b669f109 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -62,6 +62,16 @@
 #include "stream/dvbin.h"
 #endif
 
+static void uninit_sub(struct MPContext *mpctx, int order)
+{
+    if (mpctx->d_sub[order])
+        sub_reset(mpctx->d_sub[order]);
+    mpctx->d_sub[order] = NULL; // Note: not free'd.
+    mpctx->osd->objs[order ? OSDTYPE_SUB2 : OSDTYPE_SUB]->dec_sub = NULL;
+    reset_subtitles(mpctx, order);
+    reselect_demux_streams(mpctx);
+}
+
 void uninit_player(struct MPContext *mpctx, unsigned int mask)
 {
     struct MPOpts *opts = mpctx->opts;
@@ -80,12 +90,11 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
 
     if (mask & INITIALIZED_SUB) {
         mpctx->initialized_flags &= ~INITIALIZED_SUB;
-        if (mpctx->d_sub)
-            sub_reset(mpctx->d_sub);
-        mpctx->d_sub = NULL; // Note: not free'd.
-        mpctx->osd->dec_sub = NULL;
-        reset_subtitles(mpctx);
-        reselect_demux_streams(mpctx);
+        uninit_sub(mpctx, 0);
+    }
+    if (mask & INITIALIZED_SUB2) {
+        mpctx->initialized_flags &= ~INITIALIZED_SUB2;
+        uninit_sub(mpctx, 1);
     }
 
     if (mask & INITIALIZED_LIBASS) {
@@ -110,7 +119,8 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
     if (mask & INITIALIZED_DEMUXER) {
         mpctx->initialized_flags &= ~INITIALIZED_DEMUXER;
         assert(!(mpctx->initialized_flags &
-                 (INITIALIZED_VCODEC | INITIALIZED_ACODEC | INITIALIZED_SUB)));
+                 (INITIALIZED_VCODEC | INITIALIZED_ACODEC |
+                  INITIALIZED_SUB2 | INITIALIZED_SUB)));
         for (int i = 0; i < mpctx->num_tracks; i++) {
             talloc_free(mpctx->tracks[i]);
         }
@@ -119,7 +129,8 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
             for (int t = 0; t < STREAM_TYPE_COUNT; t++)
                 mpctx->current_track[r][t] = NULL;
         }
-        assert(!mpctx->d_video && !mpctx->d_audio && !mpctx->d_sub);
+        assert(!mpctx->d_video && !mpctx->d_audio &&
+               !mpctx->d_sub[0] && !mpctx->d_sub[1]);
         mpctx->master_demuxer = NULL;
         for (int i = 0; i < mpctx->num_sources; i++) {
             uninit_subs(mpctx->sources[i]);
@@ -324,7 +335,7 @@ bool timeline_set_part(struct MPContext *mpctx, int i, bool force)
     enum stop_play_reason orig_stop_play = mpctx->stop_play;
     if (!mpctx->d_video && mpctx->stop_play == KEEP_PLAYING)
         mpctx->stop_play = AT_END_OF_FILE;  // let audio uninit drain data
-    uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts->fixed_vo ? 0 : INITIALIZED_VO) | (mpctx->opts->gapless_audio ? 0 : INITIALIZED_AO) | INITIALIZED_ACODEC | INITIALIZED_SUB);
+    uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts->fixed_vo ? 0 : INITIALIZED_VO) | (mpctx->opts->gapless_audio ? 0 : INITIALIZED_AO) | INITIALIZED_ACODEC | INITIALIZED_SUB | INITIALIZED_SUB2);
     mpctx->stop_play = orig_stop_play;
 
     mpctx->demuxer = n->source;
@@ -405,8 +416,10 @@ static struct track *add_stream_track(struct MPContext *mpctx,
             track->demuxer_id = stream->demuxer_id;
             // Initialize lazily selected track
             demuxer_select_track(track->demuxer, stream, track->selected);
-            if (track->selected)
-                reinit_subs(mpctx);
+            if (mpctx->current_track[0][STREAM_SUB] == track)
+                reinit_subs(mpctx, 0);
+            if (mpctx->current_track[1][STREAM_SUB] == track)
+                reinit_subs(mpctx, 1);
             return track;
         }
     }
@@ -605,6 +618,9 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
         } else if (type == STREAM_SUB) {
             uninit_player(mpctx, INITIALIZED_SUB);
         }
+    } else if (order == 1) {
+        if (type == STREAM_SUB)
+            uninit_player(mpctx, INITIALIZED_SUB2);
     }
 
     if (current)
@@ -631,9 +647,14 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
             mp_notify_property(mpctx, "aid");
         } else if (type == STREAM_SUB) {
             mpctx->opts->sub_id = user_tid;
-            reinit_subs(mpctx);
+            reinit_subs(mpctx, 0);
             mp_notify_property(mpctx, "sid");
         }
+    } else if (order == 1) {
+        if (type == STREAM_SUB) {
+            mpctx->opts->sub2_id = user_tid;
+            reinit_subs(mpctx, 1);
+        }
     }
 
     talloc_free(mpctx->track_layout_hash);
@@ -1048,7 +1069,8 @@ static void play_current_file(struct MPContext *mpctx)
     assert(mpctx->demuxer == NULL);
     assert(mpctx->d_audio == NULL);
     assert(mpctx->d_video == NULL);
-    assert(mpctx->d_sub == NULL);
+    assert(mpctx->d_sub[0] == NULL);
+    assert(mpctx->d_sub[1] == NULL);
 
     char *stream_filename = mpctx->filename;
     mpctx->resolve_result = resolve_url(stream_filename, mpctx->global);
@@ -1189,7 +1211,8 @@ goto_reopen_demuxer: ;
 
     reinit_video_chain(mpctx);
     reinit_audio_chain(mpctx);
-    reinit_subs(mpctx);
+    reinit_subs(mpctx, 0);
+    reinit_subs(mpctx, 1);
 
     //==================== START PLAYING =======================
 
diff --git a/player/osd.c b/player/osd.c
index 607af6714e..d1af1a6e65 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -373,8 +373,8 @@ void set_osd_subtitle(struct MPContext *mpctx, const char *text)
 {
     if (!text)
         text = "";
-    if (strcmp(mpctx->osd->sub_text, text) != 0) {
-        osd_set_sub(mpctx->osd, text);
+    if (strcmp(mpctx->osd->objs[OSDTYPE_SUB]->sub_text, text) != 0) {
+        osd_set_sub(mpctx->osd, mpctx->osd->objs[OSDTYPE_SUB], text);
         if (!mpctx->video_out) {
             rm_osd_msg(mpctx, OSD_MSG_SUB_BASE);
             if (text && text[0])
diff --git a/player/playloop.c b/player/playloop.c
index dcbc1de9ba..6359803fcf 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -181,7 +181,8 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao)
             clear_audio_output_buffers(mpctx);
     }
 
-    reset_subtitles(mpctx);
+    reset_subtitles(mpctx, 0);
+    reset_subtitles(mpctx, 1);
 
     mpctx->video_pts = MP_NOPTS_VALUE;
     mpctx->video_next_pts = MP_NOPTS_VALUE;
@@ -271,7 +272,8 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
     if (need_reset) {
         reinit_video_chain(mpctx);
         reinit_audio_chain(mpctx);
-        reinit_subs(mpctx);
+        reinit_subs(mpctx, 0);
+        reinit_subs(mpctx, 1);
     }
 
     int demuxer_style = 0;
diff --git a/player/sub.c b/player/sub.c
index 3593bab710..cc8f89a531 100644
--- a/player/sub.c
+++ b/player/sub.c
@@ -66,23 +66,32 @@ static bool is_interleaved(struct MPContext *mpctx, struct track *track)
     return false;
 }
 
-void reset_subtitles(struct MPContext *mpctx)
+void reset_subtitles(struct MPContext *mpctx, int order)
 {
-    if (mpctx->d_sub)
-        sub_reset(mpctx->d_sub);
+    struct osd_object *osd_obj =
+        mpctx->osd->objs[order ? OSDTYPE_SUB2 : OSDTYPE_SUB];
+    if (mpctx->d_sub[order])
+        sub_reset(mpctx->d_sub[order]);
     set_osd_subtitle(mpctx, NULL);
-    osd_changed(mpctx->osd, OSDTYPE_SUB);
+    osd_set_sub(mpctx->osd, osd_obj, NULL);
 }
 
-void update_subtitles(struct MPContext *mpctx)
+static void update_subtitle(struct MPContext *mpctx, int order)
 {
     struct MPOpts *opts = mpctx->opts;
-    if (!(mpctx->initialized_flags & INITIALIZED_SUB))
-        return;
+    if (order == 0) {
+        if (!(mpctx->initialized_flags & INITIALIZED_SUB))
+            return;
+    } else {
+        if (!(mpctx->initialized_flags & INITIALIZED_SUB2))
+            return;
+    }
 
-    struct track *track = mpctx->current_track[0][STREAM_SUB];
-    struct dec_sub *dec_sub = mpctx->d_sub;
+    struct track *track = mpctx->current_track[order][STREAM_SUB];
+    struct dec_sub *dec_sub = mpctx->d_sub[order];
     assert(track && dec_sub);
+    struct osd_object *osd_obj
+        = mpctx->osd->objs[order ? OSDTYPE_SUB2 : OSDTYPE_SUB];
 
     if (mpctx->d_video) {
         struct mp_image_params params = mpctx->d_video->vf_input;
@@ -90,9 +99,9 @@ void update_subtitles(struct MPContext *mpctx)
             sub_control(dec_sub, SD_CTRL_SET_VIDEO_PARAMS, &params);
     }
 
-    mpctx->osd->video_offset = track->under_timeline ? mpctx->video_offset : 0;
+    osd_obj->video_offset = track->under_timeline ? mpctx->video_offset : 0;
 
-    double refpts_s = mpctx->playback_pts - mpctx->osd->video_offset;
+    double refpts_s = mpctx->playback_pts - osd_obj->video_offset;
     double curpts_s = refpts_s + opts->sub_delay;
 
     if (!track->preloaded && track->stream) {
@@ -125,8 +134,19 @@ void update_subtitles(struct MPContext *mpctx)
         }
     }
 
-    if (!mpctx->osd->render_bitmap_subs || !mpctx->video_out)
-        set_osd_subtitle(mpctx, sub_get_text(dec_sub, curpts_s));
+    // Handle displaying subtitles on terminal; never done for secondary subs
+    if (order == 0) {
+        if (!osd_obj->render_bitmap_subs || !mpctx->video_out)
+            set_osd_subtitle(mpctx, sub_get_text(dec_sub, curpts_s));
+    } else if (order == 1) {
+        osd_set_sub(mpctx->osd, osd_obj, sub_get_text(dec_sub, curpts_s));
+    }
+}
+
+void update_subtitles(struct MPContext *mpctx)
+{
+    update_subtitle(mpctx, 0);
+    update_subtitle(mpctx, 1);
 }
 
 static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st,
@@ -169,12 +189,42 @@ static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st
     talloc_free(s);
 }
 
-void reinit_subs(struct MPContext *mpctx)
+static void reinit_subdec(struct MPContext *mpctx, struct track *track,
+                          struct dec_sub *dec_sub)
+{
+    if (sub_is_initialized(dec_sub))
+        return;
+
+    struct sh_video *sh_video =
+        mpctx->d_video ? mpctx->d_video->header->video : NULL;
+    int w = sh_video ? sh_video->disp_w : 0;
+    int h = sh_video ? sh_video->disp_h : 0;
+    float fps = sh_video ? sh_video->fps : 25;
+
+    set_dvdsub_fake_extradata(dec_sub, track->demuxer->stream, w, h);
+    sub_set_video_res(dec_sub, w, h);
+    sub_set_video_fps(dec_sub, fps);
+    sub_set_ass_renderer(dec_sub, mpctx->ass_library, mpctx->ass_renderer);
+    sub_init_from_sh(dec_sub, track->stream);
+
+    // Don't do this if the file has video/audio streams. Don't do it even
+    // if it has only sub streams, because reading packets will change the
+    // demuxer position.
+    if (!track->preloaded && track->is_external) {
+        demux_seek(track->demuxer, 0, SEEK_ABSOLUTE);
+        track->preloaded = sub_read_all_packets(dec_sub, track->stream);
+    }
+}
+
+void reinit_subs(struct MPContext *mpctx, int order)
 {
     struct MPOpts *opts = mpctx->opts;
-    struct track *track = mpctx->current_track[0][STREAM_SUB];
+    struct track *track = mpctx->current_track[order][STREAM_SUB];
+    struct osd_object *osd_obj =
+        mpctx->osd->objs[order ? OSDTYPE_SUB2 : OSDTYPE_SUB];
+    int init_flag = order ? INITIALIZED_SUB2 : INITIALIZED_SUB;
 
-    assert(!(mpctx->initialized_flags & INITIALIZED_SUB));
+    assert(!(mpctx->initialized_flags & init_flag));
 
     struct sh_stream *sh = init_demux_stream(mpctx, track);
 
@@ -184,48 +234,33 @@ void reinit_subs(struct MPContext *mpctx)
         return;
 
     if (!sh->sub->dec_sub) {
-        assert(!mpctx->d_sub);
+        assert(!mpctx->d_sub[order]);
         sh->sub->dec_sub = sub_create(mpctx->global);
     }
 
-    assert(!mpctx->d_sub || sh->sub->dec_sub == mpctx->d_sub);
+    assert(!mpctx->d_sub[order] || sh->sub->dec_sub == mpctx->d_sub[order]);
 
     // The decoder is kept in the stream header in order to make ordered
     // chapters work well.
-    mpctx->d_sub = sh->sub->dec_sub;
+    mpctx->d_sub[order] = sh->sub->dec_sub;
 
-    mpctx->initialized_flags |= INITIALIZED_SUB;
+    mpctx->initialized_flags |= init_flag;
 
-    struct dec_sub *dec_sub = mpctx->d_sub;
+    struct dec_sub *dec_sub = mpctx->d_sub[order];
     assert(dec_sub);
 
-    if (!sub_is_initialized(dec_sub)) {
-        struct sh_video *sh_video =
-            mpctx->d_video ? mpctx->d_video->header->video : NULL;
-        int w = sh_video ? sh_video->disp_w : 0;
-        int h = sh_video ? sh_video->disp_h : 0;
-        float fps = sh_video ? sh_video->fps : 25;
+    reinit_subdec(mpctx, track, dec_sub);
 
-        set_dvdsub_fake_extradata(dec_sub, track->demuxer->stream, w, h);
-        sub_set_video_res(dec_sub, w, h);
-        sub_set_video_fps(dec_sub, fps);
-        sub_set_ass_renderer(dec_sub, mpctx->ass_library, mpctx->ass_renderer);
-        sub_init_from_sh(dec_sub, sh);
-
-        // Don't do this if the file has video/audio streams. Don't do it even
-        // if it has only sub streams, because reading packets will change the
-        // demuxer position.
-        if (!track->preloaded && track->is_external) {
-            demux_seek(track->demuxer, 0, SEEK_ABSOLUTE);
-            track->preloaded = sub_read_all_packets(dec_sub, sh);
-        }
-    }
-
-    mpctx->osd->dec_sub = dec_sub;
+    osd_obj->dec_sub = dec_sub;
 
     // Decides whether to use OSD path or normal subtitle rendering path.
-    mpctx->osd->render_bitmap_subs =
+    osd_obj->render_bitmap_subs =
         opts->ass_enabled || !sub_has_get_text(dec_sub);
 
-    reset_subtitles(mpctx);
+    // Secondary subs are rendered with the "text" renderer to transform them
+    // to toptitles.
+    if (order == 1 && sub_has_get_text(dec_sub))
+        osd_obj->render_bitmap_subs = false;
+
+    reset_subtitles(mpctx, order);
 }
diff --git a/player/video.c b/player/video.c
index 9df3caa940..039248c1d0 100644
--- a/player/video.c
+++ b/player/video.c
@@ -199,7 +199,8 @@ int reinit_video_chain(struct MPContext *mpctx)
     mpctx->vo_pts_history_seek_ts++;
 
     vo_seek_reset(mpctx->video_out);
-    reset_subtitles(mpctx);
+    reset_subtitles(mpctx, 0);
+    reset_subtitles(mpctx, 1);
 
     if (opts->force_fps) {
         d_video->fps = opts->force_fps;
diff --git a/sub/osd.c b/sub/osd.c
index 72ae5db841..107ca232cf 100644
--- a/sub/osd.c
+++ b/sub/osd.c
@@ -88,21 +88,22 @@ struct osd_state *osd_create(struct mpv_global *global)
         .global = global,
         .log = mp_log_new(osd, global->log, "osd"),
         .osd_text = talloc_strdup(osd, ""),
-        .sub_text = talloc_strdup(osd, ""),
         .progbar_type = -1,
     };
 
     for (int n = 0; n < MAX_OSD_PARTS; n++) {
-        struct osd_object *obj = talloc_struct(osd, struct osd_object, {
+        struct osd_object *obj = talloc(osd, struct osd_object);
+        *obj = (struct osd_object) {
             .type = n,
-        });
+            .sub_text = talloc_strdup(obj, ""),
+        };
         for (int i = 0; i < OSD_CONV_CACHE_MAX; i++)
             obj->cache[i] = talloc_steal(obj, osd_conv_cache_new());
         osd->objs[n] = obj;
     }
 
-    osd->objs[OSDTYPE_SUB]->is_sub = true;      // dec_sub.c
-    osd->objs[OSDTYPE_SUBTEXT]->is_sub = true;  // osd_libass.c
+    osd->objs[OSDTYPE_SUB]->is_sub = true;
+    osd->objs[OSDTYPE_SUB2]->is_sub = true;
 
     osd_init_backend(osd);
     return osd;
@@ -133,10 +134,10 @@ void osd_set_text(struct osd_state *osd, const char *text)
         osd_changed(osd, OSDTYPE_OSD);
 }
 
-void osd_set_sub(struct osd_state *osd, const char *text)
+void osd_set_sub(struct osd_state *osd, struct osd_object *obj, const char *text)
 {
-    if (!set_text(osd, &osd->sub_text, text))
-        osd_changed(osd, OSDTYPE_SUBTEXT);
+    if (!set_text(obj, &obj->sub_text, text))
+        osd_changed(osd, obj->type);
 }
 
 static void render_object(struct osd_state *osd, struct osd_object *obj,
@@ -157,12 +158,14 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
         obj->force_redraw = true;
     obj->vo_res = res;
 
-    if (obj->type == OSDTYPE_SUB) {
-        if (osd->render_bitmap_subs && osd->dec_sub) {
+    if (obj->type == OSDTYPE_SUB || obj->type == OSDTYPE_SUB2) {
+        if (obj->render_bitmap_subs && obj->dec_sub) {
             double sub_pts = video_pts;
             if (sub_pts != MP_NOPTS_VALUE)
-                sub_pts -= osd->video_offset - opts->sub_delay;
-            sub_get_bitmaps(osd->dec_sub, obj->vo_res, sub_pts, out_imgs);
+                sub_pts -= obj->video_offset - opts->sub_delay;
+            sub_get_bitmaps(obj->dec_sub, obj->vo_res, sub_pts, out_imgs);
+        } else {
+            osd_object_get_bitmaps(osd, obj, out_imgs);
         }
     } else if (obj->type == OSDTYPE_EXTERNAL2) {
         if (osd->external2.format) {
diff --git a/sub/osd.h b/sub/osd.h
index 9714a06c18..17e8a02c08 100644
--- a/sub/osd.h
+++ b/sub/osd.h
@@ -84,7 +84,7 @@ struct mp_osd_res {
 
 enum mp_osdtype {
     OSDTYPE_SUB,
-    OSDTYPE_SUBTEXT,
+    OSDTYPE_SUB2,
 
     OSDTYPE_NAV_HIGHLIGHT,      // dvdnav fake highlights
 
@@ -105,6 +105,12 @@ struct osd_object {
 
     bool force_redraw;
 
+    // OSDTYPE_SUB
+    struct dec_sub *dec_sub;
+    double video_offset;
+    bool render_bitmap_subs;
+    char *sub_text;
+
     // caches for OSD conversion (internal to render_object())
     struct osd_conv_cache *cache[OSD_CONV_CACHE_MAX];
     struct sub_bitmaps cached;
@@ -124,11 +130,9 @@ struct osd_object {
 struct osd_state {
     struct osd_object *objs[MAX_OSD_PARTS];
 
-    double video_offset;
     double vo_pts;
 
     bool render_subs_in_filter;
-    bool render_bitmap_subs;
 
     struct mp_osd_res last_vo_res;
 
@@ -136,8 +140,6 @@ struct osd_state {
 
     // OSDTYPE_OSD
     char *osd_text;
-    // OSDTYPE_SUBTEXT
-    char *sub_text;
     // OSDTYPE_PROGBAR
     int progbar_type;      // <0: disabled, 1-255: symbol, else: no symbol
     float progbar_value;   // range 0.0-1.0
@@ -148,8 +150,6 @@ struct osd_state {
     int external_res_x, external_res_y;
     // OSDTYPE_EXTERNAL2
     struct sub_bitmaps external2;
-    // OSDTYPE_SUB
-    struct dec_sub *dec_sub;
     // OSDTYPE_NAV_HIGHLIGHT
     void *highlight_priv;
 
@@ -206,7 +206,7 @@ extern const struct m_sub_options osd_style_conf;
 
 struct osd_state *osd_create(struct mpv_global *global);
 void osd_set_text(struct osd_state *osd, const char *text);
-void osd_set_sub(struct osd_state *osd, const char *text);
+void osd_set_sub(struct osd_state *osd, struct osd_object *obj, const char *text);
 void osd_changed(struct osd_state *osd, int new_value);
 void osd_changed_all(struct osd_state *osd);
 void osd_free(struct osd_state *osd);
diff --git a/sub/osd_libass.c b/sub/osd_libass.c
index 48083bc71f..e43408829b 100644
--- a/sub/osd_libass.c
+++ b/sub/osd_libass.c
@@ -411,7 +411,7 @@ static void update_sub(struct osd_state *osd, struct osd_object *obj)
 
     clear_obj(obj);
 
-    if (!osd->sub_text || !osd->sub_text[0])
+    if (!obj->sub_text || !obj->sub_text[0] || obj->render_bitmap_subs)
         return;
 
     create_ass_renderer(osd, obj);
@@ -423,12 +423,14 @@ static void update_sub(struct osd_state *osd, struct osd_object *obj)
 
     ASS_Style *style = obj->osd_track->styles + obj->osd_track->default_style;
     mp_ass_set_style(style, obj->osd_track->PlayResY, &font);
+    if (obj->type == OSDTYPE_SUB2)
+        style->Alignment = 6;
 
 #if LIBASS_VERSION >= 0x01010000
     ass_set_line_position(obj->osd_render, 100 - opts->sub_pos);
 #endif
 
-    char *escaped_text = mangle_ass(osd->sub_text);
+    char *escaped_text = mangle_ass(obj->sub_text);
     add_osd_ass_event(obj->osd_track, escaped_text);
     talloc_free(escaped_text);
 }
@@ -439,7 +441,8 @@ static void update_object(struct osd_state *osd, struct osd_object *obj)
     case OSDTYPE_OSD:
         update_osd(osd, obj);
         break;
-    case OSDTYPE_SUBTEXT:
+    case OSDTYPE_SUB:
+    case OSDTYPE_SUB2:
         update_sub(osd, obj);
         break;
     case OSDTYPE_PROGBAR: