diff --git a/docs/source/options/dash_stream_descriptors.rst b/docs/source/options/dash_stream_descriptors.rst index a6a6a101aed..c5c646899b5 100644 --- a/docs/source/options/dash_stream_descriptors.rst +++ b/docs/source/options/dash_stream_descriptors.rst @@ -12,5 +12,5 @@ DASH specific stream descriptor fields Optional semicolon separated list of values for DASH Role element. The value should be one of: **caption**, **subtitle**, **main**, **alternate**, - **supplementary**, **commentary**, **description** and **dub**. See - DASH (ISO/IEC 23009-1) specification for details. + **supplementary**, **commentary**, **description**, **dub** and **forced-subtitle** . + See DASH (ISO/IEC 23009-1) specification for details. diff --git a/docs/source/options/stream_descriptors.rst b/docs/source/options/stream_descriptors.rst index 7b5aaf16667..82e8037735b 100644 --- a/docs/source/options/stream_descriptors.rst +++ b/docs/source/options/stream_descriptors.rst @@ -73,6 +73,15 @@ These are the available fields: CEA allows specifying up to 4 streams within a single video stream. If not specified, all subtitles will be merged together. +:forced_subtitle: + + Optional boolean value (0|1). If set to 1 indicates that this stream is a + Forced Narrative subtitle that should be displayed when subtitles are otherwise + off, for example used to caption short portions of the audio that might be in + a foreign language. For DASH this will set role to **forced_subtitle**, for HLS + it will set FORCED=YES and AUTOSELECT=YES. Only valid for subtitles. + + .. include:: /options/drm_stream_descriptors.rst .. include:: /options/dash_stream_descriptors.rst .. include:: /options/hls_stream_descriptors.rst diff --git a/include/packager/packager.h b/include/packager/packager.h index 3c32abe9c70..76917a4120e 100644 --- a/include/packager/packager.h +++ b/include/packager/packager.h @@ -152,6 +152,9 @@ struct StreamDescriptor { /// Set to true to indicate that the stream is for hls only. bool hls_only = false; + /// Optional, indicates if this is a Forced Narrative subtitle stream. + bool forced_subtitle = false; + /// Optional for DASH output. It defines the Label element in Adaptation Set. std::string dash_label; }; diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 5c45c32dc8f..7f5186c59d9 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -120,8 +120,16 @@ const char kUsage[] = " in the format: scheme_id_uri=value.\n" " - dash_roles (roles): Optional semicolon separated list of values for\n" " DASH Role elements. The value should be one of: caption, subtitle,\n" - " main, alternate, supplementary, commentary, description and dub. See\n" - " DASH (ISO/IEC 23009-1) specification for details.\n"; + " forced-subtitle, main, alternate, supplementary, commentary, \n" + " description and dub. See DASH\n" + " (ISO/IEC 23009-1) specification for details.\n" + " - forced_subtitle: Optional boolean value (0|1). If set to 1 \n" + " indicates that this stream is a Forced Narrative subtitle that \n" + " should be displayed when subtitles are otherwise off, for example \n" + " used to caption short portions of the audio that might be in a \n" + " foreign language. For DASH this will set role to forced_subtitle, \n" + " for HLS it will set FORCED=YES and AUTOSELECT=YES. \n" + " Only valid for subtitles."; // Labels for parameters in RawKey key info. const char kDrmLabelLabel[] = "label"; diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index 9326d2face8..b49d2dfc8f6 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -40,6 +40,7 @@ enum FieldType { kDashOnlyField, kHlsOnlyField, kDashLabelField, + kForcedSubtitleField, }; struct FieldNameToTypeMapping { @@ -88,6 +89,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = { {"dash_only", kDashOnlyField}, {"hls_only", kHlsOnlyField}, {"dash_label", kDashLabelField}, + {"forced_subtitle", kForcedSubtitleField}, }; FieldType GetFieldType(const std::string& field_name) { @@ -255,12 +257,35 @@ std::optional ParseStreamDescriptor( case kDashLabelField: descriptor.dash_label = pair.second; break; + case kForcedSubtitleField: + unsigned forced_subtitle_value; + if (!absl::SimpleAtoi(pair.second, &forced_subtitle_value)) { + LOG(ERROR) << "Non-numeric option for forced field " + "specified (" + << pair.second << ")."; + return std::nullopt; + } + if (forced_subtitle_value > 1) { + LOG(ERROR) << "forced should be either 0 or 1."; + return std::nullopt; + } + descriptor.forced_subtitle = forced_subtitle_value > 0; + break; default: LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first << "\")."; return std::nullopt; } } + + if (descriptor.forced_subtitle) { + auto itr = std::find(descriptor.dash_roles.begin(), + descriptor.dash_roles.end(), "forced-subtitle"); + if (itr == descriptor.dash_roles.end()) { + descriptor.dash_roles.push_back("forced-subtitle"); + } + } + return descriptor; } diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index da9f5e24e6b..032473e9703 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -310,7 +310,8 @@ def _GetStream(self, skip_encryption=None, bandwidth=None, split_content_on_ad_cues=False, - test_file=None): + test_file=None, + forced_subtitle=None): """Get a stream descriptor as a string. @@ -347,8 +348,9 @@ def _GetStream(self, into multiple files, with a total of NumAdCues + 1 files. test_file: The input file to use. If the input file is not specified, a default file will be used. - - + forced_subtitle: If set to true, it marks this as a Forced Narrative + subtitle, marked in DASH using forced-subtitle role and + in HLS using FORCED=YES. Returns: A string that makes up a single stream descriptor for input to the packager. @@ -402,6 +404,9 @@ def _GetStream(self, if dash_only: stream.Append('dash_only', 1) + if forced_subtitle: + stream.Append('forced_subtitle', 1) + if dash_label: stream.Append('dash_label', dash_label) @@ -799,6 +804,21 @@ def testDashLabel(self): self.assertPackageSuccess(streams, self._GetFlags(output_dash=True)) self._CheckTestResults('dash-label') + def testForcedSubtitle(self): + streams = [ + self._GetStream('audio', hls=True), + self._GetStream('video', hls=True), + ] + + streams += self._GetStreams( + ['text'], + test_files=['bear-english.vtt'], + forced_subtitle=True) + + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + output_hls=True)) + self._CheckTestResults('forced-subtitle') + def testAudioVideoWithLanguageOverride(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video'], language='por', hls=True), diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.m3u8 b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.m3u8 new file mode 100644 index 00000000000..4722e68aa9d --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="804@0" +#EXTINF:1.022, +#EXT-X-BYTERANGE:17028@872 +bear-640x360-audio.mp4 +#EXTINF:0.998, +#EXT-X-BYTERANGE:16285 +bear-640x360-audio.mp4 +#EXTINF:0.720, +#EXT-X-BYTERANGE:9558 +bear-640x360-audio.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.mp4 b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.mp4 new file mode 100644 index 00000000000..10077f9af8a Binary files /dev/null and b/packager/app/test/testdata/forced-subtitle/bear-640x360-audio.mp4 differ diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-video-iframe.m3u8 b/packager/app/test/testdata/forced-subtitle/bear-640x360-video-iframe.m3u8 new file mode 100644 index 00000000000..6642032a393 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-640x360-video-iframe.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-I-FRAMES-ONLY +#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:15581@938 +bear-640x360-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:18221@100251 +bear-640x360-video.mp4 +#EXTINF:0.734, +#EXT-X-BYTERANGE:19663@222058 +bear-640x360-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-video.m3u8 b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.m3u8 new file mode 100644 index 00000000000..b687af701eb --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0" +#EXTINF:1.001, +#EXT-X-BYTERANGE:99313@938 +bear-640x360-video.mp4 +#EXTINF:1.001, +#EXT-X-BYTERANGE:121807 +bear-640x360-video.mp4 +#EXTINF:0.734, +#EXT-X-BYTERANGE:79662 +bear-640x360-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/forced-subtitle/bear-640x360-video.mp4 b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.mp4 new file mode 100644 index 00000000000..de83807979b Binary files /dev/null and b/packager/app/test/testdata/forced-subtitle/bear-640x360-video.mp4 differ diff --git a/packager/app/test/testdata/forced-subtitle/bear-english-text.vtt b/packager/app/test/testdata/forced-subtitle/bear-english-text.vtt new file mode 100644 index 00000000000..18ae752fe8e --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/bear-english-text.vtt @@ -0,0 +1,11 @@ +WEBVTT + +STYLE +::cue { color:lime } + +00:00:00.000 --> 00:00:00.800 align:center +Yup, that's a bear, eh. + +00:00:01.000 --> 00:00:04.700 align:center +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/forced-subtitle/output.m3u8 b/packager/app/test/testdata/forced-subtitle/output.m3u8 new file mode 100644 index 00000000000..183b69e84b7 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/output.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2" + +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_2.m3u8",GROUP-ID="default-text-group",NAME="stream_2",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES + +#EXT-X-STREAM-INF:BANDWIDTH=1106817,AVERAGE-BANDWIDTH=1004632,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group",CLOSED-CAPTIONS=NONE +bear-640x360-video.m3u8 + +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=214292,AVERAGE-BANDWIDTH=156327,CODECS="avc1.64001e",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,URI="bear-640x360-video-iframe.m3u8" diff --git a/packager/app/test/testdata/forced-subtitle/output.mpd b/packager/app/test/testdata/forced-subtitle/output.mpd new file mode 100644 index 00000000000..28ccba2d4e7 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/output.mpd @@ -0,0 +1,29 @@ + + + + + + + + bear-640x360-audio.mp4 + + + + + + + + bear-640x360-video.mp4 + + + + + + + + + bear-english-text.vtt + + + + diff --git a/packager/app/test/testdata/forced-subtitle/stream_2.m3u8 b/packager/app/test/testdata/forced-subtitle/stream_2.m3u8 new file mode 100644 index 00000000000..1ed204e2746 --- /dev/null +++ b/packager/app/test/testdata/forced-subtitle/stream_2.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:5 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:4.700, +bear-english-text.vtt +#EXT-X-ENDLIST diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index e6f8f650b2d..0b5c22f9350 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -318,11 +318,16 @@ void BuildMediaTag(const MediaPlaylist& playlist, } else { tag.AddString("DEFAULT", "NO"); } - if (is_autoselect) { tag.AddString("AUTOSELECT", "YES"); } + if (playlist.stream_type() == + MediaPlaylist::MediaPlaylistStreamType::kSubtitle && + playlist.forced_subtitle()) { + tag.AddString("FORCED", "YES"); + } + const std::vector& characteristics = playlist.characteristics(); if (!characteristics.empty()) { tag.AddQuotedString("CHARACTERISTICS", absl::StrJoin(characteristics, ",")); @@ -401,6 +406,12 @@ void BuildMediaTags( } } + if (playlist->stream_type() == + MediaPlaylist::MediaPlaylistStreamType::kSubtitle && + playlist->forced_subtitle()) { + is_autoselect = true; + } + BuildMediaTag(*playlist, group_id, is_default, is_autoselect, base_url, out); } diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 1b129c2555f..9c1366ed4c2 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -373,6 +373,10 @@ void MediaPlaylist::SetCharacteristicsForTesting( characteristics_ = characteristics; } +void MediaPlaylist::SetForcedSubtitleForTesting(const bool forced_subtitle) { + forced_subtitle_ = forced_subtitle; +} + bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { const int32_t time_scale = GetTimeScale(media_info); if (time_scale == 0) { @@ -400,6 +404,8 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { std::vector(media_info_.hls_characteristics().begin(), media_info_.hls_characteristics().end()); + forced_subtitle_ = media_info_.forced_subtitle(); + return true; } diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index ac64efe9b97..b2a97c10698 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -90,6 +90,9 @@ class MediaPlaylist { /// For testing only. void SetLanguageForTesting(const std::string& language); + /// For testing only. + void SetForcedSubtitleForTesting(const bool forced_subtitle); + /// For testing only. void SetCharacteristicsForTesting( const std::vector& characteristics); @@ -223,6 +226,8 @@ class MediaPlaylist { return characteristics_; } + bool forced_subtitle() const { return forced_subtitle_; } + bool is_dvs() const { // HLS Authoring Specification for Apple Devices // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices#overview @@ -262,6 +267,7 @@ class MediaPlaylist { std::string codec_; std::string language_; std::vector characteristics_; + bool forced_subtitle_ = false; uint32_t media_sequence_number_ = 0; bool inserted_discontinuity_tag_ = false; int discontinuity_sequence_number_ = 0; diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index e7bcc49edf3..c0f71267a3c 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -26,6 +26,7 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener( const std::string& ext_x_media_name, const std::string& ext_x_media_group_id, const std::vector& characteristics, + bool forced_subtitle, hls::HlsNotifier* hls_notifier, std::optional index) : playlist_name_(playlist_name), @@ -33,6 +34,7 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener( ext_x_media_name_(ext_x_media_name), ext_x_media_group_id_(ext_x_media_group_id), characteristics_(characteristics), + forced_subtitle_(forced_subtitle), hls_notifier_(hls_notifier), index_(index) { DCHECK(hls_notifier); @@ -103,6 +105,9 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options, for (const std::string& characteristic : characteristics_) media_info->add_hls_characteristics(characteristic); } + if (forced_subtitle_) { + media_info->set_forced_subtitle(forced_subtitle_); + } if (index_.has_value()) media_info->set_index(index_.value()); diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index 3aa0f3151f3..9d0f58c0b3b 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -39,12 +39,16 @@ class HlsNotifyMuxerListener : public MuxerListener { /// @param characteristics is the characteristics for this playlist. This is /// the value of CHARACTERISTICS attribute for EXT-X-MEDIA. This may be /// empty. + /// @param forced is the HLS FORCED SUBTITLE setting for this playlist. This + /// is the value of FORCED attribute for EXT-X-MEDIA. This may be + /// empty. /// @param hls_notifier used by this listener. Ownership does not transfer. HlsNotifyMuxerListener(const std::string& playlist_name, bool iframes_only, const std::string& ext_x_media_name, const std::string& ext_x_media_group_id, const std::vector& characteristics, + bool forced, hls::HlsNotifier* hls_notifier, std::optional index); ~HlsNotifyMuxerListener() override; @@ -86,6 +90,7 @@ class HlsNotifyMuxerListener : public MuxerListener { const std::string ext_x_media_name_; const std::string ext_x_media_group_id_; const std::vector characteristics_; + const bool forced_subtitle_; hls::HlsNotifier* const hls_notifier_; std::optional stream_id_; std::optional index_; diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index 9c1f4296c20..4d9650a8e9e 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -99,6 +99,7 @@ const char kDefaultName[] = "DEFAULTNAME"; const char kDefaultGroupId[] = "DEFAULTGROUPID"; const char kCharactersticA[] = "public.accessibility.transcribes-spoken-dialog"; const char kCharactersticB[] = "public.easy-to-read"; +const bool kForced = false; MATCHER_P(HasEncryptionScheme, expected_scheme, "") { *result_listener << "it has_protected_content: " @@ -121,6 +122,7 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test { kDefaultName, kDefaultGroupId, std::vector{kCharactersticA, kCharactersticB}, + kForced, &mock_notifier_, 0) {} @@ -459,6 +461,7 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam { kDefaultName, kDefaultGroupId, std::vector(), // no characteristics. + kForced, &mock_notifier_, 0) {} diff --git a/packager/media/event/muxer_listener_factory.cc b/packager/media/event/muxer_listener_factory.cc index 3578a08b235..896f01c0dd6 100644 --- a/packager/media/event/muxer_listener_factory.cc +++ b/packager/media/event/muxer_listener_factory.cc @@ -62,6 +62,7 @@ std::list> CreateHlsListenersInternal( const std::string& group_id = stream.hls_group_id; const std::string& iframe_playlist_name = stream.hls_iframe_playlist_name; const std::vector& characteristics = stream.hls_characteristics; + const bool forced_subtitle = stream.forced_subtitle; if (name.empty()) { name = absl::StrFormat("stream_%d", stream_index); @@ -73,13 +74,13 @@ std::list> CreateHlsListenersInternal( const bool kIFramesOnly = true; std::list> listeners; - listeners.emplace_back( - new HlsNotifyMuxerListener(playlist_name, !kIFramesOnly, name, group_id, - characteristics, notifier, stream.index)); + listeners.emplace_back(new HlsNotifyMuxerListener( + playlist_name, !kIFramesOnly, name, group_id, characteristics, + forced_subtitle, notifier, stream.index)); if (!iframe_playlist_name.empty()) { listeners.emplace_back(new HlsNotifyMuxerListener( iframe_playlist_name, kIFramesOnly, name, group_id, - std::vector(), notifier, stream.index)); + std::vector(), forced_subtitle, notifier, stream.index)); } return listeners; } diff --git a/packager/media/event/muxer_listener_factory.h b/packager/media/event/muxer_listener_factory.h index 2eec362e32f..9901373c484 100644 --- a/packager/media/event/muxer_listener_factory.h +++ b/packager/media/event/muxer_listener_factory.h @@ -47,6 +47,7 @@ class MuxerListenerFactory { std::string hls_playlist_name; std::string hls_iframe_playlist_name; std::vector hls_characteristics; + bool forced_subtitle = false; bool hls_only = false; // DASH specific values needed to write DASH mpd. Will only be used if an diff --git a/packager/mpd/base/adaptation_set.cc b/packager/mpd/base/adaptation_set.cc index c033710d059..e855ccb01aa 100644 --- a/packager/mpd/base/adaptation_set.cc +++ b/packager/mpd/base/adaptation_set.cc @@ -59,6 +59,8 @@ std::string RoleToText(AdaptationSet::Role role) { return "commentary"; case AdaptationSet::kRoleDub: return "dub"; + case AdaptationSet::kRoleForcedSubtitle: + return "forced-subtitle"; case AdaptationSet::kRoleDescription: return "description"; default: diff --git a/packager/mpd/base/adaptation_set.h b/packager/mpd/base/adaptation_set.h index 3ed4d12f0ff..239669c898e 100644 --- a/packager/mpd/base/adaptation_set.h +++ b/packager/mpd/base/adaptation_set.h @@ -43,6 +43,7 @@ class AdaptationSet { kRoleSupplementary, kRoleCommentary, kRoleDub, + kRoleForcedSubtitle, kRoleDescription }; diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index e0377da23d9..67777ea1ce0 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -213,6 +213,11 @@ message MediaInfo { // Equal to the target segment duration times the reference time scale. optional uint64 segment_duration = 25; + // Marks stream as a Forced Narrative subtitle stream, indicated using + // forced-subtitle role in DASH + // and FORCED=YES in HLS + optional bool forced_subtitle = 26 [default = false]; + // stream index for consistent ordering of streams optional uint32 index = 28; diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index bc6d990cedb..e2679a1460f 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -59,6 +59,8 @@ AdaptationSet::Role RoleFromString(const std::string& role_str) { return AdaptationSet::Role::kRoleCommentary; if (role_str == "dub") return AdaptationSet::Role::kRoleDub; + if (role_str == "forced-subtitle") + return AdaptationSet::Role::kRoleForcedSubtitle; if (role_str == "description") return AdaptationSet::Role::kRoleDescription; return AdaptationSet::Role::kRoleUnknown; diff --git a/packager/packager.cc b/packager/packager.cc index 636bada6321..de0993de76d 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -69,6 +69,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData( data.hls_playlist_name = stream.hls_playlist_name; data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name; data.hls_characteristics = stream.hls_characteristics; + data.forced_subtitle = stream.forced_subtitle; data.hls_only = stream.hls_only; data.dash_accessiblities = stream.dash_accessiblities;