blob: 48d5deeb4aae662667916ec21df2e6cee55fe126 [file] [log] [blame]
Mike Reedc0ee21f2019-05-28 16:11:03 -04001/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Klein4b432fa2019-06-06 11:44:05 -05008#include "experimental/ffmpeg/SkVideoEncoder.h"
9#include "include/core/SkCanvas.h"
Mike Reedf755bc72019-09-20 11:37:36 -040010#include "include/core/SkGraphics.h"
Mike Klein4b432fa2019-06-06 11:44:05 -050011#include "include/core/SkStream.h"
12#include "include/core/SkSurface.h"
13#include "include/core/SkTime.h"
Mike Reedc0ee21f2019-05-28 16:11:03 -040014#include "modules/skottie/include/Skottie.h"
Mike Reed5093e232019-05-30 10:10:50 -040015#include "modules/skottie/utils/SkottieUtils.h"
Mike Reed5093e232019-05-30 10:10:50 -040016#include "src/utils/SkOSPath.h"
Mike Reed5f12eaa2019-09-24 12:01:58 -040017
Mike Reedc0ee21f2019-05-28 16:11:03 -040018#include "tools/flags/CommandLineFlags.h"
Mike Reed5f12eaa2019-09-24 12:01:58 -040019#include "tools/gpu/GrContextFactory.h"
20
21#include "include/gpu/GrContextOptions.h"
Mike Reedc0ee21f2019-05-28 16:11:03 -040022
23static DEFINE_string2(input, i, "", "skottie animation to render");
24static DEFINE_string2(output, o, "", "mp4 file to create");
Mike Reed5093e232019-05-30 10:10:50 -040025static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
Mike Reedc0ee21f2019-05-28 16:11:03 -040026static DEFINE_int_2(fps, f, 25, "fps");
27static DEFINE_bool2(verbose, v, false, "verbose mode");
Mike Reed5093e232019-05-30 10:10:50 -040028static DEFINE_bool2(loop, l, false, "loop mode for profiling");
Mike Reed2d241f52019-09-16 15:14:21 -040029static DEFINE_int(set_dst_width, 0, "set destination width (height will be computed)");
Mike Reed5f12eaa2019-09-24 12:01:58 -040030static DEFINE_bool2(gpu, g, false, "use GPU for rendering");
Mike Reed4b203ad2019-06-17 17:45:01 -040031
32static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame_time) {
33 anim->seekFrameTime(frame_time);
34 surf->getCanvas()->clear(SK_ColorWHITE);
35 anim->render(surf->getCanvas());
36}
37
Mike Reed5f12eaa2019-09-24 12:01:58 -040038struct AsyncRec {
39 SkImageInfo info;
40 SkVideoEncoder* encoder;
41};
42
Mike Reedc0ee21f2019-05-28 16:11:03 -040043int main(int argc, char** argv) {
Mike Reedf755bc72019-09-20 11:37:36 -040044 SkGraphics::Init();
45
Mike Reedc0ee21f2019-05-28 16:11:03 -040046 CommandLineFlags::SetUsage("Converts skottie to a mp4");
47 CommandLineFlags::Parse(argc, argv);
48
Mike Reed5093e232019-05-30 10:10:50 -040049 if (FLAGS_input.count() == 0) {
50 SkDebugf("-i input_file.json argument required\n");
Mike Reedc0ee21f2019-05-28 16:11:03 -040051 return -1;
52 }
53
Mike Reed5f12eaa2019-09-24 12:01:58 -040054 auto contextType = sk_gpu_test::GrContextFactory::kGL_ContextType;
55 GrContextOptions grCtxOptions;
56 sk_gpu_test::GrContextFactory factory(grCtxOptions);
57
Mike Reed5093e232019-05-30 10:10:50 -040058 SkString assetPath;
59 if (FLAGS_assetPath.count() > 0) {
60 assetPath.set(FLAGS_assetPath[0]);
61 } else {
62 assetPath = SkOSPath::Dirname(FLAGS_input[0]);
63 }
64 SkDebugf("assetPath %s\n", assetPath.c_str());
65
66 auto animation = skottie::Animation::Builder()
67 .setResourceProvider(skottie_utils::FileResourceProvider::Make(assetPath))
68 .makeFromFile(FLAGS_input[0]);
69 if (!animation) {
70 SkDebugf("failed to load %s\n", FLAGS_input[0]);
71 return -1;
72 }
73
Mike Reedc0ee21f2019-05-28 16:11:03 -040074 SkISize dim = animation->size().toRound();
75 double duration = animation->duration();
76 int fps = FLAGS_fps;
77 if (fps < 1) {
78 fps = 1;
79 } else if (fps > 240) {
80 fps = 240;
81 }
82
Mike Reed2d241f52019-09-16 15:14:21 -040083 float scale = 1;
84 if (FLAGS_set_dst_width > 0) {
85 scale = FLAGS_set_dst_width / (float)dim.width();
86 dim = { FLAGS_set_dst_width, SkScalarRoundToInt(scale * dim.height()) };
87 }
88
Mike Reed4b203ad2019-06-17 17:45:01 -040089 const int frames = SkScalarRoundToInt(duration * fps);
90 const double frame_duration = 1.0 / fps;
91
Mike Reedc0ee21f2019-05-28 16:11:03 -040092 if (FLAGS_verbose) {
Mike Reed2d241f52019-09-16 15:14:21 -040093 SkDebugf("Size %dx%d duration %g, fps %d, frame_duration %g\n",
Mike Reed4b203ad2019-06-17 17:45:01 -040094 dim.width(), dim.height(), duration, fps, frame_duration);
Mike Reedc0ee21f2019-05-28 16:11:03 -040095 }
96
Mike Reedc0ee21f2019-05-28 16:11:03 -040097 SkVideoEncoder encoder;
Mike Reedc0ee21f2019-05-28 16:11:03 -040098
Mike Reed5f12eaa2019-09-24 12:01:58 -040099 GrContext* context = nullptr;
100 sk_sp<SkSurface> surf;
Mike Reed4b203ad2019-06-17 17:45:01 -0400101 sk_sp<SkData> data;
102
103 do {
104 double loop_start = SkTime::GetSecs();
105
Mike Reedf97e8e92019-05-29 13:33:55 -0400106 encoder.beginRecording(dim, fps);
Mike Reed5f12eaa2019-09-24 12:01:58 -0400107 auto info = encoder.preferredInfo();
108
Mike Reed4b203ad2019-06-17 17:45:01 -0400109 // lazily allocate the surfaces
110 if (!surf) {
Mike Reed5f12eaa2019-09-24 12:01:58 -0400111 if (FLAGS_gpu) {
112 context = factory.getContextInfo(contextType).grContext();
113 surf = SkSurface::MakeRenderTarget(context,
114 SkBudgeted::kNo,
115 info,
116 0,
117 GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
118 nullptr);
119 if (!surf) {
120 context = nullptr;
121 }
122 }
123 if (!surf) {
124 surf = SkSurface::MakeRaster(info);
125 }
126 surf->getCanvas()->scale(scale, scale);
Mike Reed4b203ad2019-06-17 17:45:01 -0400127 }
128
Mike Reedc0ee21f2019-05-28 16:11:03 -0400129 for (int i = 0; i <= frames; ++i) {
Mike Reed4b203ad2019-06-17 17:45:01 -0400130 double ts = i * 1.0 / fps;
131 if (FLAGS_verbose) {
132 SkDebugf("rendering frame %d ts %g\n", i, ts);
133 }
134
135 double normal_time = i * 1.0 / frames;
136 double frame_time = normal_time * duration;
137
Mike Reed2d241f52019-09-16 15:14:21 -0400138 produce_frame(surf.get(), animation.get(), frame_time);
Mike Reed4b203ad2019-06-17 17:45:01 -0400139
Mike Reed5f12eaa2019-09-24 12:01:58 -0400140 AsyncRec asyncRec = { info, &encoder };
141 if (context) {
142 surf->asyncRescaleAndReadPixels(info, {0, 0, info.width(), info.height()},
143 SkSurface::RescaleGamma::kSrc, kNone_SkFilterQuality,
144 [](void* ctx, const void* data, size_t rb) {
145 AsyncRec* rec = (AsyncRec*)ctx;
146 rec->encoder->addFrame({rec->info, data, rb});
147 }, &asyncRec);
148 } else {
149 SkPixmap pm;
150 SkAssertResult(surf->peekPixels(&pm));
151 encoder.addFrame(pm);
152 }
Mike Reedc0ee21f2019-05-28 16:11:03 -0400153 }
Mike Reed4b203ad2019-06-17 17:45:01 -0400154 data = encoder.endRecording();
155
156 if (FLAGS_loop) {
157 double loop_dur = SkTime::GetSecs() - loop_start;
158 SkDebugf("recording secs %g, frames %d, recording fps %d\n",
159 loop_dur, frames, (int)(frames / loop_dur));
Mike Reed5093e232019-05-30 10:10:50 -0400160 }
Mike Reed4b203ad2019-06-17 17:45:01 -0400161 } while (FLAGS_loop);
Mike Reedc0ee21f2019-05-28 16:11:03 -0400162
Mike Reed5093e232019-05-30 10:10:50 -0400163 if (FLAGS_output.count() == 0) {
164 SkDebugf("missing -o output_file.mp4 argument\n");
165 return 0;
166 }
167
Mike Reedc0ee21f2019-05-28 16:11:03 -0400168 SkFILEWStream ostream(FLAGS_output[0]);
169 if (!ostream.isValid()) {
170 SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
171 return -1;
172 }
173 ostream.write(data->data(), data->size());
Mike Reed5093e232019-05-30 10:10:50 -0400174 return 0;
Mike Reedc0ee21f2019-05-28 16:11:03 -0400175}