Shaka Packager SDK
packager_main.cc
1 // Copyright 2014 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include <gflags/gflags.h>
8 #include <iostream>
9 
10 #include "packager/app/ad_cue_generator_flags.h"
11 #include "packager/app/crypto_flags.h"
12 #include "packager/app/hls_flags.h"
13 #include "packager/app/manifest_flags.h"
14 #include "packager/app/mpd_flags.h"
15 #include "packager/app/muxer_flags.h"
16 #include "packager/app/packager_util.h"
17 #include "packager/app/playready_key_encryption_flags.h"
18 #include "packager/app/protection_system_flags.h"
19 #include "packager/app/raw_key_encryption_flags.h"
20 #include "packager/app/stream_descriptor.h"
21 #include "packager/app/vlog_flags.h"
22 #include "packager/app/widevine_encryption_flags.h"
23 #include "packager/base/command_line.h"
24 #include "packager/base/logging.h"
25 #include "packager/base/optional.h"
26 #include "packager/base/strings/string_number_conversions.h"
27 #include "packager/base/strings/string_split.h"
28 #include "packager/base/strings/string_util.h"
29 #include "packager/base/strings/stringprintf.h"
30 #include "packager/file/file.h"
31 #include "packager/packager.h"
32 #include "packager/tools/license_notice.h"
33 
34 #if defined(OS_WIN)
35 #include <codecvt>
36 #include <functional>
37 #include <locale>
38 #endif // defined(OS_WIN)
39 
40 DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
41 DEFINE_bool(licenses, false, "Dump licenses.");
42 DEFINE_bool(use_fake_clock_for_muxer,
43  false,
44  "Set to true to use a fake clock for muxer. With this flag set, "
45  "creation time and modification time in outputs are set to 0. "
46  "Should only be used for testing.");
47 DEFINE_string(test_packager_version,
48  "",
49  "Packager version for testing. Should be used for testing only.");
50 
51 namespace shaka {
52 namespace {
53 
54 const char kUsage[] =
55  "%s [flags] <stream_descriptor> ...\n\n"
56  " stream_descriptor consists of comma separated field_name/value pairs:\n"
57  " field_name=value,[field_name=value,]...\n"
58  " Supported field names are as follows (names in parenthesis are alias):\n"
59  " - input (in): Required input/source media file path or network stream\n"
60  " URL.\n"
61  " - stream_selector (stream): Required field with value 'audio',\n"
62  " 'video', 'text', or stream number (zero based).\n"
63  " - output (out,init_segment): Required output file (single file) or\n"
64  " initialization file path (multiple file).\n"
65  " - segment_template (segment): Optional value which specifies the\n"
66  " naming pattern for the segment files, and that the stream should be\n"
67  " split into multiple files. Its presence should be consistent across\n"
68  " streams.\n"
69  " - bandwidth (bw): Optional value which contains a user-specified\n"
70  " content bit rate for the stream, in bits/sec. If specified, this\n"
71  " value is propagated to (HLS) EXT-X-STREAM-INF:BANDWIDTH or (DASH)\n"
72  " Representation@bandwidth and the $Bandwidth$ template parameter for\n"
73  " segment names. If not specified, the bandwidth value is estimated\n"
74  " from content bitrate. Note that it only affects the generated\n"
75  " manifests/playlists; it has no effect on the media content itself.\n"
76  " - language (lang): Optional value which contains a user-specified\n"
77  " language tag. If specified, this value overrides any language\n"
78  " metadata in the input stream.\n"
79  " - output_format (format): Optional value which specifies the format\n"
80  " of the output files (MP4 or WebM). If not specified, it will be\n"
81  " derived from the file extension of the output file.\n"
82  " - skip_encryption=0|1: Optional. Defaults to 0 if not specified. If\n"
83  " it is set to 1, no encryption of the stream will be made.\n"
84  " - drm_label: Optional value for custom DRM label, which defines the\n"
85  " encryption key applied to the stream. Typical values include AUDIO,\n"
86  " SD, HD, UHD1, UHD2. For raw key, it should be a label defined in\n"
87  " --keys. If not provided, the DRM label is derived from stream type\n"
88  " (video, audio), resolution, etc.\n"
89  " Note that it is case sensitive.\n"
90  " - trick_play_factor (tpf): Optional value which specifies the trick\n"
91  " play, a.k.a. trick mode, stream sampling rate among key frames.\n"
92  " If specified, the output is a trick play stream.\n"
93  " - hls_name: Used for HLS audio to set the NAME attribute for\n"
94  " EXT-X-MEDIA. Defaults to the base of the playlist name.\n"
95  " - hls_group_id: Used for HLS audio to set the GROUP-ID attribute for\n"
96  " EXT-X-MEDIA. Defaults to 'audio' if not specified.\n"
97  " - playlist_name: The HLS playlist file to create. Usually ends with\n"
98  " '.m3u8', and is relative to --hls_master_playlist_output. If\n"
99  " unspecified, defaults to something of the form 'stream_0.m3u8',\n"
100  " 'stream_1.m3u8', 'stream_2.m3u8', etc.\n"
101  " - iframe_playlist_name: The optional HLS I-Frames only playlist file\n"
102  " to create. Usually ends with '.m3u8', and is relative to\n"
103  " hls_master_playlist_output. Should only be set for video streams. If\n"
104  " unspecified, no I-Frames only playlist is created.\n";
105 
106 // Labels for parameters in RawKey key info.
107 const char kDrmLabelLabel[] = "label";
108 const char kKeyIdLabel[] = "key_id";
109 const char kKeyLabel[] = "key";
110 
111 enum ExitStatus {
112  kSuccess = 0,
113  kArgumentValidationFailed,
114  kPackagingFailed,
115  kInternalError,
116 };
117 
118 bool GetWidevineSigner(WidevineSigner* signer) {
119  signer->signer_name = FLAGS_signer;
120  if (!FLAGS_aes_signing_key_bytes.empty()) {
121  signer->signing_key_type = WidevineSigner::SigningKeyType::kAes;
122  signer->aes.key = FLAGS_aes_signing_key_bytes;
123  signer->aes.iv = FLAGS_aes_signing_iv_bytes;
124  } else if (!FLAGS_rsa_signing_key_path.empty()) {
125  signer->signing_key_type = WidevineSigner::SigningKeyType::kRsa;
126  if (!File::ReadFileToString(FLAGS_rsa_signing_key_path.c_str(),
127  &signer->rsa.key)) {
128  LOG(ERROR) << "Failed to read from '" << FLAGS_rsa_signing_key_path
129  << "'.";
130  return false;
131  }
132  }
133  return true;
134 }
135 
136 bool GetHlsPlaylistType(const std::string& playlist_type,
137  HlsPlaylistType* playlist_type_enum) {
138  if (base::ToUpperASCII(playlist_type) == "VOD") {
139  *playlist_type_enum = HlsPlaylistType::kVod;
140  } else if (base::ToUpperASCII(playlist_type) == "LIVE") {
141  *playlist_type_enum = HlsPlaylistType::kLive;
142  } else if (base::ToUpperASCII(playlist_type) == "EVENT") {
143  *playlist_type_enum = HlsPlaylistType::kEvent;
144  } else {
145  LOG(ERROR) << "Unrecognized playlist type " << playlist_type;
146  return false;
147  }
148  return true;
149 }
150 
151 bool GetProtectionScheme(uint32_t* protection_scheme) {
152  if (FLAGS_protection_scheme == "cenc") {
153  *protection_scheme = EncryptionParams::kProtectionSchemeCenc;
154  return true;
155  }
156  if (FLAGS_protection_scheme == "cbc1") {
157  *protection_scheme = EncryptionParams::kProtectionSchemeCbc1;
158  return true;
159  }
160  if (FLAGS_protection_scheme == "cbcs") {
161  *protection_scheme = EncryptionParams::kProtectionSchemeCbcs;
162  return true;
163  }
164  if (FLAGS_protection_scheme == "cens") {
165  *protection_scheme = EncryptionParams::kProtectionSchemeCens;
166  return true;
167  }
168  LOG(ERROR) << "Unrecognized protection_scheme " << FLAGS_protection_scheme;
169  return false;
170 }
171 
172 bool ParseKeys(const std::string& keys, RawKeyParams* raw_key) {
173  for (const std::string& key_data : base::SplitString(
174  keys, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
175  base::StringPairs string_pairs;
176  base::SplitStringIntoKeyValuePairs(key_data, '=', ':', &string_pairs);
177 
178  std::map<std::string, std::string> value_map;
179  for (const auto& string_pair : string_pairs)
180  value_map[string_pair.first] = string_pair.second;
181  const std::string drm_label = value_map[kDrmLabelLabel];
182  if (raw_key->key_map.find(drm_label) != raw_key->key_map.end()) {
183  LOG(ERROR) << "Seeing duplicated DRM label '" << drm_label << "'.";
184  return false;
185  }
186  auto& key_info = raw_key->key_map[drm_label];
187  if (value_map[kKeyIdLabel].empty() ||
188  !base::HexStringToBytes(value_map[kKeyIdLabel], &key_info.key_id)) {
189  LOG(ERROR) << "Empty key id or invalid hex string for key id: "
190  << value_map[kKeyIdLabel];
191  return false;
192  }
193  if (value_map[kKeyLabel].empty() ||
194  !base::HexStringToBytes(value_map[kKeyLabel], &key_info.key)) {
195  LOG(ERROR) << "Empty key or invalid hex string for key: "
196  << value_map[kKeyLabel];
197  return false;
198  }
199  }
200  return true;
201 }
202 
203 bool GetRawKeyParams(RawKeyParams* raw_key) {
204  raw_key->iv = FLAGS_iv_bytes;
205  raw_key->pssh = FLAGS_pssh_bytes;
206  if (!FLAGS_keys.empty()) {
207  if (!ParseKeys(FLAGS_keys, raw_key)) {
208  LOG(ERROR) << "Failed to parse --keys " << FLAGS_keys;
209  return false;
210  }
211  } else {
212  // An empty StreamLabel specifies the default key info.
213  RawKeyParams::KeyInfo& key_info = raw_key->key_map[""];
214  key_info.key_id = FLAGS_key_id_bytes;
215  key_info.key = FLAGS_key_bytes;
216  }
217  return true;
218 }
219 
220 bool ParseAdCues(const std::string& ad_cues, std::vector<Cuepoint>* cuepoints) {
221  // Track if optional field is supplied consistently across all cue points.
222  size_t duration_count = 0;
223 
224  for (const std::string& ad_cue : base::SplitString(
225  ad_cues, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
226  Cuepoint cuepoint;
227  auto split_ad_cue = base::SplitString(ad_cue, ",", base::TRIM_WHITESPACE,
228  base::SPLIT_WANT_NONEMPTY);
229  if (split_ad_cue.size() > 2) {
230  LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
231  << " Each ad cue must contain no more than 2 components.";
232  }
233  if (!base::StringToDouble(split_ad_cue.front(),
234  &cuepoint.start_time_in_seconds)) {
235  LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
236  << " Start time component must be of type double.";
237  return false;
238  }
239  if (split_ad_cue.size() > 1) {
240  duration_count++;
241  if (!base::StringToDouble(split_ad_cue[1],
242  &cuepoint.duration_in_seconds)) {
243  LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
244  << " Duration component must be of type double.";
245  return false;
246  }
247  }
248  cuepoints->push_back(cuepoint);
249  }
250 
251  if (duration_count > 0 && duration_count != cuepoints->size()) {
252  LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
253  << " Duration component is optional. However if it is supplied,"
254  << " it must be supplied consistently across all cuepoints.";
255  return false;
256  }
257  return true;
258 }
259 
260 base::Optional<PackagingParams> GetPackagingParams() {
261  PackagingParams packaging_params;
262 
263  packaging_params.temp_dir = FLAGS_temp_dir;
264 
265  AdCueGeneratorParams& ad_cue_generator_params =
266  packaging_params.ad_cue_generator_params;
267  if (!ParseAdCues(FLAGS_ad_cues, &ad_cue_generator_params.cue_points)) {
268  return base::nullopt;
269  }
270 
271  ChunkingParams& chunking_params = packaging_params.chunking_params;
272  chunking_params.segment_duration_in_seconds = FLAGS_segment_duration;
273  chunking_params.subsegment_duration_in_seconds = FLAGS_fragment_duration;
274  chunking_params.segment_sap_aligned = FLAGS_segment_sap_aligned;
275  chunking_params.subsegment_sap_aligned = FLAGS_fragment_sap_aligned;
276 
277  int num_key_providers = 0;
278  EncryptionParams& encryption_params = packaging_params.encryption_params;
279  encryption_params.generate_common_pssh = FLAGS_generate_common_pssh;
280  encryption_params.generate_playready_pssh = FLAGS_generate_playready_pssh;
281  encryption_params.generate_widevine_pssh = FLAGS_generate_widevine_pssh;
282  if (FLAGS_enable_widevine_encryption) {
283  encryption_params.key_provider = KeyProvider::kWidevine;
284  ++num_key_providers;
285  }
286  if (FLAGS_enable_playready_encryption) {
287  encryption_params.key_provider = KeyProvider::kPlayReady;
288  ++num_key_providers;
289  }
290  if (FLAGS_enable_raw_key_encryption) {
291  encryption_params.key_provider = KeyProvider::kRawKey;
292  ++num_key_providers;
293  }
294  if (num_key_providers > 1) {
295  LOG(ERROR) << "Only one of --enable_widevine_encryption, "
296  "--enable_playready_encryption, "
297  "--enable_raw_key_encryption can be enabled.";
298  return base::nullopt;
299  }
300 
301  if (encryption_params.key_provider != KeyProvider::kNone) {
302  encryption_params.clear_lead_in_seconds = FLAGS_clear_lead;
303  if (!GetProtectionScheme(&encryption_params.protection_scheme))
304  return base::nullopt;
305  encryption_params.crypto_period_duration_in_seconds =
306  FLAGS_crypto_period_duration;
307  encryption_params.vp9_subsample_encryption = FLAGS_vp9_subsample_encryption;
308  encryption_params.stream_label_func = std::bind(
309  &Packager::DefaultStreamLabelFunction, FLAGS_max_sd_pixels,
310  FLAGS_max_hd_pixels, FLAGS_max_uhd1_pixels, std::placeholders::_1);
311  }
312  switch (encryption_params.key_provider) {
313  case KeyProvider::kWidevine: {
314  WidevineEncryptionParams& widevine = encryption_params.widevine;
315  widevine.key_server_url = FLAGS_key_server_url;
316 
317  widevine.content_id = FLAGS_content_id_bytes;
318  widevine.policy = FLAGS_policy;
319  widevine.group_id = FLAGS_group_id_bytes;
320  widevine.enable_entitlement_license = FLAGS_enable_entitlement_license;
321  if (!GetWidevineSigner(&widevine.signer))
322  return base::nullopt;
323  break;
324  }
325  case KeyProvider::kPlayReady: {
326  PlayReadyEncryptionParams& playready = encryption_params.playready;
327  playready.key_server_url = FLAGS_playready_server_url;
328  playready.program_identifier = FLAGS_program_identifier;
329  playready.ca_file = FLAGS_ca_file;
330  playready.client_cert_file = FLAGS_client_cert_file;
331  playready.client_cert_private_key_file =
332  FLAGS_client_cert_private_key_file;
333  playready.client_cert_private_key_password =
334  FLAGS_client_cert_private_key_password;
335  break;
336  }
337  case KeyProvider::kRawKey: {
338  if (!GetRawKeyParams(&encryption_params.raw_key))
339  return base::nullopt;
340  break;
341  }
342  case KeyProvider::kNone:
343  break;
344  }
345 
346  num_key_providers = 0;
347  DecryptionParams& decryption_params = packaging_params.decryption_params;
348  if (FLAGS_enable_widevine_decryption) {
349  decryption_params.key_provider = KeyProvider::kWidevine;
350  ++num_key_providers;
351  }
352  if (FLAGS_enable_raw_key_decryption) {
353  decryption_params.key_provider = KeyProvider::kRawKey;
354  ++num_key_providers;
355  }
356  if (num_key_providers > 1) {
357  LOG(ERROR) << "Only one of --enable_widevine_decryption, "
358  "--enable_raw_key_decryption can be enabled.";
359  return base::nullopt;
360  }
361  switch (decryption_params.key_provider) {
362  case KeyProvider::kWidevine: {
363  WidevineDecryptionParams& widevine = decryption_params.widevine;
364  widevine.key_server_url = FLAGS_key_server_url;
365  if (!GetWidevineSigner(&widevine.signer))
366  return base::nullopt;
367  break;
368  }
369  case KeyProvider::kRawKey: {
370  if (!GetRawKeyParams(&decryption_params.raw_key))
371  return base::nullopt;
372  break;
373  }
374  case KeyProvider::kPlayReady:
375  case KeyProvider::kNone:
376  break;
377  }
378 
379  Mp4OutputParams& mp4_params = packaging_params.mp4_output_params;
380  mp4_params.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;
381  if (FLAGS_mp4_use_decoding_timestamp_in_timeline) {
382  LOG(WARNING) << "Flag --mp4_use_decoding_timestamp_in_timeline is set. "
383  "Note that it is a temporary hack to workaround Chromium "
384  "bug https://crbug.com/398130. The flag may be removed "
385  "when the Chromium bug is fixed.";
386  }
387  mp4_params.use_decoding_timestamp_in_timeline =
388  FLAGS_mp4_use_decoding_timestamp_in_timeline;
389  mp4_params.include_pssh_in_stream = FLAGS_mp4_include_pssh_in_stream;
390 
391  packaging_params.output_media_info = FLAGS_output_media_info;
392 
393  MpdParams& mpd_params = packaging_params.mpd_params;
394  mpd_params.mpd_output = FLAGS_mpd_output;
395  mpd_params.base_urls = base::SplitString(
396  FLAGS_base_urls, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
397  mpd_params.min_buffer_time = FLAGS_min_buffer_time;
398  mpd_params.minimum_update_period = FLAGS_minimum_update_period;
399  mpd_params.suggested_presentation_delay = FLAGS_suggested_presentation_delay;
400  mpd_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
401  mpd_params.preserved_segments_outside_live_window =
402  FLAGS_preserved_segments_outside_live_window;
403 
404  if (!FLAGS_utc_timings.empty()) {
405  base::StringPairs pairs;
406  if (!base::SplitStringIntoKeyValuePairs(FLAGS_utc_timings, '=', ',',
407  &pairs)) {
408  LOG(ERROR) << "Invalid --utc_timings scheme_id_uri/value pairs.";
409  return base::nullopt;
410  }
411  for (const auto& string_pair : pairs) {
412  mpd_params.utc_timings.push_back({string_pair.first, string_pair.second});
413  }
414  }
415 
416  mpd_params.default_language = FLAGS_default_language;
417  mpd_params.generate_static_live_mpd = FLAGS_generate_static_mpd;
418  mpd_params.generate_dash_if_iop_compliant_mpd =
419  FLAGS_generate_dash_if_iop_compliant_mpd;
420  mpd_params.allow_approximate_segment_timeline =
421  FLAGS_allow_approximate_segment_timeline;
422 
423  HlsParams& hls_params = packaging_params.hls_params;
424  if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) {
425  return base::nullopt;
426  }
427  hls_params.master_playlist_output = FLAGS_hls_master_playlist_output;
428  hls_params.base_url = FLAGS_hls_base_url;
429  hls_params.key_uri = FLAGS_hls_key_uri;
430  hls_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
431  hls_params.preserved_segments_outside_live_window =
432  FLAGS_preserved_segments_outside_live_window;
433  hls_params.default_language = FLAGS_default_language;
434 
435  TestParams& test_params = packaging_params.test_params;
436  test_params.dump_stream_info = FLAGS_dump_stream_info;
437  test_params.inject_fake_clock = FLAGS_use_fake_clock_for_muxer;
438  if (!FLAGS_test_packager_version.empty())
439  test_params.injected_library_version = FLAGS_test_packager_version;
440 
441  return packaging_params;
442 }
443 
444 int PackagerMain(int argc, char** argv) {
445  // Needed to enable VLOG/DVLOG through --vmodule or --v.
446  base::CommandLine::Init(argc, argv);
447 
448  // Set up logging.
449  logging::LoggingSettings log_settings;
450  log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
451  CHECK(logging::InitLogging(log_settings));
452 
453  google::SetVersionString(shaka::Packager::GetLibraryVersion());
454  google::SetUsageMessage(base::StringPrintf(kUsage, argv[0]));
455  google::ParseCommandLineFlags(&argc, &argv, true);
456  if (FLAGS_licenses) {
457  for (const char* line : kLicenseNotice)
458  std::cout << line << std::endl;
459  return kSuccess;
460  }
461  if (argc < 2) {
462  google::ShowUsageWithFlags("Usage");
463  return kSuccess;
464  }
465 
468  return kArgumentValidationFailed;
469  }
470 
471  base::Optional<PackagingParams> packaging_params = GetPackagingParams();
472  if (!packaging_params)
473  return kArgumentValidationFailed;
474 
475  std::vector<StreamDescriptor> stream_descriptors;
476  for (int i = 1; i < argc; ++i) {
477  base::Optional<StreamDescriptor> stream_descriptor =
478  ParseStreamDescriptor(argv[i]);
479  if (!stream_descriptor)
480  return kArgumentValidationFailed;
481  stream_descriptors.push_back(stream_descriptor.value());
482  }
483  Packager packager;
484  Status status =
485  packager.Initialize(packaging_params.value(), stream_descriptors);
486  if (!status.ok()) {
487  LOG(ERROR) << "Failed to initialize packager: " << status.ToString();
488  return kArgumentValidationFailed;
489  }
490  status = packager.Run();
491  if (!status.ok()) {
492  LOG(ERROR) << "Packaging Error: " << status.ToString();
493  return kPackagingFailed;
494  }
495  printf("Packaging completed successfully.\n");
496  return kSuccess;
497 }
498 
499 } // namespace
500 } // namespace shaka
501 
502 #if defined(OS_WIN)
503 // Windows wmain, which converts wide character arguments to UTF-8.
504 int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) {
505  std::unique_ptr<char* [], std::function<void(char**)>> utf8_argv(
506  new char*[argc], [argc](char** utf8_args) {
507  // TODO(tinskip): This leaks, but if this code is enabled, it crashes.
508  // Figure out why. I suspect gflags does something funny with the
509  // argument array.
510  // for (int idx = 0; idx < argc; ++idx)
511  // delete[] utf8_args[idx];
512  delete[] utf8_args;
513  });
514  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
515  for (int idx = 0; idx < argc; ++idx) {
516  std::string utf8_arg(converter.to_bytes(argv[idx]));
517  utf8_arg += '\0';
518  utf8_argv[idx] = new char[utf8_arg.size()];
519  memcpy(utf8_argv[idx], &utf8_arg[0], utf8_arg.size());
520  }
521  return shaka::PackagerMain(argc, utf8_argv.get());
522 }
523 #else
524 int main(int argc, char** argv) {
525  return shaka::PackagerMain(argc, argv);
526 }
527 #endif // defined(OS_WIN)
static std::string DefaultStreamLabelFunction(int max_sd_pixels, int max_hd_pixels, int max_uhd1_pixels, const EncryptionParams::EncryptedStreamAttributes &stream_attributes)
Definition: packager.cc:940
HlsPlaylistType
Definition: hls_params.h:16
base::Optional< StreamDescriptor > ParseStreamDescriptor(const std::string &descriptor_string)
static bool ReadFileToString(const char *file_name, std::string *contents)
Definition: file.cc:216
bool ValidateRawKeyCryptoFlags()
bool ValidateWidevineCryptoFlags()
All the methods that are virtual are virtual for mocking.
static std::string GetLibraryVersion()
Definition: packager.cc:936
static constexpr uint32_t kProtectionSchemeCenc
The protection scheme: "cenc", "cens", "cbc1", "cbcs".