Fix default_language not effective with 2-char code
Two-character ISO-639 code in --default_language was ignored due to a bug in language code matching as the language code in stream is always converted to 3-character code. Fixes #371. Change-Id: I8618938af583a417446636ff9efe1c72ce822c33
This commit is contained in:
parent
4549b1d569
commit
0ef078a23b
|
@ -276,6 +276,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
utc_timings=None,
|
||||
generate_static_mpd=False,
|
||||
ad_cues=None,
|
||||
default_language=None,
|
||||
use_fake_clock=True):
|
||||
flags = []
|
||||
|
||||
|
@ -351,6 +352,9 @@ class PackagerAppTest(unittest.TestCase):
|
|||
if ad_cues:
|
||||
flags += ['--ad_cues', ad_cues]
|
||||
|
||||
if default_language:
|
||||
flags += ['--default_language', default_language]
|
||||
|
||||
flags.append('--segment_duration=1')
|
||||
# Use fake clock, so output can be compared.
|
||||
if use_fake_clock:
|
||||
|
@ -585,8 +589,26 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
|
||||
def testPackageAudioVideoWithLanguageOverride(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], language='por-BR'),
|
||||
self._GetFlags())
|
||||
self._GetStreams(['audio', 'video'], language='por'),
|
||||
self._GetFlags(default_language='por'))
|
||||
self._CheckTestResults('audio-video-with-language-override')
|
||||
|
||||
def testPackageAudioVideoWithLanguageOverrideUsingMixingCode(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], language='por'),
|
||||
self._GetFlags(default_language='pt'))
|
||||
self._CheckTestResults('audio-video-with-language-override')
|
||||
|
||||
def testPackageAudioVideoWithLanguageOverrideUsingMixingCode2(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], language='pt'),
|
||||
self._GetFlags(default_language='por'))
|
||||
self._CheckTestResults('audio-video-with-language-override')
|
||||
|
||||
def testPackageAudioVideoWithLanguageOverrideUsingTwoCharacterCode(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(['audio', 'video'], language='pt'),
|
||||
self._GetFlags(default_language='pt'))
|
||||
self._CheckTestResults('audio-video-with-language-override')
|
||||
|
||||
def testPackageAudioVideoWithLanguageOverrideWithSubtag(self):
|
||||
|
@ -623,6 +645,32 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self._GetFlags(output_hls=True))
|
||||
self._CheckTestResults('avc-aac-ts')
|
||||
|
||||
def testPackageAvcAacTsLanguage(self):
|
||||
# Currently we only support live packaging for ts.
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(
|
||||
['audio', 'video'],
|
||||
output_format='ts',
|
||||
segmented=True,
|
||||
hls=True,
|
||||
language='por',
|
||||
test_files=['bear-640x360.ts']),
|
||||
self._GetFlags(output_hls=True, default_language='por'))
|
||||
self._CheckTestResults('avc-aac-ts-language')
|
||||
|
||||
def testPackageAvcAacTsLanguageUsingTwoCharacterCode(self):
|
||||
# Currently we only support live packaging for ts.
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(
|
||||
['audio', 'video'],
|
||||
output_format='ts',
|
||||
segmented=True,
|
||||
hls=True,
|
||||
language='pt',
|
||||
test_files=['bear-640x360.ts']),
|
||||
self._GetFlags(output_hls=True, default_language='pt'))
|
||||
self._CheckTestResults('avc-aac-ts-language')
|
||||
|
||||
def testPackageAvcAc3Ts(self):
|
||||
# Currently we only support live packaging for ts.
|
||||
self.assertPackageSuccess(
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" lang="pt-BR" subsegmentAlignment="true">
|
||||
<AdaptationSet id="1" contentType="audio" lang="pt" subsegmentAlignment="true">
|
||||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
|
||||
<Representation id="1" bandwidth="126510" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>bear-640x360-audio.mp4</BaseURL>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXTINF:0.975,
|
||||
bear-640x360-audio-1.ts
|
||||
#EXTINF:0.998,
|
||||
bear-640x360-audio-2.ts
|
||||
#EXTINF:0.789,
|
||||
bear-640x360-audio-3.ts
|
||||
#EXT-X-ENDLIST
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
16
packager/app/test/testdata/avc-aac-ts-language/bear-640x360-video-iframe.m3u8
vendored
Normal file
16
packager/app/test/testdata/avc-aac-ts-language/bear-640x360-video-iframe.m3u8
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-I-FRAMES-ONLY
|
||||
#EXTINF:1.001,
|
||||
#EXT-X-BYTERANGE:15604@376
|
||||
bear-640x360-video-1.ts
|
||||
#EXTINF:1.001,
|
||||
#EXT-X-BYTERANGE:18236@376
|
||||
bear-640x360-video-2.ts
|
||||
#EXTINF:0.667,
|
||||
#EXT-X-BYTERANGE:19928@376
|
||||
bear-640x360-video-3.ts
|
||||
#EXT-X-ENDLIST
|
|
@ -0,0 +1,12 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#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
|
||||
#EXT-X-ENDLIST
|
|
@ -0,0 +1,9 @@
|
|||
#EXTM3U
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",LANGUAGE="pt",NAME="stream_0",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2"
|
||||
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="default-audio-group"
|
||||
bear-640x360-video.m3u8
|
||||
|
||||
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=238897,CODECS="avc1.64001e",RESOLUTION=640x360,URI="bear-640x360-video-iframe.m3u8"
|
|
@ -241,7 +241,7 @@ void BuildMediaTag(const MediaPlaylist& playlist,
|
|||
tag.AddQuotedString("URI", base_url + playlist.file_name());
|
||||
tag.AddQuotedString("GROUP-ID", group_id);
|
||||
|
||||
const std::string& language = playlist.GetLanguage();
|
||||
const std::string& language = playlist.language();
|
||||
if (!language.empty()) {
|
||||
tag.AddQuotedString("LANGUAGE", language);
|
||||
}
|
||||
|
@ -292,7 +292,7 @@ void BuildMediaTags(
|
|||
bool is_default = false;
|
||||
bool is_autoselect = false;
|
||||
|
||||
const std::string language = playlist->GetLanguage();
|
||||
const std::string language = playlist->language();
|
||||
if (languages.find(language) == languages.end()) {
|
||||
is_default = !language.empty() && language == default_language;
|
||||
is_autoselect = true;
|
||||
|
|
|
@ -78,12 +78,12 @@ std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist(
|
|||
std::unique_ptr<MockMediaPlaylist> playlist(
|
||||
new MockMediaPlaylist(kVodPlaylist, filename, name, group));
|
||||
|
||||
EXPECT_CALL(*playlist, GetLanguage()).WillRepeatedly(Return(language));
|
||||
EXPECT_CALL(*playlist, GetNumChannels()).WillRepeatedly(Return(channels));
|
||||
|
||||
playlist->SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kAudio);
|
||||
playlist->SetCodecForTesting(codec);
|
||||
playlist->SetLanguageForTesting(language);
|
||||
|
||||
EXPECT_CALL(*playlist, Bitrate())
|
||||
.Times(AtLeast(1))
|
||||
|
@ -102,10 +102,10 @@ std::unique_ptr<MockMediaPlaylist> CreateTextPlaylist(
|
|||
std::unique_ptr<MockMediaPlaylist> playlist(
|
||||
new MockMediaPlaylist(kVodPlaylist, filename, name, group));
|
||||
|
||||
EXPECT_CALL(*playlist, GetLanguage()).WillRepeatedly(Return(language));
|
||||
playlist->SetStreamTypeForTesting(
|
||||
MediaPlaylist::MediaPlaylistStreamType::kSubtitle);
|
||||
playlist->SetCodecForTesting(codec);
|
||||
playlist->SetLanguageForTesting(language);
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,22 @@ uint32_t GetTimeScale(const MediaInfo& media_info) {
|
|||
return 0u;
|
||||
}
|
||||
|
||||
// Duplicated from MpdUtils because:
|
||||
// 1. MpdUtils header depends on libxml header, which is not in the deps here
|
||||
// 2. GetLanguage depends on MediaInfo from packager/mpd/
|
||||
// 3. Moving GetLanguage to LanguageUtils would create a a media => mpd dep.
|
||||
// TODO(https://github.com/google/shaka-packager/issues/373): Fix this
|
||||
// dependency situation and factor this out to a common location.
|
||||
std::string GetLanguage(const MediaInfo& media_info) {
|
||||
std::string lang;
|
||||
if (media_info.has_audio_info()) {
|
||||
lang = media_info.audio_info().language();
|
||||
} else if (media_info.has_text_info()) {
|
||||
lang = media_info.text_info().language();
|
||||
}
|
||||
return LanguageToShortestForm(lang);
|
||||
}
|
||||
|
||||
void AppendExtXMap(const MediaInfo& media_info, std::string* out) {
|
||||
if (media_info.has_init_segment_name()) {
|
||||
Tag tag("#EXT-X-MAP", out);
|
||||
|
@ -327,6 +343,10 @@ void MediaPlaylist::SetCodecForTesting(const std::string& codec) {
|
|||
codec_ = codec;
|
||||
}
|
||||
|
||||
void MediaPlaylist::SetLanguageForTesting(const std::string& language) {
|
||||
language_ = language;
|
||||
}
|
||||
|
||||
bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||
const uint32_t time_scale = GetTimeScale(media_info);
|
||||
if (time_scale == 0) {
|
||||
|
@ -347,6 +367,7 @@ 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();
|
||||
return true;
|
||||
}
|
||||
|
@ -473,21 +494,6 @@ void MediaPlaylist::SetTargetDuration(uint32_t target_duration) {
|
|||
target_duration_set_ = true;
|
||||
}
|
||||
|
||||
// Duplicated from MpdUtils because:
|
||||
// 1. MpdUtils header depends on libxml header, which is not in the deps here
|
||||
// 2. GetLanguage depends on MediaInfo from packager/mpd/
|
||||
// 3. Moving GetLanguage to LanguageUtils would create a a media => mpd dep.
|
||||
// TODO: fix this dependency situation and factor this out to a common location
|
||||
std::string MediaPlaylist::GetLanguage() const {
|
||||
std::string lang;
|
||||
if (media_info_.has_audio_info()) {
|
||||
lang = media_info_.audio_info().language();
|
||||
} else if (media_info_.has_text_info()) {
|
||||
lang = media_info_.text_info().language();
|
||||
}
|
||||
return LanguageToShortestForm(lang);
|
||||
}
|
||||
|
||||
int MediaPlaylist::GetNumChannels() const {
|
||||
return media_info_.audio_info().num_channels();
|
||||
}
|
||||
|
|
|
@ -86,6 +86,9 @@ class MediaPlaylist {
|
|||
/// For testing only.
|
||||
void SetCodecForTesting(const std::string& codec);
|
||||
|
||||
/// For testing only.
|
||||
void SetLanguageForTesting(const std::string& language);
|
||||
|
||||
/// This must succeed before calling any other public methods.
|
||||
/// @param media_info is the info of the segments that are going to be added
|
||||
/// to this playlist.
|
||||
|
@ -167,10 +170,6 @@ class MediaPlaylist {
|
|||
/// @param target_duration is the target duration for this playlist.
|
||||
virtual void SetTargetDuration(uint32_t target_duration);
|
||||
|
||||
/// @return the language of the media, as an ISO language tag in its shortest
|
||||
/// form. May be an empty string for video.
|
||||
virtual std::string GetLanguage() const;
|
||||
|
||||
/// @return number of channels for audio. 0 is returned for video.
|
||||
virtual int GetNumChannels() const;
|
||||
|
||||
|
@ -178,6 +177,10 @@ class MediaPlaylist {
|
|||
/// resolution values.
|
||||
virtual bool GetDisplayResolution(uint32_t* width, uint32_t* height) const;
|
||||
|
||||
/// @return the language of the media, as an ISO language tag in its shortest
|
||||
/// form. May be an empty string for video.
|
||||
std::string language() const { return language_; }
|
||||
|
||||
private:
|
||||
// Add a SegmentInfoEntry (#EXTINF).
|
||||
void AddSegmentInfoEntry(const std::string& segment_file_name,
|
||||
|
@ -200,6 +203,7 @@ class MediaPlaylist {
|
|||
// Whether to use byte range for SegmentInfoEntry.
|
||||
bool use_byte_range_ = false;
|
||||
std::string codec_;
|
||||
std::string language_;
|
||||
int media_sequence_number_ = 0;
|
||||
bool inserted_discontinuity_tag_ = false;
|
||||
int discontinuity_sequence_number_ = 0;
|
||||
|
|
|
@ -449,15 +449,15 @@ TEST_F(MediaPlaylistMultiSegmentTest, GetLanguage) {
|
|||
// Check conversions from long to short form.
|
||||
media_info.mutable_audio_info()->set_language("eng");
|
||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info));
|
||||
EXPECT_EQ("en", media_playlist_.GetLanguage()); // short form
|
||||
EXPECT_EQ("en", media_playlist_.language()); // short form
|
||||
|
||||
media_info.mutable_audio_info()->set_language("eng-US");
|
||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info));
|
||||
EXPECT_EQ("en-US", media_playlist_.GetLanguage()); // region preserved
|
||||
EXPECT_EQ("en-US", media_playlist_.language()); // region preserved
|
||||
|
||||
media_info.mutable_audio_info()->set_language("apa");
|
||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(media_info));
|
||||
EXPECT_EQ("apa", media_playlist_.GetLanguage()); // no short form exists
|
||||
EXPECT_EQ("apa", media_playlist_.language()); // no short form exists
|
||||
}
|
||||
|
||||
TEST_F(MediaPlaylistMultiSegmentTest, GetNumChannels) {
|
||||
|
|
|
@ -47,7 +47,6 @@ class MockMediaPlaylist : public MediaPlaylist {
|
|||
MOCK_CONST_METHOD0(Bitrate, uint64_t());
|
||||
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
|
||||
MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration));
|
||||
MOCK_CONST_METHOD0(GetLanguage, std::string());
|
||||
MOCK_CONST_METHOD0(GetNumChannels, int());
|
||||
MOCK_CONST_METHOD2(GetDisplayResolution,
|
||||
bool(uint32_t* width, uint32_t* height));
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/media/base/language_utils.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/mpd_options.h"
|
||||
#include "packager/mpd/base/mpd_utils.h"
|
||||
|
@ -167,11 +166,11 @@ class RepresentationStateChangeListenerImpl
|
|||
|
||||
} // namespace
|
||||
|
||||
AdaptationSet::AdaptationSet(const std::string& lang,
|
||||
AdaptationSet::AdaptationSet(const std::string& language,
|
||||
const MpdOptions& mpd_options,
|
||||
base::AtomicSequenceNumber* counter)
|
||||
: representation_counter_(counter),
|
||||
lang_(lang),
|
||||
language_(language),
|
||||
mpd_options_(mpd_options),
|
||||
segments_aligned_(kSegmentAlignmentUnknown),
|
||||
force_set_segment_alignment_(false) {
|
||||
|
@ -246,8 +245,8 @@ xml::scoped_xml_ptr<xmlNode> AdaptationSet::GetXml() {
|
|||
if (id_)
|
||||
adaptation_set.SetId(id_.value());
|
||||
adaptation_set.SetStringAttribute("contentType", content_type_);
|
||||
if (!lang_.empty() && lang_ != "und") {
|
||||
adaptation_set.SetStringAttribute("lang", LanguageToShortestForm(lang_));
|
||||
if (!language_.empty() && language_ != "und") {
|
||||
adaptation_set.SetStringAttribute("lang", language_);
|
||||
}
|
||||
|
||||
// Note that std::{set,map} are ordered, so the last element is the max value.
|
||||
|
|
|
@ -170,13 +170,13 @@ class AdaptationSet {
|
|||
bool IsVideo() const;
|
||||
|
||||
protected:
|
||||
/// @param lang is the language of this AdaptationSet. Mainly relevant for
|
||||
/// @param language is the language of this AdaptationSet. Mainly relevant for
|
||||
/// audio.
|
||||
/// @param mpd_options is the options for this MPD.
|
||||
/// @param mpd_type is the type of this MPD.
|
||||
/// @param representation_counter is a Counter for assigning ID numbers to
|
||||
/// Representation. It can not be NULL.
|
||||
AdaptationSet(const std::string& lang,
|
||||
AdaptationSet(const std::string& language,
|
||||
const MpdOptions& mpd_options,
|
||||
base::AtomicSequenceNumber* representation_counter);
|
||||
|
||||
|
@ -237,7 +237,7 @@ class AdaptationSet {
|
|||
base::AtomicSequenceNumber* const representation_counter_;
|
||||
|
||||
base::Optional<uint32_t> id_;
|
||||
const std::string lang_;
|
||||
const std::string language_;
|
||||
const MpdOptions& mpd_options_;
|
||||
|
||||
// An array of adaptation sets this adaptation set can switch to.
|
||||
|
|
|
@ -155,14 +155,6 @@ TEST_F(AdaptationSetTest, CheckLanguageAttributeSet) {
|
|||
EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "en"));
|
||||
}
|
||||
|
||||
// Verify that language tags with subtags can still be converted.
|
||||
TEST_F(AdaptationSetTest, CheckConvertLanguageWithSubtag) {
|
||||
// "por-BR" is the long tag for Brazillian Portuguese. The short tag
|
||||
// is "pt-BR", which is what should appear in the manifest.
|
||||
auto adaptation_set = CreateAdaptationSet("por-BR");
|
||||
EXPECT_THAT(adaptation_set->GetXml().get(), AttributeEqual("lang", "pt-BR"));
|
||||
}
|
||||
|
||||
TEST_F(AdaptationSetTest, CheckAdaptationSetId) {
|
||||
auto adaptation_set = CreateAdaptationSet(kNoLanguage);
|
||||
const uint32_t kAdaptationSetId = 42;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "packager/base/logging.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/strings/string_util.h"
|
||||
#include "packager/media/base/language_utils.h"
|
||||
#include "packager/mpd/base/adaptation_set.h"
|
||||
#include "packager/mpd/base/content_protection_element.h"
|
||||
#include "packager/mpd/base/representation.h"
|
||||
|
@ -81,7 +82,7 @@ std::string GetLanguage(const MediaInfo& media_info) {
|
|||
} else if (media_info.has_text_info()) {
|
||||
lang = media_info.text_info().language();
|
||||
}
|
||||
return lang;
|
||||
return LanguageToShortestForm(lang);
|
||||
}
|
||||
|
||||
std::string GetCodecs(const MediaInfo& media_info) {
|
||||
|
|
|
@ -35,7 +35,7 @@ bool HasLiveOnlyFields(const MediaInfo& media_info);
|
|||
void RemoveDuplicateAttributes(
|
||||
ContentProtectionElement* content_protection_element);
|
||||
|
||||
// Returns a language tag. May be blank for video.
|
||||
// Returns a language in ISO-639 shortest form. May be blank for video.
|
||||
std::string GetLanguage(const MediaInfo& media_info);
|
||||
|
||||
// Returns a 'codecs' string that has all the video and audio codecs joined with
|
||||
|
|
|
@ -847,6 +847,13 @@ Status Packager::Initialize(
|
|||
hls_params.master_playlist_output = File::MakeCallbackFileName(
|
||||
internal->buffer_callback_params, hls_params.master_playlist_output);
|
||||
}
|
||||
// Both DASH and HLS require language to follow RFC5646
|
||||
// (https://tools.ietf.org/html/rfc5646), which requires the language to be
|
||||
// in the shortest form.
|
||||
mpd_params.default_language =
|
||||
LanguageToShortestForm(mpd_params.default_language);
|
||||
hls_params.default_language =
|
||||
LanguageToShortestForm(hls_params.default_language);
|
||||
|
||||
if (!mpd_params.mpd_output.empty()) {
|
||||
const bool on_demand_dash_profile =
|
||||
|
|
Loading…
Reference in New Issue