xref: /aosp_15_r20/external/skia/tools/skottie2movie.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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