You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
803 lines
30 KiB
803 lines
30 KiB
// Copyright (c) 2011 The WebM project authors. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
#include <stdint.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
// libwebm common includes.
|
|
#include "common/file_util.h"
|
|
#include "common/hdr_util.h"
|
|
|
|
// libwebm mkvparser includes
|
|
#include "mkvparser/mkvparser.h"
|
|
#include "mkvparser/mkvreader.h"
|
|
|
|
// libwebm mkvmuxer includes
|
|
#include "mkvmuxer/mkvmuxer.h"
|
|
#include "mkvmuxer/mkvmuxertypes.h"
|
|
#include "mkvmuxer/mkvwriter.h"
|
|
|
|
#include "sample_muxer_metadata.h"
|
|
|
|
namespace {
|
|
|
|
void Usage() {
|
|
printf("Usage: mkvmuxer_sample -i input -o output [options]\n");
|
|
printf("\n");
|
|
printf("Main options:\n");
|
|
printf(" -h | -? show help\n");
|
|
printf(" -video <int> >0 outputs video\n");
|
|
printf(" -audio <int> >0 outputs audio\n");
|
|
printf(" -live <int> >0 puts the muxer into live mode\n");
|
|
printf(" 0 puts the muxer into file mode\n");
|
|
printf(" -output_cues <int> >0 outputs cues element\n");
|
|
printf(" -cues_on_video_track <int> >0 outputs cues on video track\n");
|
|
printf(" -cues_on_audio_track <int> >0 outputs cues on audio track\n");
|
|
printf(" -max_cluster_duration <double> in seconds\n");
|
|
printf(" -max_cluster_size <int> in bytes\n");
|
|
printf(" -switch_tracks <int> >0 switches tracks in output\n");
|
|
printf(" -audio_track_number <int> >0 Changes the audio track number\n");
|
|
printf(" -video_track_number <int> >0 Changes the video track number\n");
|
|
printf(" -chunking <string> Chunk output\n");
|
|
printf(" -copy_tags <int> >0 Copies the tags\n");
|
|
printf(" -accurate_cluster_duration <int> ");
|
|
printf(">0 Writes the last frame in each cluster with Duration\n");
|
|
printf(" -fixed_size_cluster_timecode <int> ");
|
|
printf(">0 Writes the cluster timecode using exactly 8 bytes\n");
|
|
printf(" -copy_input_duration >0 Copies the input duration\n");
|
|
printf("\n");
|
|
printf("Video options:\n");
|
|
printf(" -display_width <int> Display width in pixels\n");
|
|
printf(" -display_height <int> Display height in pixels\n");
|
|
printf(" -pixel_width <int> Override pixel width\n");
|
|
printf(" -pixel_height <int> Override pixel height\n");
|
|
printf(" -projection_type <int> Set/override projection type:\n");
|
|
printf(" 0: Rectangular\n");
|
|
printf(" 1: Equirectangular\n");
|
|
printf(" 2: Cube map\n");
|
|
printf(" 3: Mesh\n");
|
|
printf(" -projection_file <string> Override projection private data");
|
|
printf(" with contents of this file\n");
|
|
printf(" -projection_pose_yaw <float> Projection pose yaw\n");
|
|
printf(" -projection_pose_pitch <float> Projection pose pitch\n");
|
|
printf(" -projection_pose_roll <float> Projection pose roll\n");
|
|
printf(" -stereo_mode <int> 3D video mode\n");
|
|
printf("\n");
|
|
printf("VP9 options:\n");
|
|
printf(" -profile <int> VP9 profile\n");
|
|
printf(" -level <int> VP9 level\n");
|
|
printf("\n");
|
|
printf("Cues options:\n");
|
|
printf(" -output_cues_block_number <int> >0 outputs cue block number\n");
|
|
printf(" -cues_before_clusters <int> >0 puts Cues before Clusters\n");
|
|
printf("\n");
|
|
printf("Metadata options:\n");
|
|
printf(" -webvtt-subtitles <vttfile> ");
|
|
printf("add WebVTT subtitles as metadata track\n");
|
|
printf(" -webvtt-captions <vttfile> ");
|
|
printf("add WebVTT captions as metadata track\n");
|
|
printf(" -webvtt-descriptions <vttfile> ");
|
|
printf("add WebVTT descriptions as metadata track\n");
|
|
printf(" -webvtt-metadata <vttfile> ");
|
|
printf("add WebVTT subtitles as metadata track\n");
|
|
printf(" -webvtt-chapters <vttfile> ");
|
|
printf("add WebVTT chapters as MKV chapters element\n");
|
|
}
|
|
|
|
struct MetadataFile {
|
|
const char* name;
|
|
SampleMuxerMetadata::Kind kind;
|
|
};
|
|
|
|
typedef std::list<MetadataFile> metadata_files_t;
|
|
|
|
// Cache the WebVTT filenames specified as command-line args.
|
|
bool LoadMetadataFiles(const metadata_files_t& files,
|
|
SampleMuxerMetadata* metadata) {
|
|
typedef metadata_files_t::const_iterator iter_t;
|
|
|
|
iter_t i = files.begin();
|
|
const iter_t j = files.end();
|
|
|
|
while (i != j) {
|
|
const metadata_files_t::value_type& v = *i++;
|
|
|
|
if (!metadata->Load(v.name, v.kind))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int ParseArgWebVTT(char* argv[], int* argv_index, int argc_check,
|
|
metadata_files_t* metadata_files) {
|
|
int& i = *argv_index;
|
|
|
|
enum { kCount = 5 };
|
|
struct Arg {
|
|
const char* name;
|
|
SampleMuxerMetadata::Kind kind;
|
|
};
|
|
const Arg args[kCount] = {
|
|
{"-webvtt-subtitles", SampleMuxerMetadata::kSubtitles},
|
|
{"-webvtt-captions", SampleMuxerMetadata::kCaptions},
|
|
{"-webvtt-descriptions", SampleMuxerMetadata::kDescriptions},
|
|
{"-webvtt-metadata", SampleMuxerMetadata::kMetadata},
|
|
{"-webvtt-chapters", SampleMuxerMetadata::kChapters}};
|
|
|
|
for (int idx = 0; idx < kCount; ++idx) {
|
|
const Arg& arg = args[idx];
|
|
|
|
if (strcmp(arg.name, argv[i]) != 0) // no match
|
|
continue;
|
|
|
|
++i; // consume arg name here
|
|
|
|
if (i > argc_check) {
|
|
printf("missing value for %s\n", arg.name);
|
|
return -1; // error
|
|
}
|
|
|
|
MetadataFile f;
|
|
f.name = argv[i]; // arg value is consumed via caller's loop idx
|
|
f.kind = arg.kind;
|
|
|
|
metadata_files->push_back(f);
|
|
return 1; // successfully parsed WebVTT arg
|
|
}
|
|
|
|
return 0; // not a WebVTT arg
|
|
}
|
|
|
|
bool CopyVideoProjection(const mkvparser::Projection& parser_projection,
|
|
mkvmuxer::Projection* muxer_projection) {
|
|
typedef mkvmuxer::Projection::ProjectionType MuxerProjType;
|
|
const int kTypeNotPresent = mkvparser::Projection::kTypeNotPresent;
|
|
if (parser_projection.type != kTypeNotPresent) {
|
|
muxer_projection->set_type(
|
|
static_cast<MuxerProjType>(parser_projection.type));
|
|
}
|
|
if (parser_projection.private_data &&
|
|
parser_projection.private_data_length > 0) {
|
|
if (!muxer_projection->SetProjectionPrivate(
|
|
parser_projection.private_data,
|
|
parser_projection.private_data_length)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const float kValueNotPresent = mkvparser::Projection::kValueNotPresent;
|
|
if (parser_projection.pose_yaw != kValueNotPresent)
|
|
muxer_projection->set_pose_yaw(parser_projection.pose_yaw);
|
|
if (parser_projection.pose_pitch != kValueNotPresent)
|
|
muxer_projection->set_pose_pitch(parser_projection.pose_pitch);
|
|
if (parser_projection.pose_roll != kValueNotPresent)
|
|
muxer_projection->set_pose_roll(parser_projection.pose_roll);
|
|
return true;
|
|
}
|
|
} // end namespace
|
|
|
|
int main(int argc, char* argv[]) {
|
|
char* input = NULL;
|
|
char* output = NULL;
|
|
|
|
// Segment variables
|
|
bool output_video = true;
|
|
bool output_audio = true;
|
|
bool live_mode = false;
|
|
bool output_cues = true;
|
|
bool cues_before_clusters = false;
|
|
bool cues_on_video_track = true;
|
|
bool cues_on_audio_track = false;
|
|
uint64_t max_cluster_duration = 0;
|
|
uint64_t max_cluster_size = 0;
|
|
bool switch_tracks = false;
|
|
int audio_track_number = 0; // 0 tells muxer to decide.
|
|
int video_track_number = 0; // 0 tells muxer to decide.
|
|
bool chunking = false;
|
|
bool copy_tags = false;
|
|
const char* chunk_name = NULL;
|
|
bool accurate_cluster_duration = false;
|
|
bool fixed_size_cluster_timecode = false;
|
|
bool copy_input_duration = false;
|
|
|
|
bool output_cues_block_number = true;
|
|
|
|
uint64_t display_width = 0;
|
|
uint64_t display_height = 0;
|
|
uint64_t pixel_width = 0;
|
|
uint64_t pixel_height = 0;
|
|
uint64_t stereo_mode = 0;
|
|
const char* projection_file = 0;
|
|
int64_t projection_type = mkvparser::Projection::kTypeNotPresent;
|
|
float projection_pose_roll = mkvparser::Projection::kValueNotPresent;
|
|
float projection_pose_pitch = mkvparser::Projection::kValueNotPresent;
|
|
float projection_pose_yaw = mkvparser::Projection::kValueNotPresent;
|
|
int vp9_profile = -1; // No profile set.
|
|
int vp9_level = -1; // No level set.
|
|
|
|
metadata_files_t metadata_files;
|
|
|
|
const int argc_check = argc - 1;
|
|
for (int i = 1; i < argc; ++i) {
|
|
char* end;
|
|
|
|
if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
|
|
Usage();
|
|
return EXIT_SUCCESS;
|
|
} else if (!strcmp("-i", argv[i]) && i < argc_check) {
|
|
input = argv[++i];
|
|
} else if (!strcmp("-o", argv[i]) && i < argc_check) {
|
|
output = argv[++i];
|
|
} else if (!strcmp("-video", argv[i]) && i < argc_check) {
|
|
output_video = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-audio", argv[i]) && i < argc_check) {
|
|
output_audio = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-live", argv[i]) && i < argc_check) {
|
|
live_mode = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-output_cues", argv[i]) && i < argc_check) {
|
|
output_cues = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-cues_before_clusters", argv[i]) && i < argc_check) {
|
|
cues_before_clusters = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-cues_on_video_track", argv[i]) && i < argc_check) {
|
|
cues_on_video_track = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
if (cues_on_video_track)
|
|
cues_on_audio_track = false;
|
|
} else if (!strcmp("-cues_on_audio_track", argv[i]) && i < argc_check) {
|
|
cues_on_audio_track = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
if (cues_on_audio_track)
|
|
cues_on_video_track = false;
|
|
} else if (!strcmp("-max_cluster_duration", argv[i]) && i < argc_check) {
|
|
const double seconds = strtod(argv[++i], &end);
|
|
max_cluster_duration = static_cast<uint64_t>(seconds * 1000000000.0);
|
|
} else if (!strcmp("-max_cluster_size", argv[i]) && i < argc_check) {
|
|
max_cluster_size = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-switch_tracks", argv[i]) && i < argc_check) {
|
|
switch_tracks = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-audio_track_number", argv[i]) && i < argc_check) {
|
|
audio_track_number = static_cast<int>(strtol(argv[++i], &end, 10));
|
|
} else if (!strcmp("-video_track_number", argv[i]) && i < argc_check) {
|
|
video_track_number = static_cast<int>(strtol(argv[++i], &end, 10));
|
|
} else if (!strcmp("-chunking", argv[i]) && i < argc_check) {
|
|
chunking = true;
|
|
chunk_name = argv[++i];
|
|
} else if (!strcmp("-copy_tags", argv[i]) && i < argc_check) {
|
|
copy_tags = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-accurate_cluster_duration", argv[i]) &&
|
|
i < argc_check) {
|
|
accurate_cluster_duration =
|
|
strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-fixed_size_cluster_timecode", argv[i]) &&
|
|
i < argc_check) {
|
|
fixed_size_cluster_timecode =
|
|
strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-copy_input_duration", argv[i]) && i < argc_check) {
|
|
copy_input_duration = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (!strcmp("-display_width", argv[i]) && i < argc_check) {
|
|
display_width = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-display_height", argv[i]) && i < argc_check) {
|
|
display_height = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-pixel_width", argv[i]) && i < argc_check) {
|
|
pixel_width = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-pixel_height", argv[i]) && i < argc_check) {
|
|
pixel_height = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-stereo_mode", argv[i]) && i < argc_check) {
|
|
stereo_mode = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-projection_type", argv[i]) && i < argc_check) {
|
|
projection_type = strtol(argv[++i], &end, 10);
|
|
} else if (!strcmp("-projection_file", argv[i]) && i < argc_check) {
|
|
projection_file = argv[++i];
|
|
} else if (!strcmp("-projection_pose_roll", argv[i]) && i < argc_check) {
|
|
projection_pose_roll = strtof(argv[++i], &end);
|
|
} else if (!strcmp("-projection_pose_pitch", argv[i]) && i < argc_check) {
|
|
projection_pose_pitch = strtof(argv[++i], &end);
|
|
} else if (!strcmp("-projection_pose_yaw", argv[i]) && i < argc_check) {
|
|
projection_pose_yaw = strtof(argv[++i], &end);
|
|
} else if (!strcmp("-profile", argv[i]) && i < argc_check) {
|
|
vp9_profile = static_cast<int>(strtol(argv[++i], &end, 10));
|
|
} else if (!strcmp("-level", argv[i]) && i < argc_check) {
|
|
vp9_level = static_cast<int>(strtol(argv[++i], &end, 10));
|
|
} else if (!strcmp("-output_cues_block_number", argv[i]) &&
|
|
i < argc_check) {
|
|
output_cues_block_number =
|
|
strtol(argv[++i], &end, 10) == 0 ? false : true;
|
|
} else if (int e = ParseArgWebVTT(argv, &i, argc_check, &metadata_files)) {
|
|
if (e < 0)
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (input == NULL || output == NULL) {
|
|
Usage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Get parser header info
|
|
mkvparser::MkvReader reader;
|
|
|
|
if (reader.Open(input)) {
|
|
printf("\n Filename is invalid or error while opening.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
long long pos = 0;
|
|
mkvparser::EBMLHeader ebml_header;
|
|
long long ret = ebml_header.Parse(&reader, pos);
|
|
if (ret) {
|
|
printf("\n EBMLHeader::Parse() failed.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
mkvparser::Segment* parser_segment_;
|
|
ret = mkvparser::Segment::CreateInstance(&reader, pos, parser_segment_);
|
|
if (ret) {
|
|
printf("\n Segment::CreateInstance() failed.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
const std::unique_ptr<mkvparser::Segment> parser_segment(parser_segment_);
|
|
ret = parser_segment->Load();
|
|
if (ret < 0) {
|
|
printf("\n Segment::Load() failed.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
const mkvparser::SegmentInfo* const segment_info = parser_segment->GetInfo();
|
|
if (segment_info == NULL) {
|
|
printf("\n Segment::GetInfo() failed.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
const long long timeCodeScale = segment_info->GetTimeCodeScale();
|
|
|
|
// Set muxer header info
|
|
mkvmuxer::MkvWriter writer;
|
|
|
|
const std::string temp_file =
|
|
cues_before_clusters ? libwebm::GetTempFileName() : output;
|
|
if (!writer.Open(temp_file.c_str())) {
|
|
printf("\n Filename is invalid or error while opening.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Set Segment element attributes
|
|
mkvmuxer::Segment muxer_segment;
|
|
|
|
if (!muxer_segment.Init(&writer)) {
|
|
printf("\n Could not initialize muxer segment!\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
muxer_segment.AccurateClusterDuration(accurate_cluster_duration);
|
|
muxer_segment.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode);
|
|
|
|
if (live_mode)
|
|
muxer_segment.set_mode(mkvmuxer::Segment::kLive);
|
|
else
|
|
muxer_segment.set_mode(mkvmuxer::Segment::kFile);
|
|
|
|
if (chunking)
|
|
muxer_segment.SetChunking(true, chunk_name);
|
|
|
|
if (max_cluster_duration > 0)
|
|
muxer_segment.set_max_cluster_duration(max_cluster_duration);
|
|
if (max_cluster_size > 0)
|
|
muxer_segment.set_max_cluster_size(max_cluster_size);
|
|
muxer_segment.OutputCues(output_cues);
|
|
|
|
// Set SegmentInfo element attributes
|
|
mkvmuxer::SegmentInfo* const info = muxer_segment.GetSegmentInfo();
|
|
info->set_timecode_scale(timeCodeScale);
|
|
info->set_writing_app("mkvmuxer_sample");
|
|
|
|
const mkvparser::Tags* const tags = parser_segment->GetTags();
|
|
if (copy_tags && tags) {
|
|
for (int i = 0; i < tags->GetTagCount(); i++) {
|
|
const mkvparser::Tags::Tag* const tag = tags->GetTag(i);
|
|
mkvmuxer::Tag* muxer_tag = muxer_segment.AddTag();
|
|
|
|
for (int j = 0; j < tag->GetSimpleTagCount(); j++) {
|
|
const mkvparser::Tags::SimpleTag* const simple_tag =
|
|
tag->GetSimpleTag(j);
|
|
muxer_tag->add_simple_tag(simple_tag->GetTagName(),
|
|
simple_tag->GetTagString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set Tracks element attributes
|
|
const mkvparser::Tracks* const parser_tracks = parser_segment->GetTracks();
|
|
unsigned long i = 0;
|
|
uint64_t vid_track = 0; // no track added
|
|
uint64_t aud_track = 0; // no track added
|
|
|
|
using mkvparser::Track;
|
|
|
|
while (i != parser_tracks->GetTracksCount()) {
|
|
unsigned long track_num = i++;
|
|
if (switch_tracks)
|
|
track_num = i % parser_tracks->GetTracksCount();
|
|
|
|
const Track* const parser_track = parser_tracks->GetTrackByIndex(track_num);
|
|
|
|
if (parser_track == NULL)
|
|
continue;
|
|
|
|
// TODO(fgalligan): Add support for language to parser.
|
|
const char* const track_name = parser_track->GetNameAsUTF8();
|
|
|
|
const long long track_type = parser_track->GetType();
|
|
|
|
if (track_type == Track::kVideo && output_video) {
|
|
// Get the video track from the parser
|
|
const mkvparser::VideoTrack* const pVideoTrack =
|
|
static_cast<const mkvparser::VideoTrack*>(parser_track);
|
|
const long long width = pVideoTrack->GetWidth();
|
|
const long long height = pVideoTrack->GetHeight();
|
|
|
|
// Add the video track to the muxer
|
|
vid_track = muxer_segment.AddVideoTrack(static_cast<int>(width),
|
|
static_cast<int>(height),
|
|
video_track_number);
|
|
if (!vid_track) {
|
|
printf("\n Could not add video track.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
mkvmuxer::VideoTrack* const video = static_cast<mkvmuxer::VideoTrack*>(
|
|
muxer_segment.GetTrackByNumber(vid_track));
|
|
if (!video) {
|
|
printf("\n Could not get video track.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (pVideoTrack->GetColour()) {
|
|
mkvmuxer::Colour muxer_colour;
|
|
if (!libwebm::CopyColour(*pVideoTrack->GetColour(), &muxer_colour))
|
|
return EXIT_FAILURE;
|
|
if (!video->SetColour(muxer_colour))
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (pVideoTrack->GetProjection() ||
|
|
projection_type != mkvparser::Projection::kTypeNotPresent) {
|
|
mkvmuxer::Projection muxer_projection;
|
|
const mkvparser::Projection* const parser_projection =
|
|
pVideoTrack->GetProjection();
|
|
typedef mkvmuxer::Projection::ProjectionType MuxerProjType;
|
|
if (parser_projection &&
|
|
!CopyVideoProjection(*parser_projection, &muxer_projection)) {
|
|
printf("\n Unable to copy video projection.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
// Override the values that came from parser if set on command line.
|
|
if (projection_type != mkvparser::Projection::kTypeNotPresent) {
|
|
muxer_projection.set_type(
|
|
static_cast<MuxerProjType>(projection_type));
|
|
if (projection_type == mkvparser::Projection::kRectangular &&
|
|
projection_file != NULL) {
|
|
printf("\n Rectangular projection must not have private data.\n");
|
|
return EXIT_FAILURE;
|
|
} else if ((projection_type == mkvparser::Projection::kCubeMap ||
|
|
projection_type == mkvparser::Projection::kMesh) &&
|
|
projection_file == NULL) {
|
|
printf("\n Mesh or CubeMap projection must have private data.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (projection_file != NULL) {
|
|
std::string contents;
|
|
if (!libwebm::GetFileContents(projection_file, &contents) ||
|
|
contents.size() == 0) {
|
|
printf("\n Failed to read file \"%s\" or file is empty\n",
|
|
projection_file);
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (!muxer_projection.SetProjectionPrivate(
|
|
reinterpret_cast<uint8_t*>(&contents[0]),
|
|
contents.size())) {
|
|
printf("\n Failed to SetProjectionPrivate of length %zu.\n",
|
|
contents.size());
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
const float kValueNotPresent = mkvparser::Projection::kValueNotPresent;
|
|
if (projection_pose_yaw != kValueNotPresent)
|
|
muxer_projection.set_pose_yaw(projection_pose_yaw);
|
|
if (projection_pose_pitch != kValueNotPresent)
|
|
muxer_projection.set_pose_pitch(projection_pose_pitch);
|
|
if (projection_pose_roll != kValueNotPresent)
|
|
muxer_projection.set_pose_roll(projection_pose_roll);
|
|
|
|
if (!video->SetProjection(muxer_projection))
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (track_name)
|
|
video->set_name(track_name);
|
|
|
|
video->set_codec_id(pVideoTrack->GetCodecId());
|
|
|
|
if (display_width > 0)
|
|
video->set_display_width(display_width);
|
|
if (display_height > 0)
|
|
video->set_display_height(display_height);
|
|
if (pixel_width > 0)
|
|
video->set_pixel_width(pixel_width);
|
|
if (pixel_height > 0)
|
|
video->set_pixel_height(pixel_height);
|
|
if (stereo_mode > 0)
|
|
video->SetStereoMode(stereo_mode);
|
|
|
|
const double rate = pVideoTrack->GetFrameRate();
|
|
if (rate > 0.0) {
|
|
video->set_frame_rate(rate);
|
|
}
|
|
|
|
size_t parser_private_size;
|
|
const unsigned char* const parser_private_data =
|
|
pVideoTrack->GetCodecPrivate(parser_private_size);
|
|
|
|
if (!strcmp(video->codec_id(), mkvmuxer::Tracks::kAv1CodecId)) {
|
|
if (parser_private_data == NULL || parser_private_size == 0) {
|
|
printf("AV1 input track has no CodecPrivate. %s is invalid.", input);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(video->codec_id(), mkvmuxer::Tracks::kVp9CodecId) &&
|
|
(vp9_profile >= 0 || vp9_level >= 0)) {
|
|
const int kMaxVp9PrivateSize = 6;
|
|
unsigned char vp9_private_data[kMaxVp9PrivateSize];
|
|
int vp9_private_size = 0;
|
|
if (vp9_profile >= 0) {
|
|
if (vp9_profile < 0 || vp9_profile > 3) {
|
|
printf("\n VP9 profile(%d) is not valid.\n", vp9_profile);
|
|
return EXIT_FAILURE;
|
|
}
|
|
const uint8_t kVp9ProfileId = 1;
|
|
const uint8_t kVp9ProfileIdLength = 1;
|
|
vp9_private_data[vp9_private_size++] = kVp9ProfileId;
|
|
vp9_private_data[vp9_private_size++] = kVp9ProfileIdLength;
|
|
vp9_private_data[vp9_private_size++] = vp9_profile;
|
|
}
|
|
|
|
if (vp9_level >= 0) {
|
|
const int kNumLevels = 14;
|
|
const int levels[kNumLevels] = {10, 11, 20, 21, 30, 31, 40,
|
|
41, 50, 51, 52, 60, 61, 62};
|
|
bool level_is_valid = false;
|
|
for (int i = 0; i < kNumLevels; ++i) {
|
|
if (vp9_level == levels[i]) {
|
|
level_is_valid = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!level_is_valid) {
|
|
printf("\n VP9 level(%d) is not valid.\n", vp9_level);
|
|
return EXIT_FAILURE;
|
|
}
|
|
const uint8_t kVp9LevelId = 2;
|
|
const uint8_t kVp9LevelIdLength = 1;
|
|
vp9_private_data[vp9_private_size++] = kVp9LevelId;
|
|
vp9_private_data[vp9_private_size++] = kVp9LevelIdLength;
|
|
vp9_private_data[vp9_private_size++] = vp9_level;
|
|
}
|
|
if (!video->SetCodecPrivate(vp9_private_data, vp9_private_size)) {
|
|
printf("\n Could not add video private data.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
} else if (parser_private_data && parser_private_size > 0) {
|
|
if (!video->SetCodecPrivate(parser_private_data, parser_private_size)) {
|
|
printf("\n Could not add video private data.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
} else if (track_type == Track::kAudio && output_audio) {
|
|
// Get the audio track from the parser
|
|
const mkvparser::AudioTrack* const pAudioTrack =
|
|
static_cast<const mkvparser::AudioTrack*>(parser_track);
|
|
const long long channels = pAudioTrack->GetChannels();
|
|
const double sample_rate = pAudioTrack->GetSamplingRate();
|
|
|
|
// Add the audio track to the muxer
|
|
aud_track = muxer_segment.AddAudioTrack(static_cast<int>(sample_rate),
|
|
static_cast<int>(channels),
|
|
audio_track_number);
|
|
if (!aud_track) {
|
|
printf("\n Could not add audio track.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
mkvmuxer::AudioTrack* const audio = static_cast<mkvmuxer::AudioTrack*>(
|
|
muxer_segment.GetTrackByNumber(aud_track));
|
|
if (!audio) {
|
|
printf("\n Could not get audio track.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (track_name)
|
|
audio->set_name(track_name);
|
|
|
|
audio->set_codec_id(pAudioTrack->GetCodecId());
|
|
|
|
size_t private_size;
|
|
const unsigned char* const private_data =
|
|
pAudioTrack->GetCodecPrivate(private_size);
|
|
if (private_size > 0) {
|
|
if (!audio->SetCodecPrivate(private_data, private_size)) {
|
|
printf("\n Could not add audio private data.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
const long long bit_depth = pAudioTrack->GetBitDepth();
|
|
if (bit_depth > 0)
|
|
audio->set_bit_depth(bit_depth);
|
|
|
|
if (pAudioTrack->GetCodecDelay())
|
|
audio->set_codec_delay(pAudioTrack->GetCodecDelay());
|
|
if (pAudioTrack->GetSeekPreRoll())
|
|
audio->set_seek_pre_roll(pAudioTrack->GetSeekPreRoll());
|
|
}
|
|
}
|
|
|
|
// We have created all the video and audio tracks. If any WebVTT
|
|
// files were specified as command-line args, then parse them and
|
|
// add a track to the output file corresponding to each metadata
|
|
// input file.
|
|
|
|
SampleMuxerMetadata metadata;
|
|
|
|
if (!metadata.Init(&muxer_segment)) {
|
|
printf("\n Could not initialize metadata cache.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (!LoadMetadataFiles(metadata_files, &metadata))
|
|
return EXIT_FAILURE;
|
|
|
|
if (!metadata.AddChapters())
|
|
return EXIT_FAILURE;
|
|
|
|
// Set Cues element attributes
|
|
mkvmuxer::Cues* const cues = muxer_segment.GetCues();
|
|
cues->set_output_block_number(output_cues_block_number);
|
|
if (cues_on_video_track && vid_track)
|
|
muxer_segment.CuesTrack(vid_track);
|
|
if (cues_on_audio_track && aud_track)
|
|
muxer_segment.CuesTrack(aud_track);
|
|
|
|
// Write clusters
|
|
unsigned char* data = NULL;
|
|
long data_len = 0;
|
|
|
|
const mkvparser::Cluster* cluster = parser_segment->GetFirst();
|
|
|
|
while (cluster != NULL && !cluster->EOS()) {
|
|
const mkvparser::BlockEntry* block_entry;
|
|
|
|
long status = cluster->GetFirst(block_entry);
|
|
|
|
if (status) {
|
|
printf("\n Could not get first block of cluster.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
while (block_entry != NULL && !block_entry->EOS()) {
|
|
const mkvparser::Block* const block = block_entry->GetBlock();
|
|
const long long trackNum = block->GetTrackNumber();
|
|
const mkvparser::Track* const parser_track =
|
|
parser_tracks->GetTrackByNumber(static_cast<unsigned long>(trackNum));
|
|
|
|
// When |parser_track| is NULL, it means that the track number in the
|
|
// Block is invalid (i.e.) the was no TrackEntry corresponding to the
|
|
// track number. So we reject the file.
|
|
if (!parser_track) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
const long long track_type = parser_track->GetType();
|
|
const long long time_ns = block->GetTime(cluster);
|
|
|
|
// Flush any metadata frames to the output file, before we write
|
|
// the current block.
|
|
if (!metadata.Write(time_ns))
|
|
return EXIT_FAILURE;
|
|
|
|
if ((track_type == Track::kAudio && output_audio) ||
|
|
(track_type == Track::kVideo && output_video)) {
|
|
const int frame_count = block->GetFrameCount();
|
|
|
|
for (int i = 0; i < frame_count; ++i) {
|
|
const mkvparser::Block::Frame& frame = block->GetFrame(i);
|
|
|
|
if (frame.len > data_len) {
|
|
delete[] data;
|
|
data = new unsigned char[frame.len];
|
|
if (!data)
|
|
return EXIT_FAILURE;
|
|
data_len = frame.len;
|
|
}
|
|
|
|
if (frame.Read(&reader, data))
|
|
return EXIT_FAILURE;
|
|
|
|
mkvmuxer::Frame muxer_frame;
|
|
if (!muxer_frame.Init(data, frame.len))
|
|
return EXIT_FAILURE;
|
|
muxer_frame.set_track_number(track_type == Track::kAudio ? aud_track
|
|
: vid_track);
|
|
if (block->GetDiscardPadding())
|
|
muxer_frame.set_discard_padding(block->GetDiscardPadding());
|
|
muxer_frame.set_timestamp(time_ns);
|
|
muxer_frame.set_is_key(block->IsKey());
|
|
if (!muxer_segment.AddGenericFrame(&muxer_frame)) {
|
|
printf("\n Could not add frame.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
status = cluster->GetNext(block_entry, block_entry);
|
|
|
|
if (status) {
|
|
printf("\n Could not get next block of cluster.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
cluster = parser_segment->GetNext(cluster);
|
|
}
|
|
|
|
// We have exhausted all video and audio frames in the input file.
|
|
// Flush any remaining metadata frames to the output file.
|
|
if (!metadata.Write(-1))
|
|
return EXIT_FAILURE;
|
|
|
|
if (copy_input_duration) {
|
|
const double input_duration =
|
|
static_cast<double>(segment_info->GetDuration()) / timeCodeScale;
|
|
muxer_segment.set_duration(input_duration);
|
|
}
|
|
|
|
if (!muxer_segment.Finalize()) {
|
|
printf("Finalization of segment failed.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
reader.Close();
|
|
writer.Close();
|
|
|
|
if (cues_before_clusters) {
|
|
if (reader.Open(temp_file.c_str())) {
|
|
printf("\n Filename is invalid or error while opening.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (!writer.Open(output)) {
|
|
printf("\n Filename is invalid or error while opening.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (!muxer_segment.CopyAndMoveCuesBeforeClusters(&reader, &writer)) {
|
|
printf("\n Unable to copy and move cues before clusters.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
reader.Close();
|
|
writer.Close();
|
|
remove(temp_file.c_str());
|
|
}
|
|
|
|
delete[] data;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|