2019-05-28 20:11:03 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2014 Google Inc.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
|
|
* found in the LICENSE file.
|
|
|
|
*/
|
|
|
|
|
2019-06-06 16:44:05 +00:00
|
|
|
#include "experimental/ffmpeg/SkVideoEncoder.h"
|
2019-06-03 15:04:20 +00:00
|
|
|
#include "include/core/SkCanvas.h"
|
2019-05-28 20:11:03 +00:00
|
|
|
#include "include/core/SkStream.h"
|
2019-06-06 16:44:05 +00:00
|
|
|
#include "include/core/SkSurface.h"
|
2019-05-28 20:11:03 +00:00
|
|
|
#include "include/core/SkTime.h"
|
2019-06-06 16:44:05 +00:00
|
|
|
#include "modules/skottie/include/Skottie.h"
|
|
|
|
#include "modules/skottie/utils/SkottieUtils.h"
|
2019-05-30 14:10:50 +00:00
|
|
|
#include "src/utils/SkOSPath.h"
|
2019-05-28 20:11:03 +00:00
|
|
|
#include "tools/flags/CommandLineFlags.h"
|
|
|
|
|
|
|
|
static DEFINE_string2(input, i, "", "skottie animation to render");
|
|
|
|
static DEFINE_string2(output, o, "", "mp4 file to create");
|
2019-05-30 14:10:50 +00:00
|
|
|
static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
|
2019-05-28 20:11:03 +00:00
|
|
|
static DEFINE_int_2(fps, f, 25, "fps");
|
|
|
|
static DEFINE_bool2(verbose, v, false, "verbose mode");
|
2019-05-30 14:10:50 +00:00
|
|
|
static DEFINE_bool2(loop, l, false, "loop mode for profiling");
|
2019-06-17 21:45:01 +00:00
|
|
|
static DEFINE_double(motion_angle, 180, "motion blur angle");
|
|
|
|
static DEFINE_double(motion_slope, 0, "motion blur slope");
|
|
|
|
static DEFINE_int(motion_samples, 1, "motion blur samples");
|
|
|
|
|
|
|
|
static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame_time) {
|
|
|
|
anim->seekFrameTime(frame_time);
|
|
|
|
surf->getCanvas()->clear(SK_ColorWHITE);
|
|
|
|
anim->render(surf->getCanvas());
|
|
|
|
}
|
|
|
|
|
|
|
|
static void produce_frame(SkSurface* surf, SkSurface* tmp, skottie::Animation* anim,
|
|
|
|
double frame_time, double frame_duration, double motion_radius,
|
|
|
|
int motion_samples) {
|
|
|
|
double samples_duration = frame_duration * motion_radius * 2;
|
|
|
|
double dt = samples_duration / (motion_samples - 1);
|
|
|
|
double t = frame_time - samples_duration / 2;
|
|
|
|
|
|
|
|
SkPaint paint;
|
|
|
|
paint.setAlphaf(1.0f / motion_samples);
|
|
|
|
paint.setBlendMode(SkBlendMode::kPlus);
|
|
|
|
surf->getCanvas()->clear(0);
|
|
|
|
|
|
|
|
for (int i = 0; i < motion_samples; ++i) {
|
|
|
|
if (FLAGS_verbose) {
|
|
|
|
SkDebugf("time %g sample_time %g\n", frame_time, t);
|
|
|
|
}
|
|
|
|
produce_frame(tmp, anim, t);
|
|
|
|
t += dt;
|
|
|
|
tmp->draw(surf->getCanvas(), 0, 0, &paint);
|
|
|
|
}
|
|
|
|
}
|
2019-05-28 20:11:03 +00:00
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
CommandLineFlags::SetUsage("Converts skottie to a mp4");
|
|
|
|
CommandLineFlags::Parse(argc, argv);
|
|
|
|
|
2019-05-30 14:10:50 +00:00
|
|
|
if (FLAGS_input.count() == 0) {
|
|
|
|
SkDebugf("-i input_file.json argument required\n");
|
2019-05-28 20:11:03 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2019-05-30 14:10:50 +00:00
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
if (FLAGS_motion_angle < 0 || FLAGS_motion_angle > 360) {
|
|
|
|
SkDebugf("--motion_angle must be [0...360]\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (FLAGS_motion_slope < -1 || FLAGS_motion_slope > 1) {
|
|
|
|
SkDebugf("--motion_slope must be [-1...1]\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (FLAGS_motion_samples < 1) {
|
|
|
|
SkDebugf("--motion_samples must be >= 1\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// map angle=180 to radius=1/4 (of a frame duration)
|
|
|
|
double motion_radius = FLAGS_motion_angle * 0.25 / 180.0;
|
|
|
|
if (FLAGS_motion_samples == 1) {
|
|
|
|
motion_radius = 0; // no blur if we're only 1 sample
|
|
|
|
}
|
|
|
|
|
2019-05-30 14:10:50 +00:00
|
|
|
SkString assetPath;
|
|
|
|
if (FLAGS_assetPath.count() > 0) {
|
|
|
|
assetPath.set(FLAGS_assetPath[0]);
|
|
|
|
} else {
|
|
|
|
assetPath = SkOSPath::Dirname(FLAGS_input[0]);
|
|
|
|
}
|
|
|
|
SkDebugf("assetPath %s\n", assetPath.c_str());
|
|
|
|
|
|
|
|
auto animation = skottie::Animation::Builder()
|
|
|
|
.setResourceProvider(skottie_utils::FileResourceProvider::Make(assetPath))
|
|
|
|
.makeFromFile(FLAGS_input[0]);
|
|
|
|
if (!animation) {
|
|
|
|
SkDebugf("failed to load %s\n", FLAGS_input[0]);
|
2019-05-28 20:11:03 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkISize dim = animation->size().toRound();
|
|
|
|
double duration = animation->duration();
|
|
|
|
int fps = FLAGS_fps;
|
|
|
|
if (fps < 1) {
|
|
|
|
fps = 1;
|
|
|
|
} else if (fps > 240) {
|
|
|
|
fps = 240;
|
|
|
|
}
|
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
const int frames = SkScalarRoundToInt(duration * fps);
|
|
|
|
const double frame_duration = 1.0 / fps;
|
|
|
|
|
2019-05-28 20:11:03 +00:00
|
|
|
if (FLAGS_verbose) {
|
2019-06-17 21:45:01 +00:00
|
|
|
SkDebugf("size %dx%d duration %g, fps %d, frame_duration %g\n",
|
|
|
|
dim.width(), dim.height(), duration, fps, frame_duration);
|
2019-05-28 20:11:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SkVideoEncoder encoder;
|
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
sk_sp<SkSurface> surf, tmp_surf;
|
|
|
|
sk_sp<SkData> data;
|
2019-05-28 20:11:03 +00:00
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
do {
|
|
|
|
double loop_start = SkTime::GetSecs();
|
2019-05-28 20:11:03 +00:00
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
encoder.beginRecording(dim, fps);
|
|
|
|
// lazily allocate the surfaces
|
|
|
|
if (!surf) {
|
|
|
|
surf = SkSurface::MakeRaster(encoder.preferredInfo());
|
|
|
|
tmp_surf = surf->makeSurface(surf->width(), surf->height());
|
|
|
|
}
|
2019-06-03 15:04:20 +00:00
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
for (int i = 0; i <= frames; ++i) {
|
|
|
|
double ts = i * 1.0 / fps;
|
|
|
|
if (FLAGS_verbose) {
|
|
|
|
SkDebugf("rendering frame %d ts %g\n", i, ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
double normal_time = i * 1.0 / frames;
|
|
|
|
double frame_time = normal_time * duration;
|
|
|
|
|
|
|
|
if (motion_radius > 0) {
|
|
|
|
produce_frame(surf.get(), tmp_surf.get(), animation.get(), frame_time, frame_duration,
|
|
|
|
motion_radius, FLAGS_motion_samples);
|
|
|
|
} else {
|
|
|
|
produce_frame(surf.get(), animation.get(), frame_time);
|
|
|
|
}
|
|
|
|
|
|
|
|
SkPixmap pm;
|
|
|
|
SkAssertResult(surf->peekPixels(&pm));
|
|
|
|
encoder.addFrame(pm);
|
2019-05-28 20:11:03 +00:00
|
|
|
}
|
2019-06-17 21:45:01 +00:00
|
|
|
data = encoder.endRecording();
|
2019-06-03 15:04:20 +00:00
|
|
|
|
2019-06-17 21:45:01 +00:00
|
|
|
if (FLAGS_loop) {
|
|
|
|
double loop_dur = SkTime::GetSecs() - loop_start;
|
|
|
|
SkDebugf("recording secs %g, frames %d, recording fps %d\n",
|
|
|
|
loop_dur, frames, (int)(frames / loop_dur));
|
|
|
|
}
|
|
|
|
} while (FLAGS_loop);
|
2019-05-28 20:11:03 +00:00
|
|
|
|
2019-05-30 14:10:50 +00:00
|
|
|
if (FLAGS_output.count() == 0) {
|
|
|
|
SkDebugf("missing -o output_file.mp4 argument\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-05-28 20:11:03 +00:00
|
|
|
SkFILEWStream ostream(FLAGS_output[0]);
|
|
|
|
if (!ostream.isValid()) {
|
|
|
|
SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
ostream.write(data->data(), data->size());
|
2019-05-30 14:10:50 +00:00
|
|
|
return 0;
|
2019-05-28 20:11:03 +00:00
|
|
|
}
|