Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Puppeteerでつくる画像と動画 / images and videos made wit...
Search
mottox2
July 17, 2021
Technology
810
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.5k
もう一歩進めたい OG画像の動的生成
mottox2
7
3k
なぜコピペで使うコンポーネント集を利用するのか?
mottox2
8
7.6k
UIコンポーネントライブラリをうまく使うためにできること / components-with-designer
mottox2
7
4.1k
Figma Plugin公開までの壁を乗り越える
mottox2
3
4.2k
手触りのよいウェブを考える / better-mobile-web
mottox2
3
2.1k
組織と権限とSlack App / slack-app-with-roles
mottox2
1
740
SSRを避けるためにやっていること / ssr-alternative
mottox2
9
3.3k
JSXでつくる宣言的UIなプレゼンテーション / jsx-presentation
mottox2
7
34k
Other Decks in Technology
See All in Technology
Agent × Maestro で始める モバイル E2E 端末準備からテスト実行・結果確認まで
sansantech
PRO
0
150
Steerable Visual Representations
sansantech
PRO
0
310
AI時代のGraphQL
joere
3
380
How to teach your AI Agent with Agent skills
linedevth
0
120
Claude Code on Bedrock で統制をかける小ネタ集/claude-code-bedrock-governance-tips
emiki
1
4k
Gemini Intelligence 入門 ― AppFunctions と Gemini Nano
takuroabe
0
360
「エーアイ」をバズワードにしないために RPA 導入経験と Kiro 上での Claude モデル利用経験から
kazzpapa3
0
120
ガチで放置できるClaude Code実行環境の作り方.pdf
lamaglama39
5
9k
Temporal 最高!でも改めて「日付」について調べたら人類の負債すぎる件
riyaamemiya
0
230
ストレスなくAIと付き合いたい!こんなときどうしてる?
namiki64
4
680
freeeにおけるSoftware Factory
freee
PRO
0
390
React改造してみた
tsukina_7mochi
0
160
Featured
See All Featured
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
570
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
2
410
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Deep Space Network (abreviated)
tonyrice
0
380
Designing for humans not robots
tammielis
254
26k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.4k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.8k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
360
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.3k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.8k
The Invisible Side of Design
smashingmag
301
52k
We Have a Design System, Now What?
morganepeng
55
8.4k
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ͷ࿅্͕͕ͬͨɻ