7 #include "packager/media/formats/webvtt/webvtt_sample_converter.h"
12 #include "packager/base/strings/string_util.h"
13 #include "packager/base/strings/stringprintf.h"
14 #include "packager/media/base/buffer_writer.h"
15 #include "packager/media/base/media_sample.h"
16 #include "packager/media/formats/mp4/box_buffer.h"
17 #include "packager/media/formats/mp4/box_definitions.h"
24 std::shared_ptr<MediaSample> CreateEmptyCueSample(uint64_t start_time,
26 DCHECK_GT(end_time, start_time);
27 mp4::VTTEmptyCueBox empty_cue_box;
29 std::vector<uint8_t> serialized;
30 AppendBoxToVector(&empty_cue_box, &serialized);
33 serialized.data(), serialized.size(),
false);
34 empty_cue_sample->set_pts(start_time);
35 empty_cue_sample->set_duration(end_time - start_time);
36 return empty_cue_sample;
39 void StripTrailingNewlines(
const std::string& input, std::string* output) {
40 const size_t found = input.find_last_not_of(
'\n');
41 if (found != std::string::npos) {
42 *output = input.substr(0, found + 1);
48 mp4::VTTCueBox CueBoxFromCue(
const Cue& cue) {
49 mp4::VTTCueBox cue_box;
50 if (!cue.identifier.empty()) {
51 cue_box.cue_id.cue_id = cue.identifier;
54 if (!cue.settings.empty()) {
55 cue_box.cue_settings.settings = cue.settings;
58 StripTrailingNewlines(cue.payload, &cue_box.cue_payload.cue_text);
62 std::string TimeToWebVttTimeStamp(uint64_t time_in_ms) {
63 const int milliseconds = time_in_ms % 1000;
64 const uint64_t seconds_left = time_in_ms / 1000;
65 const int seconds = seconds_left % 60;
66 const uint64_t minutes_left = seconds_left / 60;
67 const int minutes = minutes_left % 60;
68 const int hours = minutes_left / 60;
70 return base::StringPrintf(
"%02d:%02d:%02d.%03d", hours, minutes, seconds,
74 std::shared_ptr<MediaSample> CreateVTTCueBoxesSample(
75 const std::list<const Cue*>& cues,
83 std::vector<uint8_t> data;
84 std::string cue_current_time = TimeToWebVttTimeStamp(start_time);
87 for (
const Cue* cue : cues) {
88 mp4::VTTCueBox cue_box = CueBoxFromCue(*cue);
94 cue_box.Write(&writer);
97 std::shared_ptr<MediaSample> sample =
99 sample->set_pts(start_time);
100 sample->set_duration(end_time - start_time);
106 uint64_t GetMinimumPastSweepLine(uint64_t cue_start_time,
107 uint64_t cue_end_time,
109 uint64_t current_minimum) {
110 DCHECK_GE(current_minimum, sweep_line);
111 if (cue_end_time <= sweep_line)
112 return current_minimum;
115 if (cue_start_time > sweep_line) {
117 return std::min(cue_start_time, current_minimum);
120 return std::min(cue_end_time, current_minimum);
126 void AppendBoxToVector(mp4::Box* box, std::vector<uint8_t>* output_vector) {
129 output_vector->insert(output_vector->end(),
131 writer.Buffer() + writer.Size());
134 WebVttSampleConverter::WebVttSampleConverter() : next_cue_start_time_(0u) {}
135 WebVttSampleConverter::~WebVttSampleConverter() {}
140 if (!cue.comment.empty()) {
143 StripTrailingNewlines(cue.comment, &comment.cue_additional_text);
144 additional_texts_.push_back(comment);
150 cues_.push_back(cue);
151 if (cues_.size() == 1) {
154 next_cue_start_time_ = cues_.front().start_time;
158 CHECK_GE(cues_.size(), 2u);
165 bool processed_cues = HandleAllCuesButLatest();
170 auto erase_last_iterator = --cues_.end();
171 cues_.erase(cues_.begin(), erase_last_iterator);
177 if (cues_.size() == 1) {
178 std::list<const Cue*> temp_list;
179 temp_list.push_back(&cues_.front());
180 CHECK_EQ(next_cue_start_time_, cues_.front().start_time);
181 ready_samples_.push_back(CreateVTTCueBoxesSample(
183 next_cue_start_time_,
184 cues_.front().start_time + cues_.front().duration));
189 bool processed_cue = HandleAllCues();
191 <<
"No cues were processed but the cues should have been flushed.";
196 return ready_samples_.size();
200 CHECK(!ready_samples_.empty());
201 std::shared_ptr<MediaSample> ret = ready_samples_.front();
202 ready_samples_.pop_front();
218 bool WebVttSampleConverter::HandleAllCuesButLatest() {
219 DCHECK_GE(cues_.size(), 2u);
220 const Cue& latest_cue = cues_.back();
224 uint64_t max_cue_end_time = 0;
225 auto latest_cue_it = --cues_.end();
226 for (
auto cue_it = cues_.begin(); cue_it != latest_cue_it; ++cue_it) {
227 const Cue& cue = *cue_it;
228 const uint64_t cue_end_time = cue.start_time + cue.duration;
229 if (cue_end_time > latest_cue.start_time)
232 if (max_cue_end_time < cue_end_time)
233 max_cue_end_time = cue_end_time;
243 const uint64_t sweep_stop_time = max_cue_end_time;
244 const uint64_t sweep_line_start = cues_.front().start_time;
245 bool processed_cues =
246 SweepCues(sweep_line_start, sweep_stop_time);
247 next_cue_start_time_ = sweep_stop_time;
248 if (next_cue_start_time_ < latest_cue.start_time) {
249 ready_samples_.push_back(CreateEmptyCueSample(next_cue_start_time_,
250 latest_cue.start_time));
251 next_cue_start_time_ = latest_cue.start_time;
253 return processed_cues;
256 bool WebVttSampleConverter::HandleAllCues() {
257 uint64_t latest_time = 0u;
258 for (
const Cue& cue : cues_) {
259 if (cue.start_time + cue.duration > latest_time)
260 latest_time = cue.start_time + cue.duration;
262 const uint64_t sweep_line_start = cues_.front().start_time;
263 const uint64_t sweep_stop_time = latest_time;
264 bool processed = SweepCues(sweep_line_start, sweep_stop_time);
265 next_cue_start_time_ = sweep_stop_time;
269 bool WebVttSampleConverter::SweepCues(uint64_t sweep_line,
270 uint64_t sweep_stop_time) {
271 bool processed_cues =
false;
277 while (sweep_line < sweep_stop_time) {
278 std::list<const Cue*> cues_for_a_sample;
279 uint64_t next_start_time = sweep_stop_time;
284 for (
const Cue& cue : cues_) {
285 if (cue.start_time >= sweep_stop_time)
287 if (cue.start_time >= next_start_time)
290 const uint64_t cue_end_time = cue.start_time + cue.duration;
291 if (cue_end_time <= sweep_line)
293 next_start_time = GetMinimumPastSweepLine(
294 cue.start_time, cue_end_time, sweep_line, next_start_time);
296 if (cue.start_time <= sweep_line) {
297 DCHECK_GT(cue_end_time, sweep_line);
298 cues_for_a_sample.push_back(&cue);
302 DCHECK(!cues_for_a_sample.empty()) <<
"For now the only use case of this "
303 "function is to sweep non-empty "
305 if (!cues_for_a_sample.empty()) {
306 ready_samples_.push_back(CreateVTTCueBoxesSample(
307 cues_for_a_sample, sweep_line, next_start_time));
308 processed_cues =
true;
311 sweep_line = next_start_time;
314 DCHECK_EQ(sweep_line, sweep_stop_time);
315 return processed_cues;