• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
8 #include "experimental/ffmpeg/SkVideoEncoder.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkStream.h"
11 #include "include/core/SkSurface.h"
12 #include "include/core/SkTime.h"
13 #include "modules/skottie/include/Skottie.h"
14 #include "modules/skottie/utils/SkottieUtils.h"
15 #include "src/utils/SkOSPath.h"
16 #include "tools/flags/CommandLineFlags.h"
17 
18 static DEFINE_string2(input, i, "", "skottie animation to render");
19 static DEFINE_string2(output, o, "", "mp4 file to create");
20 static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
21 static DEFINE_int_2(fps, f, 25, "fps");
22 static DEFINE_bool2(verbose, v, false, "verbose mode");
23 static DEFINE_bool2(loop, l, false, "loop mode for profiling");
24 static DEFINE_double(motion_angle, 180, "motion blur angle");
25 static DEFINE_double(motion_slope, 0, "motion blur slope");
26 static DEFINE_int(motion_samples, 1, "motion blur samples");
27 
produce_frame(SkSurface * surf,skottie::Animation * anim,double frame_time)28 static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame_time) {
29     anim->seekFrameTime(frame_time);
30     surf->getCanvas()->clear(SK_ColorWHITE);
31     anim->render(surf->getCanvas());
32 }
33 
produce_frame(SkSurface * surf,SkSurface * tmp,skottie::Animation * anim,double frame_time,double frame_duration,double motion_radius,int motion_samples)34 static void produce_frame(SkSurface* surf, SkSurface* tmp, skottie::Animation* anim,
35                           double frame_time, double frame_duration, double motion_radius,
36                           int motion_samples) {
37     double samples_duration = frame_duration * motion_radius * 2;
38     double dt = samples_duration / (motion_samples - 1);
39     double t = frame_time - samples_duration / 2;
40 
41     SkPaint paint;
42     paint.setAlphaf(1.0f / motion_samples);
43     paint.setBlendMode(SkBlendMode::kPlus);
44     surf->getCanvas()->clear(0);
45 
46     for (int i = 0; i < motion_samples; ++i) {
47         if (FLAGS_verbose) {
48             SkDebugf("time %g sample_time %g\n", frame_time, t);
49         }
50         produce_frame(tmp, anim, t);
51         t += dt;
52         tmp->draw(surf->getCanvas(), 0, 0, &paint);
53     }
54 }
55 
main(int argc,char ** argv)56 int main(int argc, char** argv) {
57     CommandLineFlags::SetUsage("Converts skottie to a mp4");
58     CommandLineFlags::Parse(argc, argv);
59 
60     if (FLAGS_input.count() == 0) {
61         SkDebugf("-i input_file.json argument required\n");
62         return -1;
63     }
64 
65     if (FLAGS_motion_angle < 0 || FLAGS_motion_angle > 360) {
66         SkDebugf("--motion_angle must be [0...360]\n");
67         return -1;
68     }
69     if (FLAGS_motion_slope < -1 || FLAGS_motion_slope > 1) {
70         SkDebugf("--motion_slope must be [-1...1]\n");
71         return -1;
72     }
73     if (FLAGS_motion_samples < 1) {
74         SkDebugf("--motion_samples must be >= 1\n");
75         return -1;
76     }
77 
78     // map angle=180 to radius=1/4 (of a frame duration)
79     double motion_radius = FLAGS_motion_angle * 0.25 / 180.0;
80     if (FLAGS_motion_samples == 1) {
81         motion_radius = 0;  // no blur if we're only 1 sample
82     }
83 
84     SkString assetPath;
85     if (FLAGS_assetPath.count() > 0) {
86         assetPath.set(FLAGS_assetPath[0]);
87     } else {
88         assetPath = SkOSPath::Dirname(FLAGS_input[0]);
89     }
90     SkDebugf("assetPath %s\n", assetPath.c_str());
91 
92     auto animation = skottie::Animation::Builder()
93         .setResourceProvider(skottie_utils::FileResourceProvider::Make(assetPath))
94         .makeFromFile(FLAGS_input[0]);
95     if (!animation) {
96         SkDebugf("failed to load %s\n", FLAGS_input[0]);
97         return -1;
98     }
99 
100     SkISize dim = animation->size().toRound();
101     double duration = animation->duration();
102     int fps = FLAGS_fps;
103     if (fps < 1) {
104         fps = 1;
105     } else if (fps > 240) {
106         fps = 240;
107     }
108 
109     const int frames = SkScalarRoundToInt(duration * fps);
110     const double frame_duration = 1.0 / fps;
111 
112     if (FLAGS_verbose) {
113         SkDebugf("size %dx%d duration %g, fps %d, frame_duration %g\n",
114                  dim.width(), dim.height(), duration, fps, frame_duration);
115     }
116 
117     SkVideoEncoder encoder;
118 
119     sk_sp<SkSurface> surf, tmp_surf;
120     sk_sp<SkData> data;
121 
122     do {
123         double loop_start = SkTime::GetSecs();
124 
125         encoder.beginRecording(dim, fps);
126         // lazily allocate the surfaces
127         if (!surf) {
128             surf = SkSurface::MakeRaster(encoder.preferredInfo());
129             tmp_surf = surf->makeSurface(surf->width(), surf->height());
130         }
131 
132         for (int i = 0; i <= frames; ++i) {
133             double ts = i * 1.0 / fps;
134             if (FLAGS_verbose) {
135                 SkDebugf("rendering frame %d ts %g\n", i, ts);
136             }
137 
138             double normal_time = i * 1.0 / frames;
139             double frame_time = normal_time * duration;
140 
141             if (motion_radius > 0) {
142                 produce_frame(surf.get(), tmp_surf.get(), animation.get(), frame_time, frame_duration,
143                               motion_radius, FLAGS_motion_samples);
144             } else {
145                 produce_frame(surf.get(), animation.get(), frame_time);
146             }
147 
148             SkPixmap pm;
149             SkAssertResult(surf->peekPixels(&pm));
150             encoder.addFrame(pm);
151         }
152         data = encoder.endRecording();
153 
154         if (FLAGS_loop) {
155             double loop_dur = SkTime::GetSecs() - loop_start;
156             SkDebugf("recording secs %g, frames %d, recording fps %d\n",
157                      loop_dur, frames, (int)(frames / loop_dur));
158         }
159     } while (FLAGS_loop);
160 
161     if (FLAGS_output.count() == 0) {
162         SkDebugf("missing -o output_file.mp4 argument\n");
163         return 0;
164     }
165 
166     SkFILEWStream ostream(FLAGS_output[0]);
167     if (!ostream.isValid()) {
168         SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
169         return -1;
170     }
171     ostream.write(data->data(), data->size());
172     return 0;
173 }
174