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