Shaka Packager SDK
muxer_util.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 <packager/media/base/muxer_util.h>
8 
9 #include <cinttypes>
10 #include <string>
11 #include <vector>
12 
13 #include <absl/log/check.h>
14 #include <absl/log/log.h>
15 #include <absl/strings/numbers.h>
16 #include <absl/strings/str_format.h>
17 #include <absl/strings/str_split.h>
18 
19 #include <packager/media/base/video_stream_info.h>
20 
21 namespace shaka {
22 namespace {
23 Status ValidateFormatTag(const std::string& format_tag) {
24  if (format_tag.empty()) {
25  return Status(error::INVALID_ARGUMENT, "Format tag should not be empty");
26  }
27 
28  // Format tag should follow this prototype: %0[width]d.
29  if (format_tag.size() > 3 && format_tag[0] == '%' && format_tag[1] == '0' &&
30  format_tag[format_tag.size() - 1] == 'd') {
31  unsigned out;
32  if (absl::SimpleAtoi(format_tag.substr(2, format_tag.size() - 3), &out)) {
33  return Status::OK;
34  }
35  }
36 
37  return Status(
38  error::INVALID_ARGUMENT,
39  "Format tag should follow this prototype: %0[width]d if exist.");
40 }
41 } // namespace
42 
43 namespace media {
44 
45 Status ValidateSegmentTemplate(const std::string& segment_template) {
46  if (segment_template.empty()) {
47  return Status(error::INVALID_ARGUMENT,
48  "Segment template should not be empty.");
49  }
50 
51  std::vector<std::string> splits = absl::StrSplit(segment_template, "$");
52 
53  // ISO/IEC 23009-1:2012 5.3.9.4.4 Template-based Segment URL construction.
54  // Allowed identifiers: $$, $RepresentationID$, $Number$, $Bandwidth$, $Time$.
55  // "$" always appears in pairs, so there should be odd number of splits.
56  if (splits.size() % 2 == 0) {
57  return Status(error::INVALID_ARGUMENT,
58  "In segment templates, '$' should appear in pairs.");
59  }
60 
61  bool has_number = false;
62  bool has_time = false;
63  // Every second substring in split output should be an identifier.
64  for (size_t i = 1; i < splits.size(); i += 2) {
65  // Each identifier may be suffixed, within the enclosing ‘$’ characters,
66  // with an additional format tag aligned with the printf format tag as
67  // defined in IEEE 1003.1-2008 [10] following this prototype: %0[width]d.
68  size_t format_pos = splits[i].find('%');
69  std::string identifier = splits[i].substr(0, format_pos);
70  if (format_pos != std::string::npos) {
71  Status tag_check = ValidateFormatTag(splits[i].substr(format_pos));
72  if (!tag_check.ok()) {
73  return tag_check;
74  }
75  }
76 
77  // TODO(kqyang): Support "RepresentationID".
78  if (identifier == "RepresentationID") {
79  return Status(
80  error::UNIMPLEMENTED,
81  "Segment template flag $RepresentationID$ is not supported yet.");
82  } else if (identifier == "Number") {
83  has_number = true;
84  } else if (identifier == "Time") {
85  has_time = true;
86  } else if (identifier == "") {
87  if (format_pos != std::string::npos) {
88  return Status(error::INVALID_ARGUMENT,
89  "'$$' should not have any format tags.");
90  }
91  } else if (identifier != "Bandwidth") {
92  return Status(error::INVALID_ARGUMENT,
93  "'$" + splits[i] + "$' is not a valid identifier.");
94  }
95  }
96  if (has_number && has_time) {
97  return Status(
98  error::INVALID_ARGUMENT,
99  "In segment templates $Number$ and $Time$ should not co-exist.");
100  }
101  if (!has_number && !has_time) {
102  return Status(error::INVALID_ARGUMENT,
103  "In segment templates $Number$ or $Time$ should exist.");
104  }
105  // Note: The below check is skipped.
106  // Strings outside identifiers shall only contain characters that are
107  // permitted within URLs according to RFC 1738.
108  return Status::OK;
109 }
110 
111 std::string GetSegmentName(const std::string& segment_template,
112  int64_t segment_start_time,
113  uint32_t segment_number,
114  uint32_t bandwidth) {
115  DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
116 
117  std::vector<std::string> splits = absl::StrSplit(segment_template, "$");
118  // "$" always appears in pairs, so there should be odd number of splits.
119  DCHECK_EQ(1u, splits.size() % 2);
120 
121  std::string segment_name;
122  for (size_t i = 0; i < splits.size(); ++i) {
123  // Every second substring in split output should be an identifier.
124  // Simply copy the non-identifier part.
125  if (i % 2 == 0) {
126  segment_name += splits[i];
127  continue;
128  }
129  if (splits[i].empty()) {
130  // "$$" is an escape sequence, replaced with a single "$".
131  segment_name += "$";
132  continue;
133  }
134  size_t format_pos = splits[i].find('%');
135  std::string identifier = splits[i].substr(0, format_pos);
136  DCHECK(identifier == "Number" || identifier == "Time" ||
137  identifier == "Bandwidth");
138 
139  std::string format_tag;
140  if (format_pos != std::string::npos) {
141  format_tag = splits[i].substr(format_pos);
142  DCHECK_EQ(Status::OK, ValidateFormatTag(format_tag));
143  // Replace %d formatting to correctly format uint64_t.
144  format_tag = format_tag.substr(0, format_tag.size() - 1) + PRIu64;
145  } else {
146  // Default format tag "%01d", modified to format uint64_t correctly.
147  format_tag = "%01" PRIu64;
148  }
149 
150  // absl::StrFormat requires compile-time constants for format strings.
151  // If you don't have that, you build the formatter using this interface
152  // instead.
153  std::vector<absl::FormatArg> format_args;
154  absl::UntypedFormatSpec format(format_tag);
155  if (identifier == "Number") {
156  // SegmentNumber starts from 1.
157  format_args.emplace_back(static_cast<uint64_t>(segment_number));
158  } else if (identifier == "Time") {
159  format_args.emplace_back(static_cast<uint64_t>(segment_start_time));
160  } else if (identifier == "Bandwidth") {
161  format_args.emplace_back(static_cast<uint64_t>(bandwidth));
162  }
163 
164  std::string format_output;
165  if (absl::FormatUntyped(&format_output, format, format_args)) {
166  segment_name += format_output;
167  }
168  }
169  return segment_name;
170 }
171 
172 } // namespace media
173 } // namespace shaka
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66