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/SkGraphics.h"
11 #include "include/core/SkStream.h"
12 #include "include/core/SkSurface.h"
13 #include "include/private/base/SkTPin.h"
14 #include "modules/skottie/include/Skottie.h"
15 #include "modules/skresources/include/SkResources.h"
16 #include "src/base/SkTime.h"
17 #include "src/utils/SkOSPath.h"
18
19 #include "tools/CodecUtils.h"
20 #include "tools/flags/CommandLineFlags.h"
21 #include "tools/gpu/GrContextFactory.h"
22
23 #include "include/gpu/ganesh/GrContextOptions.h"
24 #include "include/gpu/ganesh/GrTypes.h"
25 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
26 #include "modules/skshaper/utils/FactoryHelpers.h"
27
28 #if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
29 #include "include/ports/SkFontMgr_mac_ct.h"
30 #elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
31 #include "include/ports/SkFontMgr_fontconfig.h"
32 #include "include/ports/SkFontScanner_FreeType.h"
33 #else
34 #include "include/ports/SkFontMgr_empty.h"
35 #endif
36
37 static DEFINE_string2(input, i, "", "skottie animation to render");
38 static DEFINE_string2(output, o, "", "mp4 file to create");
39 static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
40 static DEFINE_int_2(fps, f, 25, "fps");
41 static DEFINE_bool2(verbose, v, false, "verbose mode");
42 static DEFINE_bool2(loop, l, false, "loop mode for profiling");
43 static DEFINE_int(set_dst_width, 0, "set destination width (height will be computed)");
44 static DEFINE_bool2(gpu, g, false, "use GPU for rendering");
45
produce_frame(SkSurface * surf,skottie::Animation * anim,double frame)46 static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame) {
47 anim->seekFrame(frame);
48 surf->getCanvas()->clear(SK_ColorWHITE);
49 anim->render(surf->getCanvas());
50 }
51
52 struct AsyncRec {
53 SkImageInfo info;
54 SkVideoEncoder* encoder;
55 };
56
main(int argc,char ** argv)57 int main(int argc, char** argv) {
58 SkGraphics::Init();
59
60 CommandLineFlags::SetUsage("Converts skottie to a mp4");
61 CommandLineFlags::Parse(argc, argv);
62
63 if (FLAGS_input.size() == 0) {
64 SkDebugf("-i input_file.json argument required\n");
65 return -1;
66 }
67
68 auto contextType = skgpu::ContextType::kGL;
69 GrContextOptions grCtxOptions;
70 sk_gpu_test::GrContextFactory factory(grCtxOptions);
71
72 SkString assetPath;
73 if (FLAGS_assetPath.size() > 0) {
74 assetPath.set(FLAGS_assetPath[0]);
75 } else {
76 assetPath = SkOSPath::Dirname(FLAGS_input[0]);
77 }
78 SkDebugf("assetPath %s\n", assetPath.c_str());
79
80 CodecUtils::RegisterAllAvailable();
81
82 // If necessary, clients should use a font manager that would load fonts from the system.
83 #if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
84 sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_CoreText(nullptr);
85 #elif defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE) && defined(SK_TYPEFACE_FACTORY_FREETYPE)
86 sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_Android(nullptr, SkFontScanner_Make_FreeType());
87 #elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE) && defined(SK_TYPEFACE_FACTORY_FREETYPE)
88 sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_FontConfig(nullptr, SkFontScanner_Make_FreeType());
89 #else
90 sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_Custom_Empty();
91 #endif
92
93 auto animation = skottie::Animation::Builder()
94 .setResourceProvider(skresources::FileResourceProvider::Make(assetPath))
95 .setTextShapingFactory(SkShapers::BestAvailable())
96 .setFontManager(fontMgr)
97 .makeFromFile(FLAGS_input[0]);
98 if (!animation) {
99 SkDebugf("failed to load %s\n", FLAGS_input[0]);
100 return -1;
101 }
102
103 SkISize dim = animation->size().toRound();
104 double duration = animation->duration();
105 int fps = SkTPin(FLAGS_fps, 1, 240);
106 double fps_scale = animation->fps() / fps;
107
108 float scale = 1;
109 if (FLAGS_set_dst_width > 0) {
110 scale = FLAGS_set_dst_width / (float)dim.width();
111 dim = { FLAGS_set_dst_width, SkScalarRoundToInt(scale * dim.height()) };
112 }
113
114 const int frames = SkScalarRoundToInt(duration * fps);
115 const double frame_duration = 1.0 / fps;
116
117 if (FLAGS_verbose) {
118 SkDebugf("Size %dx%d duration %g, fps %d, frame_duration %g\n",
119 dim.width(), dim.height(), duration, fps, frame_duration);
120 }
121
122 SkVideoEncoder encoder;
123
124 GrDirectContext* grctx = nullptr;
125 sk_sp<SkSurface> surf;
126 sk_sp<SkData> data;
127
128 const auto info = SkImageInfo::MakeN32Premul(dim);
129 do {
130 double loop_start = SkTime::GetSecs();
131
132 if (!encoder.beginRecording(dim, fps)) {
133 SkDEBUGF("Invalid video stream configuration.\n");
134 return -1;
135 }
136
137 // lazily allocate the surfaces
138 if (!surf) {
139 if (FLAGS_gpu) {
140 grctx = factory.getContextInfo(contextType).directContext();
141 surf = SkSurfaces::RenderTarget(grctx,
142 skgpu::Budgeted::kNo,
143 info,
144 0,
145 GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
146 nullptr);
147 if (!surf) {
148 grctx = nullptr;
149 }
150 }
151 if (!surf) {
152 surf = SkSurfaces::Raster(info);
153 }
154 surf->getCanvas()->scale(scale, scale);
155 }
156
157 for (int i = 0; i <= frames; ++i) {
158 const double frame = i * fps_scale;
159 if (FLAGS_verbose) {
160 SkDebugf("rendering frame %g\n", frame);
161 }
162
163 produce_frame(surf.get(), animation.get(), frame);
164
165 AsyncRec asyncRec = { info, &encoder };
166 if (grctx) {
167 auto read_pixels_cb = [](SkSurface::ReadPixelsContext ctx,
168 std::unique_ptr<const SkSurface::AsyncReadResult> result) {
169 if (result && result->count() == 1) {
170 AsyncRec* rec = reinterpret_cast<AsyncRec*>(ctx);
171 rec->encoder->addFrame({rec->info, result->data(0), result->rowBytes(0)});
172 }
173 };
174 surf->asyncRescaleAndReadPixels(info, {0, 0, info.width(), info.height()},
175 SkSurface::RescaleGamma::kSrc,
176 SkImage::RescaleMode::kNearest,
177 read_pixels_cb, &asyncRec);
178 grctx->submit();
179 } else {
180 SkPixmap pm;
181 SkAssertResult(surf->peekPixels(&pm));
182 encoder.addFrame(pm);
183 }
184 }
185
186 if (grctx) {
187 // ensure all pending reads are completed
188 grctx->flushAndSubmit(GrSyncCpu::kYes);
189 }
190 data = encoder.endRecording();
191
192 if (FLAGS_loop) {
193 double loop_dur = SkTime::GetSecs() - loop_start;
194 SkDebugf("recording secs %g, frames %d, recording fps %d\n",
195 loop_dur, frames, (int)(frames / loop_dur));
196 }
197 } while (FLAGS_loop);
198
199 if (FLAGS_output.size() == 0) {
200 SkDebugf("missing -o output_file.mp4 argument\n");
201 return 0;
202 }
203
204 SkFILEWStream ostream(FLAGS_output[0]);
205 if (!ostream.isValid()) {
206 SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
207 return -1;
208 }
209 ostream.write(data->data(), data->size());
210 return 0;
211 }
212