VOD text support
Change-Id: Ic69169c31d0a54de4895d49ff8b4a758039733a7
This commit is contained in:
parent
4c10755d40
commit
29e14a3d6b
|
@ -22,14 +22,17 @@
|
|||
#include "packager/base/strings/stringprintf.h"
|
||||
#include "packager/base/threading/simple_thread.h"
|
||||
#include "packager/base/time/clock.h"
|
||||
#include "packager/media/base/container_names.h"
|
||||
#include "packager/media/base/demuxer.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
||||
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
||||
#include "packager/media/file/file.h"
|
||||
#include "packager/media/formats/mp4/mp4_muxer.h"
|
||||
#include "packager/mpd/base/dash_iop_mpd_notifier.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
#include "packager/mpd/base/simple_mpd_notifier.h"
|
||||
|
||||
|
@ -64,6 +67,8 @@ const char kUsage[] =
|
|||
"language tag. If specified, this value overrides any language metadata "
|
||||
"in the input track.\n";
|
||||
|
||||
const char kMediaInfoSuffix[] = ".media_info";
|
||||
|
||||
enum ExitStatus {
|
||||
kSuccess = 0,
|
||||
kNoArgument,
|
||||
|
@ -71,6 +76,29 @@ enum ExitStatus {
|
|||
kPackagingFailed,
|
||||
kInternalError,
|
||||
};
|
||||
|
||||
// TODO(rkuroiwa): Write TTML and WebVTT parser (demuxing) for a better check
|
||||
// and for supporting live/segmenting (muxing). With a demuxer and a muxer,
|
||||
// CreateRemuxJobs() shouldn't treat text as a special case.
|
||||
std::string DetermineTextFileFormat(const std::string& file) {
|
||||
std::string content;
|
||||
if (!edash_packager::media::File::ReadFileToString(file.c_str(), &content)) {
|
||||
LOG(ERROR) << "Failed to open file " << file
|
||||
<< " to determine file format.";
|
||||
return "";
|
||||
}
|
||||
edash_packager::media::MediaContainerName container_name =
|
||||
edash_packager::media::DetermineContainer(
|
||||
reinterpret_cast<const uint8_t*>(content.data()), content.size());
|
||||
if (container_name == edash_packager::media::CONTAINER_WEBVTT) {
|
||||
return "vtt";
|
||||
} else if (container_name == edash_packager::media::CONTAINER_TTML) {
|
||||
return "ttml";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace edash_packager {
|
||||
|
@ -114,6 +142,45 @@ class RemuxJob : public base::SimpleThread {
|
|||
DISALLOW_COPY_AND_ASSIGN(RemuxJob);
|
||||
};
|
||||
|
||||
bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
|
||||
const MuxerOptions& stream_muxer_options,
|
||||
MediaInfo* text_media_info) {
|
||||
const std::string& language = stream_descriptor.language;
|
||||
std::string format = DetermineTextFileFormat(stream_descriptor.input);
|
||||
if (format.empty()) {
|
||||
LOG(ERROR) << "Failed to determine the text file format for "
|
||||
<< stream_descriptor.input;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File::Copy(stream_descriptor.input.c_str(),
|
||||
stream_muxer_options.output_file_name.c_str())) {
|
||||
LOG(ERROR) << "Failed to copy the input file (" << stream_descriptor.input
|
||||
<< ") to output file (" << stream_muxer_options.output_file_name
|
||||
<< ").";
|
||||
return false;
|
||||
}
|
||||
|
||||
text_media_info->set_media_file_name(stream_muxer_options.output_file_name);
|
||||
text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
|
||||
|
||||
if (stream_muxer_options.bandwidth != 0) {
|
||||
text_media_info->set_bandwidth(stream_muxer_options.bandwidth);
|
||||
} else {
|
||||
// Text files are usually small and since the input is one file; there's no
|
||||
// way for the player to do ranged requests. So set this value to something
|
||||
// reasonable.
|
||||
text_media_info->set_bandwidth(256);
|
||||
}
|
||||
|
||||
MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
|
||||
text_info->set_format(format);
|
||||
if (!language.empty())
|
||||
text_info->set_language(language);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||
const MuxerOptions& muxer_options,
|
||||
FakeClock* fake_clock,
|
||||
|
@ -140,6 +207,34 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
}
|
||||
stream_muxer_options.bandwidth = stream_iter->bandwidth;
|
||||
|
||||
// Handle text input.
|
||||
if (stream_iter->stream_selector == "text") {
|
||||
MediaInfo text_media_info;
|
||||
if (!StreamInfoToTextMediaInfo(*stream_iter, stream_muxer_options,
|
||||
&text_media_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mpd_notifier) {
|
||||
uint32 unused;
|
||||
if (!mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
|
||||
LOG(ERROR) << "Failed to process text file " << stream_iter->input;
|
||||
} else {
|
||||
mpd_notifier->Flush();
|
||||
}
|
||||
} else if (FLAGS_output_media_info) {
|
||||
VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile(
|
||||
text_media_info,
|
||||
stream_muxer_options.output_file_name + kMediaInfoSuffix);
|
||||
} else {
|
||||
NOTIMPLEMENTED()
|
||||
<< "--mpd_output or --output_media_info flags are "
|
||||
"required for text output. Skipping manifest related output for "
|
||||
<< stream_iter->input;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stream_iter->input != previous_input) {
|
||||
// New remux job needed. Create demux and job thread.
|
||||
scoped_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
|
||||
|
@ -180,7 +275,7 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
DCHECK(!(FLAGS_output_media_info && mpd_notifier));
|
||||
if (FLAGS_output_media_info) {
|
||||
const std::string output_media_info_file_name =
|
||||
stream_muxer_options.output_file_name + ".media_info";
|
||||
stream_muxer_options.output_file_name + kMediaInfoSuffix;
|
||||
scoped_ptr<VodMediaInfoDumpMuxerListener>
|
||||
vod_media_info_dump_muxer_listener(
|
||||
new VodMediaInfoDumpMuxerListener(output_media_info_file_name));
|
||||
|
|
|
@ -24,7 +24,6 @@ struct MpdOptions;
|
|||
namespace media {
|
||||
|
||||
class KeySource;
|
||||
class MediaInfo;
|
||||
class MediaStream;
|
||||
class Muxer;
|
||||
struct MuxerOptions;
|
||||
|
|
|
@ -28,6 +28,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
self.tmp_dir = tempfile.mkdtemp()
|
||||
self.output_prefix = os.path.join(self.tmp_dir, 'output')
|
||||
self.mpd_output = self.output_prefix + '.mpd'
|
||||
self.output = None
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
|
@ -70,13 +71,30 @@ class PackagerAppTest(unittest.TestCase):
|
|||
self._DiffGold(self.output[0], 'bear-640x360-v-golden.mp4')
|
||||
self._DiffGold(self.mpd_output, 'bear-640x360-v-golden.mpd')
|
||||
|
||||
def testPackage(self):
|
||||
self.packager.Package(self._GetStreams(['audio', 'video']),
|
||||
def testPackageText(self):
|
||||
self.packager.Package(self._GetStreams(['text'],
|
||||
test_file='subtitle-english.vtt'),
|
||||
self._GetFlags())
|
||||
self._DiffGold(self.output[0], 'subtitle-english-golden.vtt')
|
||||
self._DiffGold(self.mpd_output, 'subtitle-english-vtt-golden.mpd')
|
||||
|
||||
# Probably one of the most common scenarios is to package audio and video.
|
||||
def testPackageAudioVideo(self):
|
||||
self.packager.Package(self._GetStreams(['audio', 'video']), self._GetFlags())
|
||||
self._DiffGold(self.output[0], 'bear-640x360-a-golden.mp4')
|
||||
self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4')
|
||||
self._DiffGold(self.mpd_output, 'bear-640x360-av-golden.mpd')
|
||||
|
||||
# Package all video, audio, and text.
|
||||
def testPackageVideoAudioText(self):
|
||||
audio_video_streams = self._GetStreams(['audio', 'video'])
|
||||
text_stream = self._GetStreams(['text'], test_file='subtitle-english.vtt')
|
||||
self.packager.Package(audio_video_streams + text_stream, self._GetFlags())
|
||||
self._DiffGold(self.output[0], 'bear-640x360-a-golden.mp4')
|
||||
self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4')
|
||||
self._DiffGold(self.output[2], 'subtitle-english-golden.vtt')
|
||||
self._DiffGold(self.mpd_output, 'bear-640x360-avt-golden.mpd')
|
||||
|
||||
def testPackageWithEncryption(self):
|
||||
self.packager.Package(self._GetStreams(['audio', 'video']),
|
||||
self._GetFlags(encryption=True))
|
||||
|
@ -208,6 +226,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
def _GetStreams(self, stream_descriptors, live = False,
|
||||
test_file = 'bear-640x360.mp4'):
|
||||
streams = []
|
||||
if not self.output:
|
||||
self.output = []
|
||||
|
||||
test_data_dir = os.path.join(
|
||||
|
@ -221,12 +240,20 @@ class PackagerAppTest(unittest.TestCase):
|
|||
'segment_template=%s-$Number$.m4s')
|
||||
streams.append(stream % (input, stream_descriptor, output, output))
|
||||
else:
|
||||
output = '%s_%s.mp4' % (self.output_prefix, stream_descriptor)
|
||||
output = '%s_%s.%s' % (self.output_prefix,
|
||||
stream_descriptor,
|
||||
self._GetExtension(stream_descriptor))
|
||||
stream = 'input=%s,stream=%s,output=%s'
|
||||
streams.append(stream % (input, stream_descriptor, output))
|
||||
self.output.append(output)
|
||||
return streams
|
||||
|
||||
def _GetExtension(self, stream_descriptor):
|
||||
# TODO(rkuroiwa): Support ttml.
|
||||
if stream_descriptor == "text":
|
||||
return "vtt"
|
||||
return "mp4"
|
||||
|
||||
def _GetFlags(self, encryption = False, random_iv = False,
|
||||
widevine_encryption = False, key_rotation = False,
|
||||
live = False, dash_if_iop=False, output_media_info = False,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S">
|
||||
<Period>
|
||||
<AdaptationSet id="0" contentType="text">
|
||||
<Representation id="0" bandwidth="256" mimeType="text/vtt">
|
||||
<BaseURL>output_text.vtt</BaseURL>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="video" width="640" height="360" frameRate="30000/1001" subSegmentAlignment="true" par="16:9">
|
||||
<Representation id="1" bandwidth="881637" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
|
||||
<BaseURL>output_video.mp4</BaseURL>
|
||||
<SegmentBase indexRange="677-744" timescale="30000">
|
||||
<Initialization range="0-676"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="2" contentType="audio" subSegmentAlignment="true">
|
||||
<Representation id="2" bandwidth="126087" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<BaseURL>output_audio.mp4</BaseURL>
|
||||
<SegmentBase indexRange="611-678" timescale="44100">
|
||||
<Initialization range="0-610"/>
|
||||
</SegmentBase>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
|
@ -0,0 +1,79 @@
|
|||
WEBVTT FILE
|
||||
|
||||
1
|
||||
00:00:03.837 --> 00:00:07.299
|
||||
Captain's log, stardate 41636.9.
|
||||
|
||||
2
|
||||
00:00:07.466 --> 00:00:11.845
|
||||
As feared, our examination of the
|
||||
overdue Federation freighter Odin,
|
||||
|
||||
3
|
||||
00:00:12.012 --> 00:00:16.475
|
||||
disabled by an asteroid collision,
|
||||
revealed no life signs.
|
||||
|
||||
4
|
||||
00:00:16.642 --> 00:00:19.019
|
||||
However three escape pods
|
||||
were missing,
|
||||
|
||||
5
|
||||
00:00:19.186 --> 00:00:21.939
|
||||
suggesting
|
||||
the possibility of survivors.
|
||||
|
||||
6
|
||||
00:00:22.606 --> 00:00:27.861
|
||||
- Ready to orbit Angel One.
|
||||
- What kind of place is this, Data?
|
||||
|
||||
7
|
||||
00:00:28.028 --> 00:00:31.615
|
||||
A Class-M planet supporting
|
||||
carbon-based flora and fauna,
|
||||
|
||||
8
|
||||
00:00:31.782 --> 00:00:34.326
|
||||
sparsely populated
|
||||
with intelligent life.
|
||||
|
||||
9
|
||||
00:00:34.493 --> 00:00:38.497
|
||||
Similar in technological development
|
||||
to mid-20th century Earth.
|
||||
|
||||
10
|
||||
00:00:38.664 --> 00:00:41.000
|
||||
Kinda like being marooned at home.
|
||||
|
||||
11
|
||||
00:00:41.166 --> 00:00:43.586
|
||||
Assuming any survivors
|
||||
made it this far.
|
||||
|
||||
12
|
||||
00:00:43.794 --> 00:00:49.174
|
||||
It is the closest planet, but to
|
||||
go the distance we did in two days,
|
||||
|
||||
13
|
||||
00:00:49.341 --> 00:00:52.344
|
||||
would've taken the Odin escape pod
|
||||
five months.
|
||||
|
||||
14
|
||||
00:00:52.511 --> 00:00:54.680
|
||||
Five months, six days, 11 hours,
|
||||
two min...
|
||||
|
||||
15
|
||||
00:00:54.847 --> 00:00:58.392
|
||||
- Thank you, Data.
|
||||
- ...and 57 seconds.
|
||||
|
||||
16
|
||||
00:00:58.559 --> 00:01:01.353
|
||||
Receiving an audio signal
|
||||
from Angel One.
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT0S">
|
||||
<Period>
|
||||
<AdaptationSet id="0" contentType="text">
|
||||
<Representation id="0" bandwidth="256" mimeType="text/vtt">
|
||||
<BaseURL>output_text.vtt</BaseURL>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
|
@ -1618,6 +1618,29 @@ static MediaContainerName LookupContainerByFirst4(const uint8_t* buffer,
|
|||
return CONTAINER_UNKNOWN;
|
||||
}
|
||||
|
||||
namespace {
|
||||
const char kWebVtt[] = "WEBVTT";
|
||||
|
||||
bool CheckWebVtt(const uint8_t* buffer, int buffer_size) {
|
||||
const int offset =
|
||||
StartsWith(buffer, buffer_size, UTF8_BYTE_ORDER_MARK) ? 3 : 0;
|
||||
|
||||
return StartsWith(buffer + offset, buffer_size - offset,
|
||||
reinterpret_cast<const uint8_t*>(kWebVtt),
|
||||
arraysize(kWebVtt) - 1);
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): This check is a very simple check to see if it is UTF-8 or
|
||||
// UTF-16, which is not sufficient to determine whether it is TTML. Check if the
|
||||
// entire buffer is a valid TTML.
|
||||
bool CheckTtml(const uint8_t* buffer, int buffer_size) {
|
||||
return StartsWith(buffer, buffer_size,
|
||||
"<?xml version='1.0' encoding='UTF-8'?>") ||
|
||||
StartsWith(buffer, buffer_size,
|
||||
"<?xml version='1.0' encoding='UTF-16'?>");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Attempt to determine the container name from the buffer provided.
|
||||
MediaContainerName DetermineContainer(const uint8_t* buffer, int buffer_size) {
|
||||
DCHECK(buffer);
|
||||
|
@ -1632,6 +1655,10 @@ MediaContainerName DetermineContainer(const uint8_t* buffer, int buffer_size) {
|
|||
if (result != CONTAINER_UNKNOWN)
|
||||
return result;
|
||||
|
||||
// WebVTT check only checks for the first few bytes.
|
||||
if (CheckWebVtt(buffer, buffer_size))
|
||||
return CONTAINER_WEBVTT;
|
||||
|
||||
// Additional checks that may scan a portion of the buffer.
|
||||
if (CheckMpeg2ProgramStream(buffer, buffer_size))
|
||||
return CONTAINER_MPEG2PS;
|
||||
|
@ -1666,6 +1693,11 @@ MediaContainerName DetermineContainer(const uint8_t* buffer, int buffer_size) {
|
|||
return CONTAINER_EAC3;
|
||||
}
|
||||
|
||||
// To do a TTML check, it (should) do a schema check which requires scanning
|
||||
// the whole content.
|
||||
if (CheckTtml(buffer, buffer_size))
|
||||
return CONTAINER_TTML;
|
||||
|
||||
return CONTAINER_UNKNOWN;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,9 +47,11 @@ enum MediaContainerName {
|
|||
CONTAINER_RM, // RM (RealMedia)
|
||||
CONTAINER_SRT, // SRT (SubRip subtitle)
|
||||
CONTAINER_SWF, // SWF (ShockWave Flash)
|
||||
CONTAINER_TTML, // TTML file.
|
||||
CONTAINER_VC1, // VC-1
|
||||
CONTAINER_WAV, // WAV / WAVE (Waveform Audio)
|
||||
CONTAINER_WEBM, // Matroska / WebM
|
||||
CONTAINER_WEBVTT, // WebVTT file.
|
||||
CONTAINER_WTV, // WTV (Windows Television)
|
||||
CONTAINER_MAX // Must be last
|
||||
};
|
||||
|
|
|
@ -19,8 +19,8 @@ namespace edash_packager {
|
|||
namespace media {
|
||||
|
||||
VodMediaInfoDumpMuxerListener::VodMediaInfoDumpMuxerListener(
|
||||
const std::string& output_file_name)
|
||||
: output_file_name_(output_file_name), is_encrypted_(false) {}
|
||||
const std::string& output_file_path)
|
||||
: output_file_name_(output_file_path), is_encrypted_(false) {}
|
||||
|
||||
VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {}
|
||||
|
||||
|
@ -97,7 +97,7 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(bool has_init_range,
|
|||
LOG(ERROR) << "Failed to generate VOD information from input.";
|
||||
return;
|
||||
}
|
||||
SerializeMediaInfoToFile();
|
||||
WriteMediaInfoToFile(*media_info_, output_file_name_);
|
||||
}
|
||||
|
||||
void VodMediaInfoDumpMuxerListener::OnNewSegment(uint64_t start_time,
|
||||
|
@ -105,17 +105,20 @@ void VodMediaInfoDumpMuxerListener::OnNewSegment(uint64_t start_time,
|
|||
uint64_t segment_file_size) {
|
||||
}
|
||||
|
||||
bool VodMediaInfoDumpMuxerListener::SerializeMediaInfoToFile() {
|
||||
// static
|
||||
bool VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile(
|
||||
const edash_packager::MediaInfo& media_info,
|
||||
const std::string& output_file_path) {
|
||||
std::string output_string;
|
||||
if (!google::protobuf::TextFormat::PrintToString(*media_info_,
|
||||
if (!google::protobuf::TextFormat::PrintToString(media_info,
|
||||
&output_string)) {
|
||||
LOG(ERROR) << "Failed to serialize MediaInfo to string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
media::File* file = File::Open(output_file_name_.c_str(), "w");
|
||||
media::File* file = File::Open(output_file_path.c_str(), "w");
|
||||
if (!file) {
|
||||
LOG(ERROR) << "Failed to open " << output_file_name_;
|
||||
LOG(ERROR) << "Failed to open " << output_file_path;
|
||||
return false;
|
||||
}
|
||||
if (file->Write(output_string.data(), output_string.size()) <= 0) {
|
||||
|
@ -124,7 +127,7 @@ bool VodMediaInfoDumpMuxerListener::SerializeMediaInfoToFile() {
|
|||
return false;
|
||||
}
|
||||
if (!file->Close()) {
|
||||
LOG(ERROR) << "Failed to close " << output_file_name_;
|
||||
LOG(ERROR) << "Failed to close " << output_file_path;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -59,9 +59,16 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
|
|||
uint64_t segment_file_size) override;
|
||||
/// @}
|
||||
|
||||
/// Write @a media_info to @a output_file_path in human readable format.
|
||||
/// @param media_info is the MediaInfo to write out.
|
||||
/// @param output_file_path is the path of the output file.
|
||||
/// @return true on success, false otherwise.
|
||||
// TODO(rkuroiwa): Move this to muxer_listener_internal and rename
|
||||
// muxer_listener_internal to muxer_listener_util.
|
||||
static bool WriteMediaInfoToFile(const MediaInfo& media_info,
|
||||
const std::string& output_file_path);
|
||||
|
||||
private:
|
||||
// Write |media_info_| to |output_file_name_|.
|
||||
bool SerializeMediaInfoToFile();
|
||||
|
||||
std::string output_file_name_;
|
||||
std::string scheme_id_uri_;
|
||||
|
|
|
@ -175,5 +175,35 @@ bool File::ReadFileToString(const char* file_name, std::string* contents) {
|
|||
return len == 0;
|
||||
}
|
||||
|
||||
bool File::Copy(const char* from_file_name, const char* to_file_name) {
|
||||
std::string content;
|
||||
if (!ReadFileToString(from_file_name, &content)) {
|
||||
LOG(ERROR) << "Failed to open file " << from_file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
scoped_ptr<edash_packager::media::File, edash_packager::media::FileCloser>
|
||||
output_file(edash_packager::media::File::Open(to_file_name, "w"));
|
||||
if (!output_file) {
|
||||
LOG(ERROR) << "Failed to write to " << to_file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t bytes_left = content.size();
|
||||
uint64_t total_bytes_written = 0;
|
||||
const char* content_cstr = content.c_str();
|
||||
while (bytes_left > total_bytes_written) {
|
||||
const int64_t bytes_written =
|
||||
output_file->Write(content_cstr + total_bytes_written, bytes_left);
|
||||
if (bytes_written < 0) {
|
||||
LOG(ERROR) << "Failure while writing to " << to_file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
total_bytes_written += bytes_written;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
|
|
@ -102,6 +102,14 @@ class File {
|
|||
/// @return true on success, false otherwise.
|
||||
static bool ReadFileToString(const char* file_name, std::string* contents);
|
||||
|
||||
/// Copies files. This is not good for copying huge files. Although not
|
||||
/// recommended, it is safe to have source file and destination file name be
|
||||
/// the same.
|
||||
/// @param from_file_name is the source file name.
|
||||
/// @param to_file_name is the destination file name.
|
||||
/// @return true on success, false otherwise.
|
||||
static bool Copy(const char* from_file_name, const char* to_file_name);
|
||||
|
||||
protected:
|
||||
explicit File(const std::string& file_name) : file_name_(file_name) {}
|
||||
/// Do *not* call the destructor directly (with the "delete" keyword)
|
||||
|
|
|
@ -60,6 +60,30 @@ TEST_F(LocalFileTest, Size) {
|
|||
ASSERT_EQ(kDataSize, File::GetFileSize(local_file_name_.c_str()));
|
||||
}
|
||||
|
||||
TEST_F(LocalFileTest, Copy) {
|
||||
ASSERT_EQ(kDataSize,
|
||||
base::WriteFile(test_file_path_, data_.data(), kDataSize));
|
||||
|
||||
base::FilePath temp_dir;
|
||||
ASSERT_TRUE(base::CreateNewTempDirectory("", &temp_dir));
|
||||
|
||||
// Copy the test file to temp dir as filename "a".
|
||||
base::FilePath destination = temp_dir.Append("a");
|
||||
ASSERT_TRUE(
|
||||
File::Copy(local_file_name_.c_str(), destination.value().c_str()));
|
||||
|
||||
// Make a buffer bigger than the expected file content size to make sure that
|
||||
// there isn't extra stuff appended.
|
||||
char copied_file_content_buffer[kDataSize * 2] = {};
|
||||
ASSERT_EQ(kDataSize, base::ReadFile(destination,
|
||||
copied_file_content_buffer,
|
||||
arraysize(copied_file_content_buffer)));
|
||||
|
||||
ASSERT_EQ(data_, std::string(copied_file_content_buffer, kDataSize));
|
||||
|
||||
base::DeleteFile(temp_dir, true);
|
||||
}
|
||||
|
||||
TEST_F(LocalFileTest, Write) {
|
||||
// Write file using File API.
|
||||
File* file = File::Open(local_file_name_.c_str(), "w");
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
WEBVTT FILE
|
||||
|
||||
1
|
||||
00:00:03.837 --> 00:00:07.299
|
||||
Captain's log, stardate 41636.9.
|
||||
|
||||
2
|
||||
00:00:07.466 --> 00:00:11.845
|
||||
As feared, our examination of the
|
||||
overdue Federation freighter Odin,
|
||||
|
||||
3
|
||||
00:00:12.012 --> 00:00:16.475
|
||||
disabled by an asteroid collision,
|
||||
revealed no life signs.
|
||||
|
||||
4
|
||||
00:00:16.642 --> 00:00:19.019
|
||||
However three escape pods
|
||||
were missing,
|
||||
|
||||
5
|
||||
00:00:19.186 --> 00:00:21.939
|
||||
suggesting
|
||||
the possibility of survivors.
|
||||
|
||||
6
|
||||
00:00:22.606 --> 00:00:27.861
|
||||
- Ready to orbit Angel One.
|
||||
- What kind of place is this, Data?
|
||||
|
||||
7
|
||||
00:00:28.028 --> 00:00:31.615
|
||||
A Class-M planet supporting
|
||||
carbon-based flora and fauna,
|
||||
|
||||
8
|
||||
00:00:31.782 --> 00:00:34.326
|
||||
sparsely populated
|
||||
with intelligent life.
|
||||
|
||||
9
|
||||
00:00:34.493 --> 00:00:38.497
|
||||
Similar in technological development
|
||||
to mid-20th century Earth.
|
||||
|
||||
10
|
||||
00:00:38.664 --> 00:00:41.000
|
||||
Kinda like being marooned at home.
|
||||
|
||||
11
|
||||
00:00:41.166 --> 00:00:43.586
|
||||
Assuming any survivors
|
||||
made it this far.
|
||||
|
||||
12
|
||||
00:00:43.794 --> 00:00:49.174
|
||||
It is the closest planet, but to
|
||||
go the distance we did in two days,
|
||||
|
||||
13
|
||||
00:00:49.341 --> 00:00:52.344
|
||||
would've taken the Odin escape pod
|
||||
five months.
|
||||
|
||||
14
|
||||
00:00:52.511 --> 00:00:54.680
|
||||
Five months, six days, 11 hours,
|
||||
two min...
|
||||
|
||||
15
|
||||
00:00:54.847 --> 00:00:58.392
|
||||
- Thank you, Data.
|
||||
- ...and 57 seconds.
|
||||
|
||||
16
|
||||
00:00:58.559 --> 00:01:01.353
|
||||
Receiving an audio signal
|
||||
from Angel One.
|
Loading…
Reference in New Issue