| // 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; |
| } |