Shaka Packager SDK
mpd_generator.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 <iostream>
8 
9 #if defined(OS_WIN)
10 #include <codecvt>
11 #include <functional>
12 #endif // defined(OS_WIN)
13 
14 #include <absl/flags/parse.h>
15 #include <absl/flags/usage.h>
16 #include <absl/flags/usage_config.h>
17 #include <absl/log/check.h>
18 #include <absl/log/initialize.h>
19 #include <absl/log/log.h>
20 #include <absl/strings/str_format.h>
21 #include <absl/strings/str_split.h>
22 
23 #include <packager/app/mpd_generator_flags.h>
24 #include <packager/mpd/util/mpd_writer.h>
25 #include <packager/tools/license_notice.h>
26 #include <packager/version/version.h>
27 
28 ABSL_FLAG(bool, licenses, false, "Dump licenses.");
29 ABSL_FLAG(std::string,
30  test_packager_version,
31  "",
32  "Packager version for testing. Should be used for testing only.");
33 
34 // From absl/log:
35 ABSL_DECLARE_FLAG(int, stderrthreshold);
36 
37 namespace shaka {
38 namespace {
39 const char kUsage[] =
40  "MPD generation driver program.\n"
41  "This program accepts MediaInfo files in human readable text "
42  "format and outputs an MPD.\n"
43  "The main use case for this is to output MPD for VOD.\n"
44  "Limitations:\n"
45  " Each MediaInfo can only have one of VideoInfo, AudioInfo, or TextInfo.\n"
46  " There will be at most 3 AdaptationSets in the MPD, i.e. 1 video, 1 "
47  "audio, and 1 text.\n"
48  "Sample Usage:\n"
49  "%s --input=\"video1.media_info,video2.media_info,audio1.media_info\" "
50  "--output=\"video_audio.mpd\"";
51 
52 enum ExitStatus {
53  kSuccess = 0,
54  kEmptyInputError,
55  kEmptyOutputError,
56  kFailedToWriteMpdToFileError
57 };
58 
59 ExitStatus CheckRequiredFlags() {
60  if (absl::GetFlag(FLAGS_input).empty()) {
61  LOG(ERROR) << "--input is required.";
62  return kEmptyInputError;
63  }
64 
65  if (absl::GetFlag(FLAGS_output).empty()) {
66  LOG(ERROR) << "--output is required.";
67  return kEmptyOutputError;
68  }
69 
70  return kSuccess;
71 }
72 
73 ExitStatus RunMpdGenerator() {
74  DCHECK_EQ(CheckRequiredFlags(), kSuccess);
75  std::vector<std::string> base_urls;
76  typedef std::vector<std::string>::const_iterator Iterator;
77 
78  std::vector<std::string> input_files =
79  absl::StrSplit(absl::GetFlag(FLAGS_input), ",", absl::AllowEmpty());
80 
81  if (!absl::GetFlag(FLAGS_base_urls).empty()) {
82  base_urls =
83  absl::StrSplit(absl::GetFlag(FLAGS_base_urls), ",", absl::AllowEmpty());
84  }
85 
86  MpdWriter mpd_writer;
87  for (Iterator it = base_urls.begin(); it != base_urls.end(); ++it)
88  mpd_writer.AddBaseUrl(*it);
89 
90  for (const std::string& file : input_files) {
91  if (!mpd_writer.AddFile(file)) {
92  LOG(WARNING) << "MpdWriter failed to read " << file << ", skipping.";
93  }
94  }
95 
96  if (!mpd_writer.WriteMpdToFile(absl::GetFlag(FLAGS_output).c_str())) {
97  LOG(ERROR) << "Failed to write MPD to " << absl::GetFlag(FLAGS_output);
98  return kFailedToWriteMpdToFileError;
99  }
100 
101  return kSuccess;
102 }
103 
104 int MpdMain(int argc, char** argv) {
105  absl::FlagsUsageConfig flag_config;
106  flag_config.version_string = []() -> std::string {
107  return "mpd_generator version " + GetPackagerVersion() + "\n";
108  };
109  flag_config.contains_help_flags =
110  [](absl::string_view flag_file_name) -> bool { return true; };
111  absl::SetFlagsUsageConfig(flag_config);
112 
113  auto usage = absl::StrFormat(kUsage, argv[0]);
114  absl::SetProgramUsageMessage(usage);
115 
116  // Before parsing the command line, change the default value of some flags
117  // provided by libraries.
118 
119  // Always log to stderr. Log levels are still controlled by --minloglevel.
120  absl::SetFlag(&FLAGS_stderrthreshold, 0);
121 
122  absl::ParseCommandLine(argc, argv);
123 
124  if (absl::GetFlag(FLAGS_licenses)) {
125  for (const char* line : kLicenseNotice)
126  std::cout << line << std::endl;
127  return kSuccess;
128  }
129 
130  ExitStatus status = CheckRequiredFlags();
131  if (status != kSuccess) {
132  std::cerr << "Usage " << absl::ProgramUsageMessage();
133  return status;
134  }
135 
136  absl::InitializeLog();
137 
138  if (!absl::GetFlag(FLAGS_test_packager_version).empty())
139  SetPackagerVersionForTesting(absl::GetFlag(FLAGS_test_packager_version));
140 
141  return RunMpdGenerator();
142 }
143 
144 } // namespace
145 } // namespace shaka
146 
147 #if defined(OS_WIN)
148 // Windows wmain, which converts wide character arguments to UTF-8.
149 int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) {
150  std::unique_ptr<char* [], std::function<void(char**)>> utf8_argv(
151  new char*[argc], [argc](char** utf8_args) {
152  // TODO(tinskip): This leaks, but if this code is enabled, it crashes.
153  // Figure out why. I suspect gflags does something funny with the
154  // argument array.
155  // for (int idx = 0; idx < argc; ++idx)
156  // delete[] utf8_args[idx];
157  delete[] utf8_args;
158  });
159  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
160 
161  for (int idx = 0; idx < argc; ++idx) {
162  std::string utf8_arg(converter.to_bytes(argv[idx]));
163  utf8_arg += '\0';
164  utf8_argv[idx] = new char[utf8_arg.size()];
165  memcpy(utf8_argv[idx], &utf8_arg[0], utf8_arg.size());
166  }
167 
168  // Because we just converted wide character args into UTF8, and because
169  // std::filesystem::u8path is used to interpret all std::string paths as
170  // UTF8, we should set the locale to UTF8 as well, for the transition point
171  // to C library functions like fopen to work correctly with non-ASCII paths.
172  std::setlocale(LC_ALL, ".UTF8");
173 
174  return shaka::MpdMain(argc, utf8_argv.get());
175 }
176 #else
177 int main(int argc, char** argv) {
178  return shaka::MpdMain(argc, argv);
179 }
180 #endif // !defined(OS_WIN)
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66