diff --git a/packager/app/muxer_factory.cc b/packager/app/muxer_factory.cc index 159889c5ca..825fc6cd58 100644 --- a/packager/app/muxer_factory.cc +++ b/packager/app/muxer_factory.cc @@ -13,6 +13,7 @@ #include "packager/media/formats/mp4/mp4_muxer.h" #include "packager/media/formats/packed_audio/packed_audio_writer.h" #include "packager/media/formats/webm/webm_muxer.h" +#include "packager/media/formats/webvtt/webvtt_muxer.h" #include "packager/packager.h" namespace shaka { @@ -20,9 +21,9 @@ namespace media { MuxerFactory::MuxerFactory(const PackagingParams& packaging_params) : mp4_params_(packaging_params.mp4_output_params), + temp_dir_(packaging_params.temp_dir), transport_stream_timestamp_offset_ms_( - packaging_params.transport_stream_timestamp_offset_ms), - temp_dir_(packaging_params.temp_dir) {} + packaging_params.transport_stream_timestamp_offset_ms) {} std::shared_ptr MuxerFactory::CreateMuxer( MediaContainerName output_format, @@ -48,6 +49,9 @@ std::shared_ptr MuxerFactory::CreateMuxer( case CONTAINER_WEBM: muxer = std::make_shared(options); break; + case CONTAINER_WEBVTT: + muxer = std::make_shared(options); + break; case CONTAINER_MPEG2TS: muxer = std::make_shared(options); break; diff --git a/packager/app/muxer_factory.h b/packager/app/muxer_factory.h index f9e5003e1c..ca7a799aaa 100644 --- a/packager/app/muxer_factory.h +++ b/packager/app/muxer_factory.h @@ -42,13 +42,17 @@ class MuxerFactory { /// this will replace the clock for all muxers created after this call. void OverrideClock(base::Clock* clock); + void SetTsStreamOffset(uint32_t offset_ms) { + transport_stream_timestamp_offset_ms_ = offset_ms; + } + private: MuxerFactory(const MuxerFactory&) = delete; MuxerFactory& operator=(const MuxerFactory&) = delete; const Mp4OutputParams mp4_params_; - const uint32_t transport_stream_timestamp_offset_ms_ = 0; const std::string temp_dir_; + uint32_t transport_stream_timestamp_offset_ms_ = 0; base::Clock* clock_ = nullptr; }; diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 2d513bf759..512251f4bd 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -833,6 +833,21 @@ class PackagerFunctionalTest(PackagerAppTest): self.assertPackageSuccess(streams, flags) self._CheckTestResults('segmented-webvtt-with-language-override') + def testSegmentedWebVttText(self): + streams = self._GetStreams( + ['text'], test_files=['bear-english.vtt'], segmented=True) + flags = self._GetFlags(output_hls=True, output_dash=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('segmented-webvtt-text') + + def testSingleFileWebVttText(self): + streams = self._GetStreams(['text'], test_files=['bear-english.vtt']) + flags = self._GetFlags(output_hls=True, output_dash=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('single-file-webvtt-text') + def testMp4TrailingMoov(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video'], @@ -1930,7 +1945,8 @@ class PackagerCommandParsingTest(PackagerAppTest): packaging_result = self.packager.Package(audio_video_stream + text_stream, self._GetFlags()) # Expect the test to fail but we do not expect a crash. - self.assertEqual(packaging_result, 1) + self.assertNotEqual(packaging_result, 0) + self.assertLess(packaging_result, 10) def testInconsistentOutputAndSegmentTemplateFormat(self): test_file = os.path.join(self.test_data_dir, 'bear-640x360.mp4') diff --git a/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/bear-english-text.vtt b/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/bear-english-text.vtt index b72b5181de..30e81dfea0 100644 --- a/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/bear-english-text.vtt +++ b/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/bear-english-text.vtt @@ -8,3 +8,4 @@ Yup, that's a bear, eh. 00:00:01.000 --> 00:00:04.700 He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd b/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd index 8032c02725..4cc940fcee 100644 --- a/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd +++ b/packager/app/test/testdata/audio-video-with-accessibilities-and-roles/output.mpd @@ -4,7 +4,7 @@ - + bear-english-text.vtt diff --git a/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/output.m3u8 b/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/output.m3u8 index 2f8252e4f9..d3eaa10ae5 100644 --- a/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/output.m3u8 +++ b/packager/app/test/testdata/hls-audio-video-text-with-ad-cues/output.m3u8 @@ -3,9 +3,9 @@ #EXT-X-INDEPENDENT-SEGMENTS -#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_1",AUTOSELECT=YES,CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" -#EXT-X-MEDIA:TYPE=SUBTITLES,URI="bear-english-text.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="bear-english-text.m3u8",GROUP-ID="default-text-group",NAME="stream_2",AUTOSELECT=YES #EXT-X-STREAM-INF:BANDWIDTH=1108115,AVERAGE-BANDWIDTH=1006069,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group" bear-640x360-video.m3u8 diff --git a/packager/app/test/testdata/hls-only-dash-only-captions/output.m3u8 b/packager/app/test/testdata/hls-only-dash-only-captions/output.m3u8 index 7ed55c2aac..9ecfac4958 100644 --- a/packager/app/test/testdata/hls-only-dash-only-captions/output.m3u8 +++ b/packager/app/test/testdata/hls-only-dash-only-captions/output.m3u8 @@ -3,9 +3,9 @@ #EXT-X-INDEPENDENT-SEGMENTS -#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_1.m3u8",GROUP-ID="default-audio-group",NAME="stream_1",AUTOSELECT=YES,CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" -#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_3.m3u8",GROUP-ID="default-text-group",NAME="stream_3",AUTOSELECT=YES #EXT-X-STREAM-INF:BANDWIDTH=1108115,AVERAGE-BANDWIDTH=1006069,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group" -stream_2.m3u8 +stream_1.m3u8 diff --git a/packager/app/test/testdata/hls-only-dash-only-captions/stream_0.m3u8 b/packager/app/test/testdata/hls-only-dash-only-captions/stream_0.m3u8 index bca9f92321..a717606102 100644 --- a/packager/app/test/testdata/hls-only-dash-only-captions/stream_0.m3u8 +++ b/packager/app/test/testdata/hls-only-dash-only-captions/stream_0.m3u8 @@ -3,14 +3,11 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:1.000, -bear-english-text-1.vtt -#EXTINF:1.000, -bear-english-text-2.vtt -#EXTINF:1.000, -bear-english-text-3.vtt -#EXTINF:1.000, -bear-english-text-4.vtt -#EXTINF:1.000, -bear-english-text-5.vtt +#EXT-X-MAP:URI="bear-640x360-audio-init.mp4" +#EXTINF:1.022, +bear-640x360-audio-1.m4s +#EXTINF:0.998, +bear-640x360-audio-2.m4s +#EXTINF:0.720, +bear-640x360-audio-3.m4s #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-only-dash-only-captions/stream_1.m3u8 b/packager/app/test/testdata/hls-only-dash-only-captions/stream_1.m3u8 index a717606102..19e29dda02 100644 --- a/packager/app/test/testdata/hls-only-dash-only-captions/stream_1.m3u8 +++ b/packager/app/test/testdata/hls-only-dash-only-captions/stream_1.m3u8 @@ -3,11 +3,11 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-MAP:URI="bear-640x360-audio-init.mp4" -#EXTINF:1.022, -bear-640x360-audio-1.m4s -#EXTINF:0.998, -bear-640x360-audio-2.m4s -#EXTINF:0.720, -bear-640x360-audio-3.m4s +#EXT-X-MAP:URI="bear-640x360-video-init.mp4" +#EXTINF:1.001, +bear-640x360-video-1.m4s +#EXTINF:1.001, +bear-640x360-video-2.m4s +#EXTINF:0.734, +bear-640x360-video-3.m4s #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-only-dash-only-captions/stream_2.m3u8 b/packager/app/test/testdata/hls-only-dash-only-captions/stream_2.m3u8 deleted file mode 100644 index 19e29dda02..0000000000 --- a/packager/app/test/testdata/hls-only-dash-only-captions/stream_2.m3u8 +++ /dev/null @@ -1,13 +0,0 @@ -#EXTM3U -#EXT-X-VERSION:6 -## Generated with https://github.com/google/shaka-packager version -- -#EXT-X-TARGETDURATION:2 -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-MAP:URI="bear-640x360-video-init.mp4" -#EXTINF:1.001, -bear-640x360-video-1.m4s -#EXTINF:1.001, -bear-640x360-video-2.m4s -#EXTINF:0.734, -bear-640x360-video-3.m4s -#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-only-dash-only-captions/stream_3.m3u8 b/packager/app/test/testdata/hls-only-dash-only-captions/stream_3.m3u8 new file mode 100644 index 0000000000..bca9f92321 --- /dev/null +++ b/packager/app/test/testdata/hls-only-dash-only-captions/stream_3.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.000, +bear-english-text-1.vtt +#EXTINF:1.000, +bear-english-text-2.vtt +#EXTINF:1.000, +bear-english-text-3.vtt +#EXTINF:1.000, +bear-english-text-4.vtt +#EXTINF:1.000, +bear-english-text-5.vtt +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-segmented-webvtt/output.m3u8 b/packager/app/test/testdata/hls-segmented-webvtt/output.m3u8 index 8300ddf271..ae3e7f28f2 100644 --- a/packager/app/test/testdata/hls-segmented-webvtt/output.m3u8 +++ b/packager/app/test/testdata/hls-segmented-webvtt/output.m3u8 @@ -3,9 +3,9 @@ #EXT-X-INDEPENDENT-SEGMENTS -#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_1.m3u8",GROUP-ID="default-audio-group",NAME="stream_1",AUTOSELECT=YES,CHANNELS="2" +#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_0.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",AUTOSELECT=YES,CHANNELS="2" -#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES,CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,private.accessibility.widevine-special" +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_2.m3u8",GROUP-ID="default-text-group",NAME="stream_2",AUTOSELECT=YES,CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog,private.accessibility.widevine-special" #EXT-X-STREAM-INF:BANDWIDTH=1217518,AVERAGE-BANDWIDTH=1117319,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group" -stream_2.m3u8 +stream_1.m3u8 diff --git a/packager/app/test/testdata/hls-segmented-webvtt/stream_0.m3u8 b/packager/app/test/testdata/hls-segmented-webvtt/stream_0.m3u8 index bca9f92321..e0ddf92e18 100644 --- a/packager/app/test/testdata/hls-segmented-webvtt/stream_0.m3u8 +++ b/packager/app/test/testdata/hls-segmented-webvtt/stream_0.m3u8 @@ -3,14 +3,10 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:1.000, -bear-english-text-1.vtt -#EXTINF:1.000, -bear-english-text-2.vtt -#EXTINF:1.000, -bear-english-text-3.vtt -#EXTINF:1.000, -bear-english-text-4.vtt -#EXTINF:1.000, -bear-english-text-5.vtt +#EXTINF:1.045, +bear-640x360-audio-1.ts +#EXTINF:0.998, +bear-640x360-audio-2.ts +#EXTINF:0.720, +bear-640x360-audio-3.ts #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-segmented-webvtt/stream_1.m3u8 b/packager/app/test/testdata/hls-segmented-webvtt/stream_1.m3u8 index e0ddf92e18..51a3eafba0 100644 --- a/packager/app/test/testdata/hls-segmented-webvtt/stream_1.m3u8 +++ b/packager/app/test/testdata/hls-segmented-webvtt/stream_1.m3u8 @@ -3,10 +3,10 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:1.045, -bear-640x360-audio-1.ts -#EXTINF:0.998, -bear-640x360-audio-2.ts -#EXTINF:0.720, -bear-640x360-audio-3.ts +#EXTINF:1.001, +bear-640x360-video-1.ts +#EXTINF:1.001, +bear-640x360-video-2.ts +#EXTINF:0.734, +bear-640x360-video-3.ts #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/hls-segmented-webvtt/stream_2.m3u8 b/packager/app/test/testdata/hls-segmented-webvtt/stream_2.m3u8 index 51a3eafba0..bca9f92321 100644 --- a/packager/app/test/testdata/hls-segmented-webvtt/stream_2.m3u8 +++ b/packager/app/test/testdata/hls-segmented-webvtt/stream_2.m3u8 @@ -3,10 +3,14 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:1.001, -bear-640x360-video-1.ts -#EXTINF:1.001, -bear-640x360-video-2.ts -#EXTINF:0.734, -bear-640x360-video-3.ts +#EXTINF:1.000, +bear-english-text-1.vtt +#EXTINF:1.000, +bear-english-text-2.vtt +#EXTINF:1.000, +bear-english-text-3.vtt +#EXTINF:1.000, +bear-english-text-4.vtt +#EXTINF:1.000, +bear-english-text-5.vtt #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-1.vtt b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-1.vtt new file mode 100644 index 0000000000..0e343feb32 --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-1.vtt @@ -0,0 +1,9 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:9000 + +STYLE +::cue { color:lime } + +00:00:00.000 --> 00:00:00.800 +Yup, that's a bear, eh. + diff --git a/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-2.vtt b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-2.vtt new file mode 100644 index 0000000000..8597f536ab --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-2.vtt @@ -0,0 +1,9 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:9000 + +STYLE +::cue { color:lime } + +00:00:01.000 --> 00:00:04.700 +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-3.vtt b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-3.vtt new file mode 100644 index 0000000000..8597f536ab --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-3.vtt @@ -0,0 +1,9 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:9000 + +STYLE +::cue { color:lime } + +00:00:01.000 --> 00:00:04.700 +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-4.vtt b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-4.vtt new file mode 100644 index 0000000000..8597f536ab --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-4.vtt @@ -0,0 +1,9 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:9000 + +STYLE +::cue { color:lime } + +00:00:01.000 --> 00:00:04.700 +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-5.vtt b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-5.vtt new file mode 100644 index 0000000000..8597f536ab --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/bear-english-text-5.vtt @@ -0,0 +1,9 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:9000 + +STYLE +::cue { color:lime } + +00:00:01.000 --> 00:00:04.700 +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/segmented-webvtt-text/output.m3u8 b/packager/app/test/testdata/segmented-webvtt-text/output.m3u8 new file mode 100644 index 0000000000..ad00c97c90 --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/output.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES diff --git a/packager/app/test/testdata/segmented-webvtt-text/output.mpd b/packager/app/test/testdata/segmented-webvtt-text/output.mpd new file mode 100644 index 0000000000..78c74aa517 --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-text/output.mpd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packager/app/test/testdata/segmented-webvtt-with-language-override/stream_0.m3u8 b/packager/app/test/testdata/segmented-webvtt-text/stream_0.m3u8 similarity index 100% rename from packager/app/test/testdata/segmented-webvtt-with-language-override/stream_0.m3u8 rename to packager/app/test/testdata/segmented-webvtt-text/stream_0.m3u8 diff --git a/packager/app/test/testdata/segmented-webvtt-with-language-override/output.m3u8 b/packager/app/test/testdata/segmented-webvtt-with-language-override/output.m3u8 index 053b55a7c1..77399f9ada 100644 --- a/packager/app/test/testdata/segmented-webvtt-with-language-override/output.m3u8 +++ b/packager/app/test/testdata/segmented-webvtt-with-language-override/output.m3u8 @@ -3,4 +3,4 @@ #EXT-X-INDEPENDENT-SEGMENTS -#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",LANGUAGE="pt",NAME="stream_0",AUTOSELECT=YES +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_1.m3u8",GROUP-ID="default-text-group",LANGUAGE="pt",NAME="stream_1",AUTOSELECT=YES diff --git a/packager/app/test/testdata/segmented-webvtt-with-language-override/stream_1.m3u8 b/packager/app/test/testdata/segmented-webvtt-with-language-override/stream_1.m3u8 new file mode 100644 index 0000000000..d3112ce3f9 --- /dev/null +++ b/packager/app/test/testdata/segmented-webvtt-with-language-override/stream_1.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:1 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.000, +bear-english-text-1.vtt +#EXTINF:1.000, +bear-english-text-2.vtt +#EXTINF:1.000, +bear-english-text-3.vtt +#EXTINF:1.000, +bear-english-text-4.vtt +#EXTINF:1.000, +bear-english-text-5.vtt +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/single-file-webvtt-text/bear-english-text.vtt b/packager/app/test/testdata/single-file-webvtt-text/bear-english-text.vtt new file mode 100644 index 0000000000..6f20be5417 --- /dev/null +++ b/packager/app/test/testdata/single-file-webvtt-text/bear-english-text.vtt @@ -0,0 +1,12 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:9000 + +STYLE +::cue { color:lime } + +00:00:00.000 --> 00:00:00.800 +Yup, that's a bear, eh. + +00:00:01.000 --> 00:00:04.700 +He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/single-file-webvtt-text/output.m3u8 b/packager/app/test/testdata/single-file-webvtt-text/output.m3u8 new file mode 100644 index 0000000000..ad00c97c90 --- /dev/null +++ b/packager/app/test/testdata/single-file-webvtt-text/output.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_0.m3u8",GROUP-ID="default-text-group",NAME="stream_0",AUTOSELECT=YES diff --git a/packager/app/test/testdata/single-file-webvtt-text/output.mpd b/packager/app/test/testdata/single-file-webvtt-text/output.mpd new file mode 100644 index 0000000000..e0317579b1 --- /dev/null +++ b/packager/app/test/testdata/single-file-webvtt-text/output.mpd @@ -0,0 +1,12 @@ + + + + + + + + bear-english-text.vtt + + + + diff --git a/packager/app/test/testdata/single-file-webvtt-text/stream_0.m3u8 b/packager/app/test/testdata/single-file-webvtt-text/stream_0.m3u8 new file mode 100644 index 0000000000..d1587072ec --- /dev/null +++ b/packager/app/test/testdata/single-file-webvtt-text/stream_0.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/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/app/test/testdata/video-audio-webvtt/bear-english-text.vtt b/packager/app/test/testdata/video-audio-webvtt/bear-english-text.vtt index b72b5181de..30e81dfea0 100644 --- a/packager/app/test/testdata/video-audio-webvtt/bear-english-text.vtt +++ b/packager/app/test/testdata/video-audio-webvtt/bear-english-text.vtt @@ -8,3 +8,4 @@ Yup, that's a bear, eh. 00:00:01.000 --> 00:00:04.700 He 's... um... doing bear-like stuff. + diff --git a/packager/app/test/testdata/video-audio-webvtt/output.mpd b/packager/app/test/testdata/video-audio-webvtt/output.mpd index 1d7eb4173f..c9506e753f 100644 --- a/packager/app/test/testdata/video-audio-webvtt/output.mpd +++ b/packager/app/test/testdata/video-audio-webvtt/output.mpd @@ -3,7 +3,8 @@ - + + bear-english-text.vtt diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 9bc585b851..e1089448d2 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -391,7 +391,8 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { time_scale_ = time_scale; media_info_ = media_info; language_ = GetLanguage(media_info); - use_byte_range_ = !media_info_.has_segment_template_url(); + use_byte_range_ = !media_info_.has_segment_template_url() && + media_info_.container_type() != MediaInfo::CONTAINER_TEXT; characteristics_ = std::vector(media_info_.hls_characteristics().begin(), media_info_.hls_characteristics().end()); diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index cf8212d75f..dbed0b98c3 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -68,7 +68,11 @@ Status Muxer::Process(std::unique_ptr stream_data) { return FinalizeSegment(stream_data->stream_index, segment_info); } case StreamDataType::kMediaSample: - return AddSample(stream_data->stream_index, *stream_data->media_sample); + return AddMediaSample(stream_data->stream_index, + *stream_data->media_sample); + case StreamDataType::kTextSample: + return AddTextSample(stream_data->stream_index, + *stream_data->text_sample); case StreamDataType::kCueEvent: if (muxer_listener_) { const int64_t time_scale = @@ -99,6 +103,14 @@ Status Muxer::OnFlushRequest(size_t input_stream_index) { return Finalize(); } +Status Muxer::AddMediaSample(size_t stream_id, const MediaSample& sample) { + return Status::OK; +} + +Status Muxer::AddTextSample(size_t stream_id, const TextSample& sample) { + return Status::OK; +} + Status Muxer::ReinitializeMuxer(int64_t timestamp) { if (muxer_listener_ && streams_.back()->is_encrypted()) { const EncryptionConfig& encryption_config = diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index 3b01fa6827..252f969a98 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -83,10 +83,13 @@ class Muxer : public MediaHandler { // Final clean up. virtual Status Finalize() = 0; - // Add a new sample. - virtual Status AddSample( - size_t stream_id, - const MediaSample& sample) = 0; + // Add a new media sample. This does nothing by default; so subclasses that + // handle media samples will need to replace this. + virtual Status AddMediaSample(size_t stream_id, const MediaSample& sample); + + // Add a new text sample. This does nothing by default; so subclasses that + // handle text samples will need to replace this. + virtual Status AddTextSample(size_t stream_id, const TextSample& sample); // Finalize the segment or subsegment. virtual Status FinalizeSegment( diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 749b7843cb..421f0454dd 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -73,8 +73,14 @@ void MpdNotifyMuxerListener::OnMediaStart( } for (const std::string& accessibility : accessibilities_) media_info->add_dash_accessibilities(accessibility); - for (const std::string& role : roles_) - media_info->add_dash_roles(role); + if (roles_.empty() && stream_info.stream_type() == kStreamText) { + // If there aren't any roles, default to "subtitle" since some apps may + // require it to distinguish between subtitle/caption. + media_info->add_dash_roles("subtitle"); + } else { + for (const std::string& role : roles_) + media_info->add_dash_roles(role); + } if (is_encrypted_) { internal::SetContentProtectionFields(protection_scheme_, default_key_id_, diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index 013d8a2a58..0ef27cff27 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -166,9 +166,8 @@ void AddAudioInfo(const AudioStreamInfo* audio_stream_info, void AddTextInfo(const TextStreamInfo& text_stream_info, MediaInfo* media_info) { - // For now, set everything as subtitle. + // TODO(modmaker): Set kind. MediaInfo::TextInfo* text_info = media_info->mutable_text_info(); - text_info->set_type(MediaInfo::TextInfo::SUBTITLE); text_info->set_codec(text_stream_info.codec_string()); text_info->set_language(text_stream_info.language()); } @@ -234,8 +233,9 @@ bool GenerateMediaInfo(const MuxerOptions& muxer_options, SetMediaInfoMuxerOptions(muxer_options, media_info); SetMediaInfoStreamInfo(stream_info, media_info); - media_info->set_reference_time_scale(reference_time_scale); SetMediaInfoContainerType(container_type, media_info); + if (reference_time_scale > 0) + media_info->set_reference_time_scale(reference_time_scale); if (muxer_options.bandwidth > 0) media_info->set_bandwidth(muxer_options.bandwidth); diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index e3d6447ad4..b3be39611b 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -32,15 +32,15 @@ Status TsMuxer::Finalize() { return segmenter_->Finalize(); } -Status TsMuxer::AddSample(size_t stream_id, const MediaSample& sample) { +Status TsMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) { DCHECK_EQ(stream_id, 0u); if (num_samples_ < 2) { - sample_durations_[num_samples_] = sample.duration() * kTsTimescale / - streams().front()->time_scale(); - if (num_samples_ == 1 && muxer_listener()) + sample_durations_[num_samples_] = + sample.duration() * kTsTimescale / streams().front()->time_scale(); + if (num_samples_ == 1 && muxer_listener()) muxer_listener()->OnSampleDurationReady(sample_durations_[num_samples_]); num_samples_++; - } + } return segmenter_->AddSample(sample); } diff --git a/packager/media/formats/mp2t/ts_muxer.h b/packager/media/formats/mp2t/ts_muxer.h index c4c0c4bc74..650d81a07a 100644 --- a/packager/media/formats/mp2t/ts_muxer.h +++ b/packager/media/formats/mp2t/ts_muxer.h @@ -26,8 +26,7 @@ class TsMuxer : public Muxer { // Muxer implementation. Status InitializeMuxer() override; Status Finalize() override; - Status AddSample(size_t stream_id, - const MediaSample& sample) override; + Status AddMediaSample(size_t stream_id, const MediaSample& sample) override; Status FinalizeSegment(size_t stream_id, const SegmentInfo& sample) override; diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 6af6b38aab..189784f209 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -181,7 +181,7 @@ Status MP4Muxer::Finalize() { return Status::OK; } -Status MP4Muxer::AddSample(size_t stream_id, const MediaSample& sample) { +Status MP4Muxer::AddMediaSample(size_t stream_id, const MediaSample& sample) { if (to_be_initialized_) { RETURN_IF_ERROR(UpdateEditListOffsetFromSample(sample)); RETURN_IF_ERROR(DelayInitializeMuxer()); diff --git a/packager/media/formats/mp4/mp4_muxer.h b/packager/media/formats/mp4/mp4_muxer.h index 67e625b291..6d6ae6ed7a 100644 --- a/packager/media/formats/mp4/mp4_muxer.h +++ b/packager/media/formats/mp4/mp4_muxer.h @@ -39,7 +39,7 @@ class MP4Muxer : public Muxer { // Muxer implementation overrides. Status InitializeMuxer() override; Status Finalize() override; - Status AddSample(size_t stream_id, const MediaSample& sample) override; + Status AddMediaSample(size_t stream_id, const MediaSample& sample) override; Status FinalizeSegment(size_t stream_id, const SegmentInfo& segment_info) override; diff --git a/packager/media/formats/packed_audio/packed_audio_writer.cc b/packager/media/formats/packed_audio/packed_audio_writer.cc index 29f796c56e..0b17cf3872 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer.cc @@ -58,8 +58,8 @@ Status PackedAudioWriter::Finalize() { return Status::OK; } -Status PackedAudioWriter::AddSample(size_t stream_id, - const MediaSample& sample) { +Status PackedAudioWriter::AddMediaSample(size_t stream_id, + const MediaSample& sample) { DCHECK_EQ(stream_id, 0u); return segmenter_->AddSample(sample); } diff --git a/packager/media/formats/packed_audio/packed_audio_writer.h b/packager/media/formats/packed_audio/packed_audio_writer.h index 59f46bb23a..816327838b 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.h +++ b/packager/media/formats/packed_audio/packed_audio_writer.h @@ -35,7 +35,7 @@ class PackedAudioWriter : public Muxer { // Muxer implementations. Status InitializeMuxer() override; Status Finalize() override; - Status AddSample(size_t stream_id, const MediaSample& sample) override; + Status AddMediaSample(size_t stream_id, const MediaSample& sample) override; Status FinalizeSegment(size_t stream_id, const SegmentInfo& sample) override; Status WriteSegment(const std::string& segment_path, diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index dcf02be7a7..5f1d516577 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -58,7 +58,7 @@ Status WebMMuxer::Finalize() { return Status::OK; } -Status WebMMuxer::AddSample(size_t stream_id, const MediaSample& sample) { +Status WebMMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) { DCHECK(segmenter_); DCHECK_EQ(stream_id, 0u); if (sample.pts() < 0) { diff --git a/packager/media/formats/webm/webm_muxer.h b/packager/media/formats/webm/webm_muxer.h index 86bd94e55d..d44caebb46 100644 --- a/packager/media/formats/webm/webm_muxer.h +++ b/packager/media/formats/webm/webm_muxer.h @@ -26,7 +26,7 @@ class WebMMuxer : public Muxer { // Muxer implementation overrides. Status InitializeMuxer() override; Status Finalize() override; - Status AddSample(size_t stream_id, const MediaSample& sample) override; + Status AddMediaSample(size_t stream_id, const MediaSample& sample) override; Status FinalizeSegment(size_t stream_id, const SegmentInfo& segment_info) override; diff --git a/packager/media/formats/webvtt/webvtt.gyp b/packager/media/formats/webvtt/webvtt.gyp index b496c2d605..62c971a26b 100644 --- a/packager/media/formats/webvtt/webvtt.gyp +++ b/packager/media/formats/webvtt/webvtt.gyp @@ -19,10 +19,10 @@ 'text_readers.h', 'webvtt_file_buffer.cc', 'webvtt_file_buffer.h', + 'webvtt_muxer.cc', + 'webvtt_muxer.h', 'webvtt_parser.cc', 'webvtt_parser.h', - 'webvtt_text_output_handler.cc', - 'webvtt_text_output_handler.h', 'webvtt_timestamp.cc', 'webvtt_timestamp.h', 'webvtt_to_mp4_handler.cc', @@ -40,8 +40,8 @@ 'type': '<(gtest_target_type)', 'sources': [ 'text_readers_unittest.cc', + 'webvtt_muxer_unittest.cc', 'webvtt_parser_unittest.cc', - 'webvtt_text_output_handler_unittest.cc', 'webvtt_timestamp_unittest.cc', 'webvtt_to_mp4_handler_unittest.cc', ], diff --git a/packager/media/formats/webvtt/webvtt_muxer.cc b/packager/media/formats/webvtt/webvtt_muxer.cc new file mode 100644 index 0000000000..8520a58270 --- /dev/null +++ b/packager/media/formats/webvtt/webvtt_muxer.cc @@ -0,0 +1,135 @@ +// Copyright 2020 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/formats/webvtt/webvtt_muxer.h" + +#include + +#include "packager/file/file.h" +#include "packager/file/file_closer.h" +#include "packager/media/base/muxer_util.h" +#include "packager/media/formats/webvtt/webvtt_timestamp.h" +#include "packager/status_macros.h" + +namespace shaka { +namespace media { +namespace webvtt { + +namespace { + +std::string ToString(const std::vector& v) { + return std::string(v.begin(), v.end()); +} + +} // namespace + +WebVttMuxer::WebVttMuxer(const MuxerOptions& options) : Muxer(options) {} +WebVttMuxer::~WebVttMuxer() {} + +Status WebVttMuxer::InitializeMuxer() { + if (streams().size() != 1) { + return Status(error::MUXER_FAILURE, "Incorrect number of streams"); + } + + // Only initialize the stream once we see a cue to avoid empty files. + muxer_listener()->OnMediaStart(options(), *streams()[0], + streams()[0]->time_scale(), + MuxerListener::kContainerText); + + buffer_.reset( + new WebVttFileBuffer(options().transport_stream_timestamp_offset_ms, + ToString(streams()[0]->codec_config()))); + last_cue_ms_ = 0; + + return Status::OK; +} + +Status WebVttMuxer::Finalize() { + const float duration_ms = static_cast(total_duration_ms_); + float duration_seconds = duration_ms / 1000; + + // If we haven't seen any segments, this is a single-file. In this case, + // flush the single segment. + MuxerListener::MediaRanges ranges; + if (duration_seconds == 0 && last_cue_ms_ != 0) { + DCHECK(options().segment_template.empty()); + duration_seconds = static_cast(last_cue_ms_) / 1000; + + uint64_t size; + RETURN_IF_ERROR(WriteToFile(options().output_file_name, &size)); + // Insert a dummy value so the HLS generator will generate a segment list. + ranges.subsegment_ranges.emplace_back(); + + muxer_listener()->OnNewSegment( + options().output_file_name, 0, + duration_seconds * streams()[0]->time_scale(), size); + } + + muxer_listener()->OnMediaEnd(ranges, duration_seconds); + + return Status::OK; +} + +Status WebVttMuxer::AddTextSample(size_t stream_id, const TextSample& sample) { + // Ignore sync samples. + if (sample.payload().empty()) { + return Status::OK; + } + + if (sample.id().find('\n') != std::string::npos || + sample.settings().find('\n') != std::string::npos) { + return Status(error::MUXER_FAILURE, + "Text id/settings cannot contain newlines"); + } + + last_cue_ms_ = sample.EndTime(); + buffer_->Append(sample); + return Status::OK; +} + +Status WebVttMuxer::FinalizeSegment(size_t stream_id, + const SegmentInfo& segment_info) { + total_duration_ms_ += segment_info.duration; + + const std::string& segment_template = options().segment_template; + DCHECK(!segment_template.empty()); + const uint32_t index = segment_index_++; + const uint64_t start = segment_info.start_timestamp; + const uint64_t duration = segment_info.duration; + const uint32_t bandwidth = options().bandwidth; + + const std::string filename = + GetSegmentName(segment_template, start, index, bandwidth); + uint64_t size; + RETURN_IF_ERROR(WriteToFile(filename, &size)); + + muxer_listener()->OnNewSegment(filename, start, duration, size); + return Status::OK; +} + +Status WebVttMuxer::WriteToFile(const std::string& filename, uint64_t* size) { + // Write everything to the file before telling the manifest so that the + // file will exist on disk. + std::unique_ptr file(File::Open(filename.c_str(), "w")); + if (!file) { + return Status(error::FILE_FAILURE, "Failed to open " + filename); + } + + buffer_->WriteTo(file.get()); + buffer_->Reset(); + if (!file.release()->Close()) { + return Status(error::FILE_FAILURE, "Failed to close " + filename); + } + + if (size) { + *size = File::GetFileSize(filename.c_str()); + } + return Status::OK; +} + +} // namespace webvtt +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_muxer.h b/packager/media/formats/webvtt/webvtt_muxer.h new file mode 100644 index 0000000000..089fede39a --- /dev/null +++ b/packager/media/formats/webvtt/webvtt_muxer.h @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MUXER_H_ +#define PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MUXER_H_ + +#include "packager/media/base/buffer_writer.h" +#include "packager/media/base/muxer.h" +#include "packager/media/formats/webvtt/webvtt_file_buffer.h" + +namespace shaka { +namespace media { +namespace webvtt { + +/// Implements WebVtt Muxer. +class WebVttMuxer : public Muxer { + public: + /// Create a WebMMuxer object from MuxerOptions. + explicit WebVttMuxer(const MuxerOptions& options); + ~WebVttMuxer() override; + + private: + // Muxer implementation overrides. + Status InitializeMuxer() override; + Status Finalize() override; + Status AddTextSample(size_t stream_id, const TextSample& sample) override; + Status FinalizeSegment(size_t stream_id, + const SegmentInfo& segment_info) override; + + Status WriteToFile(const std::string& filename, uint64_t* size); + + DISALLOW_COPY_AND_ASSIGN(WebVttMuxer); + + std::unique_ptr buffer_; + uint64_t total_duration_ms_ = 0; + uint64_t last_cue_ms_ = 0; + uint32_t segment_index_ = 0; +}; + +} // namespace webvtt +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_MUXER_H_ diff --git a/packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc b/packager/media/formats/webvtt/webvtt_muxer_unittest.cc similarity index 90% rename from packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc rename to packager/media/formats/webvtt/webvtt_muxer_unittest.cc index cde1afa78d..16cbb356a8 100644 --- a/packager/media/formats/webvtt/webvtt_text_output_handler_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_muxer_unittest.cc @@ -12,12 +12,15 @@ #include "packager/media/base/text_stream_info.h" #include "packager/media/event/combined_muxer_listener.h" #include "packager/media/event/mock_muxer_listener.h" -#include "packager/media/formats/webvtt/webvtt_text_output_handler.h" +#include "packager/media/formats/webvtt/webvtt_muxer.h" #include "packager/status_test_util.h" namespace shaka { namespace media { +namespace webvtt { + namespace { + using testing::_; const size_t kInputCount = 1; @@ -38,7 +41,7 @@ const uint64_t kSegmentDuration = 10000; const float kMillisecondsPerSecond = 1000.0f; } // namespace -class WebVttSegmentedOutputTest : public MediaHandlerTestBase { +class WebVttMuxerTest : public MediaHandlerTestBase { protected: void SetUp() { MuxerOptions muxer_options; @@ -49,17 +52,17 @@ class WebVttSegmentedOutputTest : public MediaHandlerTestBase { std::unique_ptr muxer_listener(new MockMuxerListener); muxer_listener_ = muxer_listener.get(); - out_ = std::make_shared(muxer_options, - std::move(muxer_listener)); + out_ = std::make_shared(muxer_options); + out_->SetMuxerListener(std::move(muxer_listener)); ASSERT_OK(SetUpAndInitializeGraph(out_, kInputCount, kOutputCount)); } MockMuxerListener* muxer_listener_ = nullptr; - std::shared_ptr out_; + std::shared_ptr out_; }; -TEST_F(WebVttSegmentedOutputTest, WithNoSegmentAndWithNoSamples) { +TEST_F(WebVttMuxerTest, WithNoSegmentAndWithNoSamples) { EXPECT_CALL(*muxer_listener_, OnNewSegment(_, _, _, _)).Times(0); { @@ -67,10 +70,7 @@ TEST_F(WebVttSegmentedOutputTest, WithNoSegmentAndWithNoSamples) { testing::InSequence s; EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); - - const float kMediaDuration = 0 * kSegmentDuration / kMillisecondsPerSecond; - EXPECT_CALL(*muxer_listener_, - OnMediaEndMock(_, _, _, _, _, _, _, _, kMediaDuration)); + EXPECT_CALL(*muxer_listener_, OnMediaEndMock(_, _, _, _, _, _, _, _, _)); } ASSERT_OK(Input(kInputIndex) @@ -79,7 +79,7 @@ TEST_F(WebVttSegmentedOutputTest, WithNoSegmentAndWithNoSamples) { ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); } -TEST_F(WebVttSegmentedOutputTest, WithOneSegmentAndWithOneSample) { +TEST_F(WebVttMuxerTest, WithOneSegmentAndWithOneSample) { const char* kExpectedOutput = "WEBVTT\n" "\n" @@ -118,7 +118,7 @@ TEST_F(WebVttSegmentedOutputTest, WithOneSegmentAndWithOneSample) { ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput); } -TEST_F(WebVttSegmentedOutputTest, WithTwoSegmentAndWithOneSample) { +TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) { const char* kExpectedOutput1 = "WEBVTT\n" "\n" @@ -181,7 +181,7 @@ TEST_F(WebVttSegmentedOutputTest, WithTwoSegmentAndWithOneSample) { ASSERT_FILE_STREQ(kSegmentedFileOutput2, kExpectedOutput2); } -TEST_F(WebVttSegmentedOutputTest, WithAnEmptySegment) { +TEST_F(WebVttMuxerTest, WithAnEmptySegment) { const char* kExpectedOutput1 = "WEBVTT\n" "\n"; @@ -235,5 +235,7 @@ TEST_F(WebVttSegmentedOutputTest, WithAnEmptySegment) { ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1); ASSERT_FILE_STREQ(kSegmentedFileOutput2, kExpectedOutput2); } + +} // namespace webvtt } // namespace media } // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_text_output_handler.cc b/packager/media/formats/webvtt/webvtt_text_output_handler.cc deleted file mode 100644 index 3043209114..0000000000 --- a/packager/media/formats/webvtt/webvtt_text_output_handler.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2017 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#include "packager/media/formats/webvtt/webvtt_text_output_handler.h" - -#include // needed for min and max - -#include "packager/base/logging.h" -#include "packager/file/file.h" -#include "packager/file/file_closer.h" -#include "packager/media/base/muxer_util.h" -#include "packager/status_macros.h" - -namespace shaka { -namespace media { -namespace { -double kMillisecondsToSeconds = 1000.0; - -std::string ToString(const std::vector& v) { - return std::string(v.begin(), v.end()); -} -} // namespace - -WebVttTextOutputHandler::WebVttTextOutputHandler( - const MuxerOptions& muxer_options, - std::unique_ptr muxer_listener) - : muxer_options_(muxer_options), - muxer_listener_(std::move(muxer_listener)) {} - -Status WebVttTextOutputHandler::InitializeInternal() { - return Status::OK; -} - -Status WebVttTextOutputHandler::Process( - std::unique_ptr stream_data) { - switch (stream_data->stream_data_type) { - case StreamDataType::kStreamInfo: - return OnStreamInfo(*stream_data->stream_info); - case StreamDataType::kSegmentInfo: - return OnSegmentInfo(*stream_data->segment_info); - case StreamDataType::kCueEvent: - return OnCueEvent(*stream_data->cue_event); - case StreamDataType::kTextSample: - OnTextSample(*stream_data->text_sample); - return Status::OK; - default: - return Status(error::INTERNAL_ERROR, - "Invalid stream data type for this handler"); - } -} - -Status WebVttTextOutputHandler::OnFlushRequest(size_t input_stream_index) { - if (!buffer_) { - LOG(INFO) << "Skip stream '" << muxer_options_.segment_template - << "' which does not contain any sample."; - return Status::OK; - } - - DCHECK_EQ(buffer_->sample_count(), 0u) - << "There should have been a segment info before flushing that would " - "have cleared out all the samples."; - - const float duration_ms = static_cast(total_duration_ms_); - const float duration_seconds = duration_ms / 1000.0f; - - MuxerListener::MediaRanges empty_ranges; - muxer_listener_->OnMediaEnd(empty_ranges, duration_seconds); - - return Status::OK; -} - -Status WebVttTextOutputHandler::OnStreamInfo(const StreamInfo& info) { - buffer_.reset( - new WebVttFileBuffer(muxer_options_.transport_stream_timestamp_offset_ms, - ToString(info.codec_config()))); - muxer_listener_->OnMediaStart(muxer_options_, info, info.time_scale(), - MuxerListener::kContainerText); - return Status::OK; -} - -Status WebVttTextOutputHandler::OnSegmentInfo(const SegmentInfo& info) { - total_duration_ms_ += info.duration; - - const std::string& segment_template = muxer_options_.segment_template; - const uint32_t index = segment_index_++; - const uint64_t start = info.start_timestamp; - const uint64_t duration = info.duration; - const uint32_t bandwidth = muxer_options_.bandwidth; - - const std::string filename = - GetSegmentName(segment_template, start, index, bandwidth); - - // Write everything to the file before telling the manifest so that the - // file will exist on disk. - std::unique_ptr file(File::Open(filename.c_str(), "w")); - - if (!file) { - return Status(error::FILE_FAILURE, "Failed to open " + filename); - } - - buffer_->WriteTo(file.get()); - buffer_->Reset(); - - if (!file.release()->Close()) { - return Status(error::FILE_FAILURE, "Failed to close " + filename); - } - - // Update the manifest with our new file. - const uint64_t size = File::GetFileSize(filename.c_str()); - muxer_listener_->OnNewSegment(filename, start, duration, size); - - return Status::OK; -} - -Status WebVttTextOutputHandler::OnCueEvent(const CueEvent& event) { - double timestamp_seconds = event.time_in_seconds; - double timestamp_ms = timestamp_seconds * kMillisecondsToSeconds; - uint64_t timestamp = static_cast(timestamp_ms); - muxer_listener_->OnCueEvent(timestamp, event.cue_data); - return Status::OK; -} - -void WebVttTextOutputHandler::OnTextSample(const TextSample& sample) { - // Skip empty samples. It is normal to see empty samples as earlier in the - // pipeline we pad the stream to remove gaps. - if (sample.payload().size()) { - buffer_->Append(sample); - } -} -} // namespace media -} // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_text_output_handler.h b/packager/media/formats/webvtt/webvtt_text_output_handler.h deleted file mode 100644 index 5d6792eab0..0000000000 --- a/packager/media/formats/webvtt/webvtt_text_output_handler.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2017 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#ifndef PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_TEXT_HANDLER_H_ -#define PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_TEXT_HANDLER_H_ - -#include - -#include - -#include "packager/media/base/media_handler.h" -#include "packager/media/base/muxer_options.h" -#include "packager/media/event/muxer_listener.h" -#include "packager/media/formats/webvtt/webvtt_file_buffer.h" - -namespace shaka { -namespace media { - -class WebVttTextOutputHandler : public MediaHandler { - public: - WebVttTextOutputHandler(const MuxerOptions& muxer_options, - std::unique_ptr muxer_listener); - virtual ~WebVttTextOutputHandler() = default; - - private: - WebVttTextOutputHandler(const WebVttTextOutputHandler&) = delete; - WebVttTextOutputHandler& operator=(const WebVttTextOutputHandler&) = delete; - - Status InitializeInternal() override; - Status Process(std::unique_ptr stream_data) override; - Status OnFlushRequest(size_t input_stream_index) override; - - Status OnStreamInfo(const StreamInfo& info); - Status OnSegmentInfo(const SegmentInfo& info); - Status OnCueEvent(const CueEvent& event); - void OnTextSample(const TextSample& sample); - - Status OnSegmentEnded(); - - void GoToNextSegment(uint64_t start_time_ms); - - const MuxerOptions muxer_options_; - std::unique_ptr muxer_listener_; - - // Sum together all segment durations so we know how long the stream is. - uint64_t total_duration_ms_ = 0; - uint32_t segment_index_ = 0; - - std::unique_ptr buffer_; -}; - -} // namespace media -} // namespace shaka - -#endif // PACKAGER_MEDIA_FORMATS_WEBVTT_WEBVTT_TEXT_HANDLER_H_ diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index 3c4f781fd3..f7c908400e 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -367,9 +367,9 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info) { return false; } - const bool need_segment_base = media_info.has_index_range() || - media_info.has_init_range() || - media_info.has_reference_time_scale(); + const bool need_segment_base = + media_info.has_index_range() || media_info.has_init_range() || + (media_info.has_reference_time_scale() && !media_info.has_text_info()); if (need_segment_base) { XmlNode segment_base("SegmentBase"); @@ -438,12 +438,12 @@ bool RepresentationXmlNode::AddLiveOnlyInfo( segment_infos.front().duration); if (FLAGS_dash_add_last_segment_number_when_needed) { uint32_t last_segment_number = start_number - 1; - for (const auto& segment_info_element : segment_infos) + for (const auto& segment_info_element : segment_infos) last_segment_number += segment_info_element.repeat + 1; - + AddSupplementalProperty( - "http://dashif.org/guidelines/last-segment-number", - std::to_string(last_segment_number)); + "http://dashif.org/guidelines/last-segment-number", + std::to_string(last_segment_number)); } } else { XmlNode segment_timeline("SegmentTimeline"); diff --git a/packager/packager.cc b/packager/packager.cc index 38761fbc59..8d66640b97 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -40,7 +40,6 @@ #include "packager/media/event/muxer_listener_factory.h" #include "packager/media/event/vod_media_info_dump_muxer_listener.h" #include "packager/media/formats/webvtt/text_padder.h" -#include "packager/media/formats/webvtt/webvtt_text_output_handler.h" #include "packager/media/formats/webvtt/webvtt_to_mp4_handler.h" #include "packager/media/replicator/replicator.h" #include "packager/media/trick_play/trick_play_handler.h" @@ -66,21 +65,6 @@ const char kMediaInfoSuffix[] = ".media_info"; const int64_t kDefaultTextZeroBiasMs = 10 * 60 * 1000; // 10 minutes -MuxerOptions CreateMuxerOptions(const StreamDescriptor& stream, - const PackagingParams& params) { - MuxerOptions options; - - options.mp4_params = params.mp4_output_params; - options.transport_stream_timestamp_offset_ms = - params.transport_stream_timestamp_offset_ms; - options.temp_dir = params.temp_dir; - options.bandwidth = stream.bandwidth; - options.output_file_name = stream.output; - options.segment_template = stream.segment_template; - - return options; -} - MuxerListenerFactory::StreamData ToMuxerListenerData( const StreamDescriptor& stream) { MuxerListenerFactory::StreamData data; @@ -486,99 +470,30 @@ std::unique_ptr CreateTextChunker( new TextChunker(segment_length_in_seconds)); } -Status CreateHlsTextJob(const StreamDescriptor& stream, - const PackagingParams& packaging_params, - std::unique_ptr muxer_listener, - SyncPointQueue* sync_points, - JobManager* job_manager) { - DCHECK(muxer_listener); - DCHECK(job_manager); - - if (stream.segment_template.empty()) { - return Status(error::INVALID_ARGUMENT, - "Cannot output text (" + stream.input + - ") to HLS with no segment template"); - } - - // Text files are usually small and since the input is one file; - // there's no way for the player to do ranged requests. So set this - // value to something reasonable if it is missing. - MuxerOptions muxer_options = CreateMuxerOptions(stream, packaging_params); - muxer_options.bandwidth = stream.bandwidth ? stream.bandwidth : 256; - - auto output = std::make_shared( - muxer_options, std::move(muxer_listener)); - - std::shared_ptr demuxer; - RETURN_IF_ERROR(CreateDemuxer(stream, packaging_params, &demuxer)); - if (!stream.language.empty()) - demuxer->SetLanguageOverride(stream.stream_selector, stream.language); - auto padder = std::make_shared(kDefaultTextZeroBiasMs); - RETURN_IF_ERROR(demuxer->SetHandler(stream.stream_selector, padder)); - - auto cue_aligner = sync_points - ? std::make_shared(sync_points) - : nullptr; - auto chunker = CreateTextChunker(packaging_params.chunking_params); - - job_manager->Add("Segmented Text Job", demuxer); - - return MediaHandler::Chain({std::move(padder), std::move(cue_aligner), - std::move(chunker), std::move(output)}); -} - -Status CreateTextJobs( +Status CreateTtmlJobs( const std::vector>& streams, const PackagingParams& packaging_params, SyncPointQueue* sync_points, - MuxerListenerFactory* muxer_listener_factory, MuxerFactory* muxer_factory, MpdNotifier* mpd_notifier, JobManager* job_manager) { - DCHECK(muxer_listener_factory); DCHECK(job_manager); for (const StreamDescriptor& stream : streams) { - // There are currently options: - // TEXT TTML --> TEXT TTML [ supported ], for DASH only. - // TEXT WEBVTT --> TEXT WEBVTT [ supported ] - // TEXT WEBVTT --> MP4 WEBVTT [ supported ] - // MP4 WEBVTT --> MP4 WEBVTT [ unsupported ] - // MP4 WEBVTT --> TEXT WEBVTT [ unsupported ] - const auto input_container = DetermineContainerFromFileName(stream.input); - - if (input_container != CONTAINER_WEBVTT && - input_container != CONTAINER_TTML) { - return Status(error::INVALID_ARGUMENT, - "Text output format is not support for " + stream.input); - } - - std::unique_ptr hls_listener = - muxer_listener_factory->CreateHlsListener(ToMuxerListenerData(stream)); - // Check input to ensure that output is possible. - if (hls_listener && !stream.dash_only) { - if (input_container == CONTAINER_TTML) { - return Status(error::INVALID_ARGUMENT, - "HLS does not support TTML in xml format."); - } - if (stream.segment_template.empty() || !stream.output.empty()) { - return Status(error::INVALID_ARGUMENT, - "segment_template needs to be specified for HLS text " - "output. Single file output is not supported yet."); - } - } - - if (mpd_notifier && !stream.segment_template.empty() && !stream.hls_only) { + if (!packaging_params.hls_params.master_playlist_output.empty() && + !stream.dash_only) { return Status(error::INVALID_ARGUMENT, - "Cannot create text output for MPD with segment output."); + "HLS does not support TTML in xml format."); } - // If we are outputting to HLS, then create the HLS test pipeline that - // will create segmented text output. - if (hls_listener && !stream.dash_only) { - RETURN_IF_ERROR(CreateHlsTextJob(stream, packaging_params, - std::move(hls_listener), sync_points, - job_manager)); + if (!stream.segment_template.empty()) { + return Status(error::INVALID_ARGUMENT, + "Segmented TTML is not supported."); + } + + if (GetOutputFormat(stream) != CONTAINER_TTML) { + return Status(error::INVALID_ARGUMENT, + "Converting TTML to other formats is not supported"); } if (!stream.output.empty()) { @@ -694,10 +609,7 @@ Status CreateAudioVideoJobs( if (sync_points) { handlers.emplace_back(cue_aligner); } - if (is_text) { - handlers.emplace_back( - CreateTextChunker(packaging_params.chunking_params)); - } else { + if (!is_text) { handlers.emplace_back(std::make_shared( packaging_params.chunking_params)); } @@ -731,6 +643,12 @@ Status CreateAudioVideoJobs( ? std::make_shared(stream.trick_play_factor) : nullptr; + std::shared_ptr chunker = + is_text && (!stream.segment_template.empty() || + output_format == CONTAINER_MOV) + ? CreateTextChunker(packaging_params.chunking_params) + : nullptr; + // TODO(modmaker): Move to MOV muxer? const auto input_container = DetermineContainerFromFileName(stream.input); auto text_to_mp4 = @@ -738,8 +656,8 @@ Status CreateAudioVideoJobs( ? std::make_shared() : nullptr; - RETURN_IF_ERROR( - MediaHandler::Chain({replicator, trick_play, text_to_mp4, muxer})); + RETURN_IF_ERROR(MediaHandler::Chain( + {replicator, trick_play, chunker, text_to_mp4, muxer})); } return Status::OK; @@ -758,7 +676,7 @@ Status CreateAllJobs(const std::vector& stream_descriptors, DCHECK(job_manager); // Group all streams based on which pipeline they will use. - std::vector> text_streams; + std::vector> ttml_streams; std::vector> audio_video_streams; @@ -766,15 +684,12 @@ Status CreateAllJobs(const std::vector& stream_descriptors, bool has_non_transport_audio_video_streams = false; for (const StreamDescriptor& stream : stream_descriptors) { - // TODO: Find a better way to determine what stream type a stream - // descriptor is as |stream_selector| may use an index. This would - // also allow us to use a simpler audio pipeline. + const auto input_container = DetermineContainerFromFileName(stream.input); const auto output_format = GetOutputFormat(stream); - if (stream.stream_selector == "text" && output_format != CONTAINER_MOV) { - text_streams.push_back(stream); + if (input_container == CONTAINER_TTML) { + ttml_streams.push_back(stream); } else { audio_video_streams.push_back(stream); - switch (output_format) { case CONTAINER_MPEG2TS: case CONTAINER_AAC: @@ -783,6 +698,9 @@ Status CreateAllJobs(const std::vector& stream_descriptors, case CONTAINER_EAC3: has_transport_audio_video_streams = true; break; + case CONTAINER_TTML: + case CONTAINER_WEBVTT: + break; default: has_non_transport_audio_video_streams = true; break; @@ -795,26 +713,21 @@ Status CreateAllJobs(const std::vector& stream_descriptors, std::sort(audio_video_streams.begin(), audio_video_streams.end(), media::StreamDescriptorCompareFn); - if (!text_streams.empty()) { - PackagingParams text_packaging_params = packaging_params; - if (text_packaging_params.transport_stream_timestamp_offset_ms > 0) { - if (has_transport_audio_video_streams && - has_non_transport_audio_video_streams) { - LOG(WARNING) << "There may be problems mixing transport streams and " - "non-transport streams. For example, the subtitles may " - "be out of sync with non-transport streams."; - } else if (has_non_transport_audio_video_streams) { - // Don't insert the X-TIMESTAMP-MAP in WebVTT if there is no transport - // stream. - text_packaging_params.transport_stream_timestamp_offset_ms = 0; - } + if (packaging_params.transport_stream_timestamp_offset_ms > 0) { + if (has_transport_audio_video_streams && + has_non_transport_audio_video_streams) { + LOG(WARNING) << "There may be problems mixing transport streams and " + "non-transport streams. For example, the subtitles may " + "be out of sync with non-transport streams."; + } else if (has_non_transport_audio_video_streams) { + // Don't insert the X-TIMESTAMP-MAP in WebVTT if there is no transport + // stream. + muxer_factory->SetTsStreamOffset(0); } - - RETURN_IF_ERROR(CreateTextJobs(text_streams, text_packaging_params, - sync_points, muxer_listener_factory, - muxer_factory, mpd_notifier, job_manager)); } + RETURN_IF_ERROR(CreateTtmlJobs(ttml_streams, packaging_params, sync_points, + muxer_factory, mpd_notifier, job_manager)); RETURN_IF_ERROR(CreateAudioVideoJobs( audio_video_streams, packaging_params, encryption_key_source, sync_points, muxer_listener_factory, muxer_factory, job_manager));