Shaka Packager SDK
Loading...
Searching...
No Matches
packager_main.cc
1// Copyright 2014 Google LLC. 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 <cstddef>
8#include <cstdint>
9#include <cstdio>
10#include <functional>
11#include <iostream>
12#include <map>
13#include <optional>
14#include <string>
15#include <vector>
16
17#include <absl/base/log_severity.h>
18#include <absl/flags/declare.h>
19#include <absl/strings/ascii.h>
20#include <absl/strings/string_view.h>
21
22#include <packager/ad_cue_generator_params.h>
23#include <packager/cea_caption.h>
24#include <packager/chunking_params.h>
25#include <packager/crypto_params.h>
26#include <packager/hls_params.h>
27#include <packager/mp4_output_params.h>
28#include <packager/mpd_params.h>
29#include <packager/packager.h>
30#include <packager/status.h>
31
32#if defined(OS_WIN)
33#include <codecvt>
34#endif // defined(OS_WIN)
35
36#include <absl/flags/flag.h>
37#include <absl/flags/parse.h>
38#include <absl/flags/usage.h>
39#include <absl/flags/usage_config.h>
40#include <absl/log/globals.h>
41#include <absl/log/initialize.h>
42#include <absl/log/log.h>
43#include <absl/strings/numbers.h>
44#include <absl/strings/str_format.h>
45
46#include <packager/app/ad_cue_generator_flags.h>
47#include <packager/app/cpix_encryption_flags.h>
48#include <packager/app/crypto_flags.h>
49#include <packager/app/hls_flags.h>
50#include <packager/app/manifest_flags.h>
51#include <packager/app/mpd_flags.h>
52#include <packager/app/muxer_flags.h>
53#include <packager/app/playready_key_encryption_flags.h>
54#include <packager/app/protection_system_flags.h>
55#include <packager/app/raw_key_encryption_flags.h>
56#include <packager/app/retired_flags.h>
57#include <packager/app/stream_descriptor.h>
58#include <packager/app/widevine_encryption_flags.h>
59#include <packager/file.h>
60#include <packager/kv_pairs/kv_pairs.h>
61#include <packager/tools/license_notice.h>
62#include <packager/utils/hex_parser.h>
63#include <packager/utils/string_trim_split.h>
64
65ABSL_FLAG(bool, dump_stream_info, false, "Dump demuxed stream info.");
66ABSL_FLAG(bool, licenses, false, "Dump licenses.");
67ABSL_FLAG(bool, quiet, false, "When enabled, LOG(INFO) output is suppressed.");
68ABSL_FLAG(bool,
69 use_fake_clock_for_muxer,
70 false,
71 "Set to true to use a fake clock for muxer. With this flag set, "
72 "creation time and modification time in outputs are set to 0. "
73 "Should only be used for testing.");
74ABSL_FLAG(std::string,
75 test_packager_version,
76 "",
77 "Packager version for testing. Should be used for testing only.");
78ABSL_FLAG(bool,
79 single_threaded,
80 false,
81 "If enabled, only use one thread when generating content.");
82
83// From absl/log:
84ABSL_DECLARE_FLAG(int, stderrthreshold);
85
86namespace shaka {
87namespace {
88
89const char kUsage[] =
90 "%s [flags] <stream_descriptor> ...\n\n"
91 " stream_descriptor consists of comma separated field_name/value pairs:\n"
92 " field_name=value,[field_name=value,]...\n"
93 " Supported field names are as follows (names in parenthesis are alias):\n"
94 " - input (in): Required input/source media file path or network stream\n"
95 " URL.\n"
96 " - stream_selector (stream): Required field with value 'audio',\n"
97 " 'video', 'text', or stream number (zero based).\n"
98 " - output (out,init_segment): Required output file (single file) or\n"
99 " initialization file path (multiple file).\n"
100 " - segment_template (segment): Optional value which specifies the\n"
101 " naming pattern for the segment files, and that the stream should be\n"
102 " split into multiple files. Its presence should be consistent across\n"
103 " streams.\n"
104 " - bandwidth (bw): Optional value which contains a user-specified\n"
105 " maximum bit rate for the stream, in bits/sec. If specified, this\n"
106 " value is propagated to (HLS) EXT-X-STREAM-INF:BANDWIDTH or (DASH)\n"
107 " Representation@bandwidth and the $Bandwidth$ template parameter for\n"
108 " segment names. If not specified, the bandwidth value is estimated\n"
109 " from content bitrate. Note that it only affects the generated\n"
110 " manifests/playlists; it has no effect on the media content itself.\n"
111 " - language (lang): Optional value which contains a user-specified\n"
112 " language tag. If specified, this value overrides any language\n"
113 " metadata in the input stream.\n"
114 " - output_format (format): Optional value which specifies the format\n"
115 " of the output files (MP4 or WebM). If not specified, it will be\n"
116 " derived from the file extension of the output file.\n"
117 " - input_format (format): Optional value which specifies the format\n"
118 " of the input files or streams. If not specified, it will be\n"
119 " autodetected, which in some cases (such as live UDP webvtt) may\n"
120 " fail.\n"
121 " - skip_encryption=0|1: Optional. Defaults to 0 if not specified. If\n"
122 " it is set to 1, no encryption of the stream will be made.\n"
123 " - drm_label: Optional value for custom DRM label, which defines the\n"
124 " encryption key applied to the stream. Typical values include AUDIO,\n"
125 " SD, HD, UHD1, UHD2. For raw key, it should be a label defined in\n"
126 " --keys. For CPIX, it should match an intendedTrackType in the\n"
127 " document. If not provided, the DRM label is derived from stream\n"
128 " type (video, audio), resolution, etc.\n"
129 " Note that it is case sensitive.\n"
130 " - trick_play_factor (tpf): Optional value which specifies the trick\n"
131 " play, a.k.a. trick mode, stream sampling rate among key frames.\n"
132 " If specified, the output is a trick play stream.\n"
133 " - hls_name: Used for HLS audio to set the NAME attribute for\n"
134 " EXT-X-MEDIA. Defaults to the base of the playlist name.\n"
135 " - hls_group_id: Used for HLS audio to set the GROUP-ID attribute for\n"
136 " EXT-X-MEDIA. Defaults to 'audio' if not specified.\n"
137 " - playlist_name: The HLS playlist file to create. Usually ends with\n"
138 " '.m3u8', and is relative to --hls_master_playlist_output. If\n"
139 " unspecified, defaults to something of the form 'stream_0.m3u8',\n"
140 " 'stream_1.m3u8', 'stream_2.m3u8', etc.\n"
141 " - iframe_playlist_name: The optional HLS I-Frames only playlist file\n"
142 " to create. Usually ends with '.m3u8', and is relative to\n"
143 " hls_master_playlist_output. Should only be set for video streams. If\n"
144 " unspecified, no I-Frames only playlist is created.\n"
145 " - hls_characteristics (charcs): Optional colon/semicolon separated\n"
146 " list of values for the CHARACTERISTICS attribute for EXT-X-MEDIA.\n"
147 " See CHARACTERISTICS attribute in http://bit.ly/2OOUkdB for details.\n"
148 " - dash_accessibilities (accessibilities): Optional semicolon separated\n"
149 " list of values for DASH Accessibility elements. The value should be\n"
150 " in the format: scheme_id_uri=value.\n"
151 " - dash_roles (roles): Optional semicolon separated list of values for\n"
152 " DASH Role elements. The value should be one of: caption, subtitle, \n"
153 " main, alternate, supplementary, commentary, dub, description, sign, \n"
154 " metadata, enhanced-audio- intelligibility, emergency, \n"
155 " forced-subtitle, easyreader, and karaoke. See DASH\n"
156 " (ISO/IEC 23009-1) specification for details.\n"
157 " - forced_subtitle: Optional boolean value (0|1). If set to 1 \n"
158 " indicates that this stream is a Forced Narrative subtitle that \n"
159 " should be displayed when subtitles are otherwise off, for example \n"
160 " used to caption short portions of the audio that might be in a \n"
161 " foreign language. For DASH this will set role to forced_subtitle, \n"
162 " for HLS it will set FORCED=YES and AUTOSELECT=YES. \n"
163 " Only valid for subtitles.\n";
164
165// Labels for parameters in RawKey key info.
166const char kDrmLabelLabel[] = "label";
167const char kKeyIdLabel[] = "key_id";
168const char kKeyLabel[] = "key";
169const char kKeyIvLabel[] = "iv";
170
171enum ExitStatus {
172 kSuccess = 0,
173 kArgumentValidationFailed,
174 kPackagingFailed,
175 kInternalError,
176};
177
178bool GetWidevineSigner(WidevineSigner* signer) {
179 signer->signer_name = absl::GetFlag(FLAGS_signer);
180 if (!absl::GetFlag(FLAGS_aes_signing_key).bytes.empty()) {
181 signer->signing_key_type = WidevineSigner::SigningKeyType::kAes;
182 signer->aes.key = absl::GetFlag(FLAGS_aes_signing_key).bytes;
183 signer->aes.iv = absl::GetFlag(FLAGS_aes_signing_iv).bytes;
184 } else if (!absl::GetFlag(FLAGS_rsa_signing_key_path).empty()) {
185 signer->signing_key_type = WidevineSigner::SigningKeyType::kRsa;
186 if (!File::ReadFileToString(
187 absl::GetFlag(FLAGS_rsa_signing_key_path).c_str(),
188 &signer->rsa.key)) {
189 LOG(ERROR) << "Failed to read from '"
190 << absl::GetFlag(FLAGS_rsa_signing_key_path) << "'.";
191 return false;
192 }
193 }
194 return true;
195}
196
197bool GetHlsPlaylistType(const std::string& playlist_type,
198 HlsPlaylistType* playlist_type_enum) {
199 if (absl::AsciiStrToUpper(playlist_type) == "VOD") {
200 *playlist_type_enum = HlsPlaylistType::kVod;
201 } else if (absl::AsciiStrToUpper(playlist_type) == "LIVE") {
202 *playlist_type_enum = HlsPlaylistType::kLive;
203 } else if (absl::AsciiStrToUpper(playlist_type) == "EVENT") {
204 *playlist_type_enum = HlsPlaylistType::kEvent;
205 } else {
206 LOG(ERROR) << "Unrecognized playlist type " << playlist_type;
207 return false;
208 }
209 return true;
210}
211
212bool GetProtectionScheme(uint32_t* protection_scheme) {
213 if (absl::GetFlag(FLAGS_protection_scheme) == "cenc") {
214 *protection_scheme = EncryptionParams::kProtectionSchemeCenc;
215 return true;
216 }
217 if (absl::GetFlag(FLAGS_protection_scheme) == "cbc1") {
218 *protection_scheme = EncryptionParams::kProtectionSchemeCbc1;
219 return true;
220 }
221 if (absl::GetFlag(FLAGS_protection_scheme) == "cbcs") {
222 *protection_scheme = EncryptionParams::kProtectionSchemeCbcs;
223 return true;
224 }
225 if (absl::GetFlag(FLAGS_protection_scheme) == "cens") {
226 *protection_scheme = EncryptionParams::kProtectionSchemeCens;
227 return true;
228 }
229 if (absl::GetFlag(FLAGS_protection_scheme) == "aes128") {
230 *protection_scheme = EncryptionParams::kProtectionSchemeAes128;
231 return true;
232 }
233 LOG(ERROR) << "Unrecognized protection_scheme "
234 << absl::GetFlag(FLAGS_protection_scheme);
235 return false;
236}
237
238bool ParseKeys(const std::string& keys, RawKeyParams* raw_key) {
239 std::vector<std::string> keys_data = SplitAndTrimSkipEmpty(keys, ',');
240
241 for (const std::string& key_data : keys_data) {
242 std::vector<KVPair> string_pairs =
243 SplitStringIntoKeyValuePairs(key_data, '=', ':');
244
245 std::map<std::string, std::string> value_map;
246 for (const auto& string_pair : string_pairs)
247 value_map[string_pair.first] = string_pair.second;
248 const std::string drm_label = value_map[kDrmLabelLabel];
249 if (raw_key->key_map.find(drm_label) != raw_key->key_map.end()) {
250 LOG(ERROR) << "Seeing duplicated DRM label '" << drm_label << "'.";
251 return false;
252 }
253 auto& key_info = raw_key->key_map[drm_label];
254 if (value_map[kKeyIdLabel].empty() ||
255 !shaka::ValidHexStringToBytes(value_map[kKeyIdLabel],
256 &key_info.key_id)) {
257 LOG(ERROR) << "Empty key id or invalid hex string for key id: "
258 << value_map[kKeyIdLabel];
259 return false;
260 }
261 if (value_map[kKeyLabel].empty() ||
262 !shaka::ValidHexStringToBytes(value_map[kKeyLabel], &key_info.key)) {
263 LOG(ERROR) << "Empty key or invalid hex string for key: "
264 << value_map[kKeyLabel];
265 return false;
266 }
267 if (!value_map[kKeyIvLabel].empty()) {
268 if (!raw_key->iv.empty()) {
269 LOG(ERROR) << "IV already specified with --iv";
270 return false;
271 }
272 if (!shaka::ValidHexStringToBytes(value_map[kKeyIvLabel], &key_info.iv)) {
273 LOG(ERROR) << "Empty IV or invalid hex string for IV: "
274 << value_map[kKeyIvLabel];
275 return false;
276 }
277 }
278 }
279 return true;
280}
281
282bool GetRawKeyParams(RawKeyParams* raw_key) {
283 raw_key->iv = absl::GetFlag(FLAGS_iv).bytes;
284 raw_key->pssh = absl::GetFlag(FLAGS_pssh).bytes;
285 if (!absl::GetFlag(FLAGS_keys).empty()) {
286 if (!ParseKeys(absl::GetFlag(FLAGS_keys), raw_key)) {
287 LOG(ERROR) << "Failed to parse --keys " << absl::GetFlag(FLAGS_keys);
288 return false;
289 }
290 } else {
291 // An empty StreamLabel specifies the default key info.
292 RawKeyParams::KeyInfo& key_info = raw_key->key_map[""];
293 key_info.key_id = absl::GetFlag(FLAGS_key_id).bytes;
294 key_info.key = absl::GetFlag(FLAGS_key).bytes;
295 }
296 return true;
297}
298
299bool ParseAdCues(const std::string& ad_cues, std::vector<Cuepoint>* cuepoints) {
300 // Track if optional field is supplied consistently across all cue points.
301 size_t duration_count = 0;
302 std::vector<std::string> ad_cues_vec = SplitAndTrimSkipEmpty(ad_cues, ';');
303
304 for (const std::string& ad_cue : ad_cues_vec) {
305 Cuepoint cuepoint;
306 std::vector<std::string> split_ad_cue = SplitAndTrimSkipEmpty(ad_cue, ',');
307
308 if (split_ad_cue.size() > 2) {
309 LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
310 << " Each ad cue must contain no more than 2 components.";
311 }
312 if (!absl::SimpleAtod(split_ad_cue.front(),
313 &cuepoint.start_time_in_seconds)) {
314 LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
315 << " Start time component must be of type double.";
316 return false;
317 }
318 if (split_ad_cue.size() > 1) {
319 duration_count++;
320 if (!absl::SimpleAtod(split_ad_cue[1], &cuepoint.duration_in_seconds)) {
321 LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
322 << " Duration component must be of type double.";
323 return false;
324 }
325 }
326 cuepoints->push_back(cuepoint);
327 }
328
329 if (duration_count > 0 && duration_count != cuepoints->size()) {
330 LOG(ERROR) << "Failed to parse --ad_cues " << ad_cues
331 << " Duration component is optional. However if it is supplied,"
332 << " it must be supplied consistently across all cuepoints.";
333 return false;
334 }
335 return true;
336}
337
338bool ParseProtectionSystems(const std::string& protection_systems_str,
339 ProtectionSystem* protection_systems) {
340 *protection_systems = ProtectionSystem::kNone;
341
342 std::map<std::string, ProtectionSystem> mapping = {
343 {"common", ProtectionSystem::kCommon},
344 {"commonsystem", ProtectionSystem::kCommon},
345 {"fairplay", ProtectionSystem::kFairPlay},
346 {"marlin", ProtectionSystem::kMarlin},
347 {"playready", ProtectionSystem::kPlayReady},
348 {"widevine", ProtectionSystem::kWidevine},
349 };
350
351 std::vector<std::string> protection_systems_vec =
352 SplitAndTrimSkipEmpty(absl::AsciiStrToLower(protection_systems_str), ',');
353
354 for (const std::string& protection_system : protection_systems_vec) {
355 auto iter = mapping.find(protection_system);
356 if (iter == mapping.end()) {
357 LOG(ERROR) << "Seeing unrecognized protection system: "
358 << protection_system;
359 return false;
360 }
361 *protection_systems |= iter->second;
362 }
363 return true;
364}
365
366bool ParseClosedCaptions(const std::string& captions_str,
367 std::vector<CeaCaption>* captions) {
368 std::vector<std::string> captions_list =
369 SplitAndTrimSkipEmpty(captions_str, ';');
370 for (const std::string& caption_str : captions_list) {
371 CeaCaption caption;
372 std::vector<KVPair> caption_parts =
373 SplitStringIntoKeyValuePairs(caption_str, '=', ',');
374 for (const auto& part : caption_parts) {
375 if (part.first == "channel") {
376 caption.channel = part.second;
377 } else if (part.first == "lang") {
378 caption.language = part.second;
379 } else if (part.first == "name") {
380 caption.name = part.second;
381 } else if (part.first == "default") {
382 caption.is_default = (part.second == "yes");
383 } else if (part.first == "autoselect") {
384 caption.autoselect = (part.second == "yes");
385 }
386 }
387 if (caption.channel.empty()) {
388 LOG(ERROR) << "Missing channel in CEA caption: " << caption_str;
389 return false;
390 }
391 if (caption.name.empty()) {
392 caption.name = caption.channel;
393 }
394 captions->push_back(caption);
395 }
396 return true;
397}
398
399std::optional<PackagingParams> GetPackagingParams() {
400 PackagingParams packaging_params;
401
402 packaging_params.temp_dir = absl::GetFlag(FLAGS_temp_dir);
403 packaging_params.single_threaded = absl::GetFlag(FLAGS_single_threaded);
404
405 AdCueGeneratorParams& ad_cue_generator_params =
406 packaging_params.ad_cue_generator_params;
407 if (!ParseAdCues(absl::GetFlag(FLAGS_ad_cues),
408 &ad_cue_generator_params.cue_points)) {
409 return std::nullopt;
410 }
411
412 ChunkingParams& chunking_params = packaging_params.chunking_params;
413 chunking_params.segment_duration_in_seconds =
414 absl::GetFlag(FLAGS_segment_duration);
415 chunking_params.subsegment_duration_in_seconds =
416 absl::GetFlag(FLAGS_fragment_duration);
417 chunking_params.low_latency_dash_mode =
418 absl::GetFlag(FLAGS_low_latency_dash_mode);
419 chunking_params.segment_sap_aligned =
420 absl::GetFlag(FLAGS_segment_sap_aligned);
421 chunking_params.subsegment_sap_aligned =
422 absl::GetFlag(FLAGS_fragment_sap_aligned);
423 chunking_params.start_segment_number =
424 absl::GetFlag(FLAGS_start_segment_number);
425 chunking_params.ts_ttx_heartbeat_shift =
426 absl::GetFlag(FLAGS_ts_ttx_heartbeat_shift);
427
428 int num_key_providers = 0;
429 EncryptionParams& encryption_params = packaging_params.encryption_params;
430 if (absl::GetFlag(FLAGS_enable_widevine_encryption)) {
431 encryption_params.key_provider = KeyProvider::kWidevine;
432 ++num_key_providers;
433 }
434 if (absl::GetFlag(FLAGS_enable_playready_encryption)) {
435 encryption_params.key_provider = KeyProvider::kPlayReady;
436 ++num_key_providers;
437 }
438 if (absl::GetFlag(FLAGS_enable_raw_key_encryption)) {
439 encryption_params.key_provider = KeyProvider::kRawKey;
440 ++num_key_providers;
441 }
442 if (absl::GetFlag(FLAGS_enable_cpix_encryption)) {
443 encryption_params.key_provider = KeyProvider::kCpix;
444 ++num_key_providers;
445 }
446 if (num_key_providers > 1) {
447 LOG(ERROR) << "Only one of --enable_widevine_encryption, "
448 "--enable_playready_encryption, "
449 "--enable_raw_key_encryption, "
450 "--enable_cpix_encryption can be enabled.";
451 return std::nullopt;
452 }
453
454 if (!ParseProtectionSystems(absl::GetFlag(FLAGS_protection_systems),
455 &encryption_params.protection_systems)) {
456 return std::nullopt;
457 }
458
459 if (encryption_params.key_provider != KeyProvider::kNone) {
460 encryption_params.clear_lead_in_seconds = absl::GetFlag(FLAGS_clear_lead);
461 if (!GetProtectionScheme(&encryption_params.protection_scheme))
462 return std::nullopt;
463 encryption_params.crypt_byte_block = absl::GetFlag(FLAGS_crypt_byte_block);
464 encryption_params.skip_byte_block = absl::GetFlag(FLAGS_skip_byte_block);
465
466 encryption_params.crypto_period_duration_in_seconds =
467 absl::GetFlag(FLAGS_crypto_period_duration);
468 if (encryption_params.crypto_period_duration_in_seconds != 0 &&
469 encryption_params.key_provider == KeyProvider::kCpix) {
470 LOG(ERROR) << "--crypto_period_duration (key rotation) is not "
471 "supported with --enable_cpix_encryption.";
472 return std::nullopt;
473 }
474 encryption_params.vp9_subsample_encryption =
475 absl::GetFlag(FLAGS_vp9_subsample_encryption);
476 encryption_params.cencv1 = absl::GetFlag(FLAGS_cencv1);
477 encryption_params.stream_label_func = std::bind(
478 &Packager::DefaultStreamLabelFunction,
479 absl::GetFlag(FLAGS_max_sd_pixels), absl::GetFlag(FLAGS_max_hd_pixels),
480 absl::GetFlag(FLAGS_max_uhd1_pixels), std::placeholders::_1);
481 encryption_params.playready_extra_header_data =
482 absl::GetFlag(FLAGS_playready_extra_header_data);
483 }
484 switch (encryption_params.key_provider) {
485 case KeyProvider::kWidevine: {
486 WidevineEncryptionParams& widevine = encryption_params.widevine;
487 widevine.key_server_url = absl::GetFlag(FLAGS_key_server_url);
488
489 widevine.content_id = absl::GetFlag(FLAGS_content_id).bytes;
490 widevine.policy = absl::GetFlag(FLAGS_policy);
491 widevine.group_id = absl::GetFlag(FLAGS_group_id).bytes;
492 widevine.enable_entitlement_license =
493 absl::GetFlag(FLAGS_enable_entitlement_license);
494 if (!GetWidevineSigner(&widevine.signer))
495 return std::nullopt;
496 break;
497 }
498 case KeyProvider::kPlayReady: {
499 PlayReadyEncryptionParams& playready = encryption_params.playready;
500 playready.key_server_url = absl::GetFlag(FLAGS_playready_server_url);
501 playready.program_identifier = absl::GetFlag(FLAGS_program_identifier);
502 break;
503 }
504 case KeyProvider::kRawKey: {
505 if (!GetRawKeyParams(&encryption_params.raw_key))
506 return std::nullopt;
507 break;
508 }
509 case KeyProvider::kCpix: {
510 CpixEncryptionParams& cpix = encryption_params.cpix;
511 cpix.document_source = absl::GetFlag(FLAGS_cpix);
512 cpix.request_document_source = absl::GetFlag(FLAGS_cpix_request_file);
513 cpix.headers =
514 SplitAndTrimSkipEmpty(absl::GetFlag(FLAGS_cpix_headers), ';');
515 cpix.private_key_source = absl::GetFlag(FLAGS_cpix_private_key);
516 cpix.max_sd_pixels = absl::GetFlag(FLAGS_max_sd_pixels);
517 cpix.max_hd_pixels = absl::GetFlag(FLAGS_max_hd_pixels);
518 cpix.max_uhd1_pixels = absl::GetFlag(FLAGS_max_uhd1_pixels);
519 break;
520 }
521 case KeyProvider::kNone:
522 break;
523 }
524
525 num_key_providers = 0;
526 DecryptionParams& decryption_params = packaging_params.decryption_params;
527 if (absl::GetFlag(FLAGS_enable_widevine_decryption)) {
528 decryption_params.key_provider = KeyProvider::kWidevine;
529 ++num_key_providers;
530 }
531 if (absl::GetFlag(FLAGS_enable_raw_key_decryption)) {
532 decryption_params.key_provider = KeyProvider::kRawKey;
533 ++num_key_providers;
534 }
535 if (absl::GetFlag(FLAGS_enable_cpix_decryption)) {
536 decryption_params.key_provider = KeyProvider::kCpix;
537 ++num_key_providers;
538 }
539 if (num_key_providers > 1) {
540 LOG(ERROR) << "Only one of --enable_widevine_decryption, "
541 "--enable_raw_key_decryption, --enable_cpix_decryption can "
542 "be enabled.";
543 return std::nullopt;
544 }
545 switch (decryption_params.key_provider) {
546 case KeyProvider::kWidevine: {
547 WidevineDecryptionParams& widevine = decryption_params.widevine;
548 widevine.key_server_url = absl::GetFlag(FLAGS_key_server_url);
549 if (!GetWidevineSigner(&widevine.signer))
550 return std::nullopt;
551 break;
552 }
553 case KeyProvider::kRawKey: {
554 if (!GetRawKeyParams(&decryption_params.raw_key))
555 return std::nullopt;
556 break;
557 }
558 case KeyProvider::kCpix: {
559 CpixEncryptionParams& cpix = decryption_params.cpix;
560 cpix.document_source = absl::GetFlag(FLAGS_cpix);
561 cpix.request_document_source = absl::GetFlag(FLAGS_cpix_request_file);
562 cpix.headers =
563 SplitAndTrimSkipEmpty(absl::GetFlag(FLAGS_cpix_headers), ';');
564 cpix.private_key_source = absl::GetFlag(FLAGS_cpix_private_key);
565 break;
566 }
567 case KeyProvider::kPlayReady:
568 case KeyProvider::kNone:
569 break;
570 }
571
572 Mp4OutputParams& mp4_params = packaging_params.mp4_output_params;
573 mp4_params.generate_sidx_in_media_segments =
574 absl::GetFlag(FLAGS_generate_sidx_in_media_segments);
575 mp4_params.include_pssh_in_stream =
576 absl::GetFlag(FLAGS_mp4_include_pssh_in_stream);
577 mp4_params.low_latency_dash_mode = absl::GetFlag(FLAGS_low_latency_dash_mode);
578
579 packaging_params.transport_stream_timestamp_offset_ms =
580 absl::GetFlag(FLAGS_transport_stream_timestamp_offset_ms);
581 packaging_params.default_text_zero_bias_ms =
582 absl::GetFlag(FLAGS_default_text_zero_bias_ms);
583 packaging_params.output_media_info = absl::GetFlag(FLAGS_output_media_info);
584
585 MpdParams& mpd_params = packaging_params.mpd_params;
586 mpd_params.mpd_output = absl::GetFlag(FLAGS_mpd_output);
587 mpd_params.event_to_vod_on_end_of_stream =
588 absl::GetFlag(FLAGS_event_to_vod_on_end_of_stream);
589
590 std::vector<std::string> base_urls =
591 SplitAndTrimSkipEmpty(absl::GetFlag(FLAGS_base_urls), ',');
592
593 mpd_params.base_urls = base_urls;
594 mpd_params.min_buffer_time = absl::GetFlag(FLAGS_min_buffer_time);
595 mpd_params.minimum_update_period = absl::GetFlag(FLAGS_minimum_update_period);
596 mpd_params.suggested_presentation_delay =
597 absl::GetFlag(FLAGS_suggested_presentation_delay);
598 mpd_params.time_shift_buffer_depth =
599 absl::GetFlag(FLAGS_time_shift_buffer_depth);
600 mpd_params.preserved_segments_outside_live_window =
601 absl::GetFlag(FLAGS_preserved_segments_outside_live_window);
602 mpd_params.use_segment_list = absl::GetFlag(FLAGS_dash_force_segment_list);
603
604 if (!absl::GetFlag(FLAGS_utc_timings).empty()) {
605 std::vector<KVPair> pairs = SplitStringIntoKeyValuePairs(
606 absl::GetFlag(FLAGS_utc_timings), '=', ',');
607 if (pairs.empty()) {
608 LOG(ERROR) << "Invalid --utc_timings scheme_id_uri/value pairs.";
609 return std::nullopt;
610 }
611 for (const auto& string_pair : pairs) {
612 mpd_params.utc_timings.push_back({string_pair.first, string_pair.second});
613 }
614 }
615
616 mpd_params.default_language = absl::GetFlag(FLAGS_default_language);
617 mpd_params.default_text_language = absl::GetFlag(FLAGS_default_text_language);
618 mpd_params.generate_static_live_mpd =
619 absl::GetFlag(FLAGS_generate_static_live_mpd);
620 mpd_params.generate_dash_if_iop_compliant_mpd =
621 absl::GetFlag(FLAGS_generate_dash_if_iop_compliant_mpd);
622 mpd_params.allow_approximate_segment_timeline =
623 absl::GetFlag(FLAGS_allow_approximate_segment_timeline);
624 mpd_params.allow_codec_switching = absl::GetFlag(FLAGS_allow_codec_switching);
625 mpd_params.include_mspr_pro =
626 absl::GetFlag(FLAGS_include_mspr_pro_for_playready);
627 mpd_params.low_latency_dash_mode = absl::GetFlag(FLAGS_low_latency_dash_mode);
628
629 HlsParams& hls_params = packaging_params.hls_params;
630 if (!GetHlsPlaylistType(absl::GetFlag(FLAGS_hls_playlist_type),
631 &hls_params.playlist_type)) {
632 return std::nullopt;
633 }
634 hls_params.event_to_vod_on_end_of_stream =
635 absl::GetFlag(FLAGS_event_to_vod_on_end_of_stream);
636 hls_params.master_playlist_output =
637 absl::GetFlag(FLAGS_hls_master_playlist_output);
638 hls_params.base_url = absl::GetFlag(FLAGS_hls_base_url);
639 hls_params.key_uri = absl::GetFlag(FLAGS_hls_key_uri);
640 hls_params.time_shift_buffer_depth =
641 absl::GetFlag(FLAGS_time_shift_buffer_depth);
642 hls_params.preserved_segments_outside_live_window =
643 absl::GetFlag(FLAGS_preserved_segments_outside_live_window);
644 hls_params.default_language = absl::GetFlag(FLAGS_default_language);
645 hls_params.default_text_language = absl::GetFlag(FLAGS_default_text_language);
646 hls_params.media_sequence_number =
647 absl::GetFlag(FLAGS_hls_media_sequence_number);
648 hls_params.start_time_offset = absl::GetFlag(FLAGS_hls_start_time_offset);
649 hls_params.create_session_keys = absl::GetFlag(FLAGS_create_session_keys);
650 hls_params.add_program_date_time = absl::GetFlag(FLAGS_add_program_date_time);
651 hls_params.per_playlist_target_duration =
652 absl::GetFlag(FLAGS_per_playlist_target_duration);
653
654 if (!ParseClosedCaptions(absl::GetFlag(FLAGS_closed_captions),
655 &packaging_params.closed_captions)) {
656 LOG(ERROR) << "Failed to parse --closed_captions "
657 << absl::GetFlag(FLAGS_closed_captions);
658 return std::nullopt;
659 }
660
661 TestParams& test_params = packaging_params.test_params;
662 test_params.dump_stream_info = absl::GetFlag(FLAGS_dump_stream_info);
663 test_params.inject_fake_clock = absl::GetFlag(FLAGS_use_fake_clock_for_muxer);
664 if (!absl::GetFlag(FLAGS_test_packager_version).empty())
665 test_params.injected_library_version =
666 absl::GetFlag(FLAGS_test_packager_version);
667
668 return packaging_params;
669}
670
671int PackagerMain(int argc, char** argv) {
672 absl::FlagsUsageConfig flag_config;
673 flag_config.version_string = []() -> std::string {
674 return "packager version " + shaka::Packager::GetLibraryVersion() + "\n";
675 };
676 flag_config.contains_help_flags =
677 [](absl::string_view flag_file_name) -> bool { return true; };
678 absl::SetFlagsUsageConfig(flag_config);
679
680 auto usage = absl::StrFormat(kUsage, argv[0]);
681 absl::SetProgramUsageMessage(usage);
682
683 // Before parsing the command line, change the default value of some flags
684 // provided by libraries.
685
686 // Always log to stderr. Log levels are still controlled by --minloglevel.
687 absl::SetFlag(&FLAGS_stderrthreshold, 0);
688
689 auto remaining_args = absl::ParseCommandLine(argc, argv);
690 if (absl::GetFlag(FLAGS_licenses)) {
691 for (const char* line : kLicenseNotice)
692 std::cout << line << std::endl;
693 return kSuccess;
694 }
695
696 if (remaining_args.size() < 2) {
697 std::cerr << "Usage: " << absl::ProgramUsageMessage();
698 return kSuccess;
699 }
700
701 if (absl::GetFlag(FLAGS_quiet)) {
702 absl::SetMinLogLevel(absl::LogSeverityAtLeast::kWarning);
703 }
704
705 absl::InitializeLog();
706
709 !ValidateCryptoFlags() || !ValidateRetiredFlags()) {
710 return kArgumentValidationFailed;
711 }
712
713 std::optional<PackagingParams> packaging_params = GetPackagingParams();
714 if (!packaging_params)
715 return kArgumentValidationFailed;
716
717 std::vector<StreamDescriptor> stream_descriptors;
718 for (size_t i = 1; i < remaining_args.size(); ++i) {
719 std::optional<StreamDescriptor> stream_descriptor =
720 ParseStreamDescriptor(remaining_args[i]);
721 if (!stream_descriptor)
722 return kArgumentValidationFailed;
723 stream_descriptors.push_back(stream_descriptor.value());
724 }
725
726 if (absl::GetFlag(FLAGS_force_cl_index)) {
727 int index = 0;
728 for (auto& descriptor : stream_descriptors) {
729 descriptor.index = index++;
730 }
731 }
732
733 Packager packager;
734 Status status =
735 packager.Initialize(packaging_params.value(), stream_descriptors);
736 if (!status.ok()) {
737 LOG(ERROR) << "Failed to initialize packager: " << status.ToString();
738 return kArgumentValidationFailed;
739 }
740 status = packager.Run();
741 if (!status.ok()) {
742 LOG(ERROR) << "Packaging Error: " << status.ToString();
743 return kPackagingFailed;
744 }
745 if (!absl::GetFlag(FLAGS_quiet))
746 printf("Packaging completed successfully.\n");
747 return kSuccess;
748}
749
750} // namespace
751} // namespace shaka
752
753#if defined(OS_WIN)
754// Windows wmain, which converts wide character arguments to UTF-8.
755int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) {
756 std::unique_ptr<char*[], std::function<void(char**)>> utf8_argv(
757 new char*[argc], [argc](char** utf8_args) {
758 // TODO(tinskip): This leaks, but if this code is enabled, it crashes.
759 // Figure out why. I suspect gflags does something funny with the
760 // argument array.
761 // for (int idx = 0; idx < argc; ++idx)
762 // delete[] utf8_args[idx];
763 delete[] utf8_args;
764 });
765 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
766
767 for (int idx = 0; idx < argc; ++idx) {
768 std::string utf8_arg(converter.to_bytes(argv[idx]));
769 utf8_arg += '\0';
770 utf8_argv[idx] = new char[utf8_arg.size()];
771 memcpy(utf8_argv[idx], &utf8_arg[0], utf8_arg.size());
772 }
773
774 // Because we just converted wide character args into UTF8, and because
775 // std::filesystem::u8path is used to interpret all std::string paths as
776 // UTF8, we should set the locale to UTF8 as well, for the transition point
777 // to C library functions like fopen to work correctly with non-ASCII paths.
778 std::setlocale(LC_ALL, ".UTF8");
779
780 return shaka::PackagerMain(argc, utf8_argv.get());
781}
782#else
783int main(int argc, char** argv) {
784 return shaka::PackagerMain(argc, argv);
785}
786#endif // defined(OS_WIN)
All the methods that are virtual are virtual for mocking.
std::optional< StreamDescriptor > ParseStreamDescriptor(const std::string &descriptor_string)
bool ValidateCpixCryptoFlags()
bool ValidateRawKeyCryptoFlags()
bool ValidateWidevineCryptoFlags()