Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Puppeteerでつくる画像と動画 / images and videos made wit...
Search
mottox2
July 17, 2021
Technology
780
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Puppeteerでつくる画像と動画 / images and videos made with puppeteer
mottox2
July 17, 2021
More Decks by mottox2
See All by mottox2
つくり方を変えていく | change-how-we-build
mottox2
2
1.4k
もう一歩進めたい OG画像の動的生成
mottox2
7
2.7k
なぜコピペで使うコンポーネント集を利用するのか?
mottox2
8
7.6k
UIコンポーネントライブラリをうまく使うためにできること / components-with-designer
mottox2
7
4k
Figma Plugin公開までの壁を乗り越える
mottox2
3
4.1k
手触りのよいウェブを考える / better-mobile-web
mottox2
3
2k
組織と権限とSlack App / slack-app-with-roles
mottox2
1
730
SSRを避けるためにやっていること / ssr-alternative
mottox2
9
3.3k
JSXでつくる宣言的UIなプレゼンテーション / jsx-presentation
mottox2
7
34k
Other Decks in Technology
See All in Technology
「ちゃんとやっている」は独りよがりだった ― 不安に寄り添うインシデント対応へ / Towards incident response that addresses anxieties
chmikata
1
5k
Genie Ontologyは銀の弾丸かを考える / Is Genie Ontology a Silver Bullet?
nttcom
0
240
公式ドキュメントの歩き方etc
coco_se
0
100
ループエンジニアリングでE2Eテストを実践
noriyukitakei
0
350
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
15
110k
最近評価が難しくなった
maroon8021
0
340
AI Agent SaaS を支える自社仮想化基盤への挑戦と実運用 / ai-agent-saas-virtualization
flatt_security
2
3.8k
実装だけじゃない! CCA-F取得エンジニアが教えるClaude Code開発プロセス活用術
diggymo
2
650
環境凍結という Toil を倒す -セルフサービス型 Ephemeral テスト環境の 設計と実践
shirouz
1
2.3k
GuardrailからGovernanceへ~AIエージェント運用の次の課題~
sbspsy
2
270
人を動かすのは時間ではなく、納得感 〜新任EMが入社3ヶ月、組織を2回変えた話〜
kakehashi
PRO
3
220
Foxgloveについて 実際にExtensionを開発して公開するまでの話 / About Foxglove: The Story of Developing and Releasing an Extension
ry0_ka
0
210
Featured
See All Featured
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.6k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
650
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
How to make the Groovebox
asonas
2
2.3k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
490
Making Projects Easy
brettharned
120
6.7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.6k
Transcript
None
ࣗݾհ • ͬͱʢ@mottox2ʣ • KODANSHAtech LLC.͋ • ि4ۈͷσβΠϯΤϯδχΞ • ڵຯ:
σβΠϯγεςϜ • झຯ: Apex Legends
ࠓ͢͜ͱ • PuppeteerΛͬͨը૾࡞ํ๏ • PuppeteerΛͬͨಈը࡞ํ๏ • զʑԿ͕Ͱ͖Δͷ͔
λΠϜϥΠϯͷ͋Ε Ͳ͏ͭ͘Γ·͔͢ʁ • GitHub • Qiita • Zenn • ࣭ശ
Webٕज़Ͱը૾࡞ • Canvasͷը૾Խ • ΫϥΠΞϯτʹґଘ͢ΔɾCanvasࣗମ͕ѻ͍ʹ͍͘ • ը૾৴SaaSͷར༻ • ָ͚ͩͲࣗ༝͕͍ •
✅ Puppeteerͷར༻ • αʔόʔαΠυ͕ඞਢ͕ͩࣗ༝͕ߴ͍ɻ
Puppeteerͱ • ChromeΛૢ࡞͢ΔAPIΛఏڙ͢ΔϥΠϒϥϦ • SPAͷΫϩʔϦϯάɺUIςετϑΥʔϜࣗಈૹ৴ͳͲͷ༻్͕ఆ ͞Ε͍ͯΔ • εΫϦʔϯγϣοτࡱΕΔ
Puppeteerͷίʔυྫ Const puppeteer = require(‘puppeteer’) const getScreenshot = async (html)
=> { const browser = await puppeteer.launch() const page = await browser.newPage(); await page.setContent(html); await page.setViewport({ width: 1200, height: 630 }); const file = await page.screenshot(); // await page.screenshot({ path: ‘dest/screenshot.png’ }); return file } const html = generateHTML(props) getScreenshot(html)
Puppeteerͷίʔυྫ const generateHTML = (props) => { return `<html> <head>
<style>…</style> </head> <body> <!— contents —> </body> </html>` }
Puppeteerͱ • ϒϥβ্Ͱͷ༷ʑͳදݱΛͬͨը૾͕࡞ΕΔɻ • ࣄྫΛհ͠·͢ɻ
ࠓճ࡞ͬͨσϞͷհ • ಉਓࢽଈചձͷެࣜαΠτ • αʔΫϧओ͕൦Λొ͢Δ • ൦ͷOGը૾Λ͖Ε͍ʹݟ͍ͤͨ • Cloud Functions্ͰPuppeteerͷεΫϦʔϯγϣοτΛ࡞ɻϨεϙ
ϯεͱͯ͠ฦ͍ͯ͠Δɻ
࠷ॳʹ࡞ͬͨͷ
🤔 ը૾ͱͯ͠ݟΔͱɺதࠇ͕ؾʹͳΔɻ
จࣈ٧ΊΛվળ͢Δ • CSSͷfont-feature-settingsϓϩύςΟΛར༻͢Δ
👍 క·ͬͨҹʹͳͬͨ
🤔 վߦҐஔ͕ؾʹͳΔɻ୯ޠͷ్தͰվߦͨ͘͠ͳ͍ɻ
վߦҐஔΛվળ͢Δ • ܗଶૉղੳΛͬͯɺจࣈྻΛվߦ͍͍ͯ͠ҐஔͰׂ͢Δ
վߦҐஔΛվળ͢Δ • Kuromojin.js※Λͬͯܗଶૉղੳɺ͍͍ײ͡ʹׂ͓ͯ͘͠ • ׂͨ͠จࣈΛࢺจࣈछྨʹ߹Θͤͯ݁߹͢Δ ※ Kuromoji.jsͷϥούʔ
վߦҐஔΛվળ͢Δ • spanͰ۠ͬͯno-wrapΕվߦҐஔͷίϯτϩʔϧ͕Մೳ • ͳΜ͔มͳέʔε͋Δ͚Ͳɺ͓͓ΉͶΑͦ͞͏ • ✅ σϞ: εΫγϣ൛ͱHTML൛ͷൺֱ
💡 վળ͞Εͨ
ରԠલޙͷൺֱ • Before/After
ରԠલޙͷൺֱ • Before/After
͜͜·Ͱͷ·ͱΊ • ׳Ε͠ΜͩϒϥβͰදݱͰ͖ΔͷΛͬͯը૾͕࡞ΕΔɻ • JavaScript͑Ͱ͖Δ͜ͱ͕૿͑Δɻ • ྫʹڍ͛ͨܗଶૉղੳɺυϛφϯτΧϥʔͷநग़ͳͲ • ʢͨͩ͠ɺPhotoshopͰ࡞ͬͨํָ͕ʣ
ಈըʹ֦ு͢Δ
ಈըΛͭ͘Δ • ਅͬઌʹࢥ͍ͭ͘ͷ͕ɺϒϥβͷಈ࡞Λը͢Δख๏ɻ • ՄೳͰ͋Δ͕҆ఆͨ͠FPSΛҡ࣋Ͱ͖ͳ͍ɻ • ըײ͕ڧ͘ɺಈըͱ͍͍ʹ͍͘ɻ
ಈըΛͭ͘Δ • ָͳͷͰɺFFmpegΛ͍·͢ɻ • σίʔυɺΤϯίʔυɺτϥϯείʔυɺϛοΫεɺσϛοΫεɺ ετϦʔϜɺϑΟϧλϦϯάͳͲΛ݉Ͷඋ͑ͨπʔϧ • CLI͔ͬ͠Γ༻ҙ͞Ε͍ͯΔɻ • ࠓճѻ͍·ͤΜ͕wasm͋Γ·͢ɻ
ಈըΛͭ͘Δ • ֤ϑϨʔϜͷը૾Λ༻ҙͯ͠ԼهίϚϯυΛ࣮ߦ͢Δɻ • ffmpeg -i Frame_%d.png -vcodec libx264 -pix_fmt
yuv420p -y out.mp4
ಈըΛͭ͘Δ // child_processͰ࣮ߦ͢Δɻ͜ΕͰNode.js͔Βಈը࡞ΕΔɻ const { execSync } = require('child_process') ;
execSync('ffmpeg -i Frame_%d.png -vcodec libx264 -pix_fmt yuv420p -y out.mp4')
֤ϑϨʔϜͷը૾Λ࡞Δ • ݱࡏͷframeΛҾͱͯͨ͠͠ΒɺͦͷframeͷεΫγϣ͕ؼͬͯ͘ ΔΠϝʔδ • 30fpsͷಈըͰ͋Ε 1frame = 0s, 2frame
= 1/30sͱରԠ͢Δɻ
֤ϑϨʔϜͷը૾Λ࡞Δ const puppeteer = require("puppeteer-core" ) const { getHtml }
= require("./template") ; (async () => { const props = {} ; const browser = await puppeteer.launch( { executablePath : "/Applications/Google Chrome.app/Contents/ MacOS/Google Chrome" , }) ; const page = await browser.newPage() ; for (let i = 1; i <= 60; i++) { const path = `./tmp/frame_${i}.png` ; const html = getHtml({ ...props, frame }) ; await page.setContent(html) ; await page.setViewport({ width: 1200, height: 630 }) ; await page.screenshot( { path , }) ; } await browser.close() ; })();
ಈըԽ͢Δ • ֤ϑϨʔϜͷը૾ΛFFmpegʹ৯ΘͤͨΒɻ
མͱ݀͠ • Frame͝ͱͷHTMLΛར༻͢ΔͨΊɺCSSΞχϝʔγϣϯ͕͑ͳ͍ • LinearҎ֎ͷΞχϝʔγϣϯʹͻͱखؒඞཁ
OSSΛར༻͢Δ • ࣗલͰશ෦༻ҙ͢Δͷے͕ѱ͍ͷͰOSSΛ͏ɻ • RemotionΛ͏ͱΑ͍ɻ
Remotion
Remotion • ReactΛϨϯμϦϯάͨ݁͠ՌΛPuppeteerͰੜɺFFmpegͰ݁߹͠ ͨͷΛు͖ग़ͯ͘͠ΕΔ • طଘͷಈըซ༻Մೳ • Webٕज़ͱطଘٕज़ʢAfter EffectsʣͷϋΠϒϦου͕Մೳɻ •
ݸਓখنͳ৫ͳͲͰ͋ΕແྉͰར༻Մೳɻ • ৄ͘͠ϥΠηϯεΛ֬ೝ͢Δ͜ͱɻ
Remotionͷίʔυྫ import { useCurrentFrame } from "remotion" ; export const
MyVideo = () => { // ݱࡏͷϑϨʔϜΛऔಘ const frame = useCurrentFrame(); return ( <div style={{ flex: 1, justifyContent: "center", alignItems: "center" }} > The current frame is {frame} . </div > ) ; } ; export const RemotionVideo: React.FC = () => ( < > <Compositio n id="MyVideo " component={MyVideo } durationInFrames={150 } fps={30 } width={1920 } height={1080 } / > </ > )
զʑʹԿ͕Ͱ͖Δͷ͔
PuppeteerΛར༻͢ΔϝϦοτ • ෳͷٕज़ΛΈ߹Θͤͯ࡞ΕΔ • CanvasWebGLΛ෦తʹऔΓೖΕΒΕΔɻ • ୯ಠͰΓ͖Δʹݫ͍͠දݱͰ࡞Γ͍͢ɻ • ͢ͰʹWebʹ͋Δࢿ࢈ΛͬͨͷΛը૾ԽɾಈըԽ͢Δ͜ͱͰϝ Ϧοτ͕ੜ·ΕΔɻ
• σʔλϏδϡΞϥΠθʔγϣϯͷ·ͱΊͱ͔ɻ
࣮ੈքͰ͏ʹϋʔυϧ͕ • 🤔 ࣮ɺಈը࡞ͷSaaSͷํ͕खܰͰߴ࣭ͳͷΛ࡞Γ͍͢ • AfterEffectsΛ͏ํ͕ϕλʔͳ͜ͱ͕ଟ͍ɻ • ࡞ର࣍ୈͰϝϦοτʹͳΔ෦ੜ·Εͯ͘Δͱࢥ͏ɻ • ձࣾͰ͍ॴΛ͍ͬͯΔ
• ಈը੍࡞Λษڧͯͨ͠ΒɺAfterEffectsͷ࿅্͕͕ͬͨɻ