This commit is contained in:
lowlighter
2021-03-23 23:29:07 +01:00
9 changed files with 430 additions and 136 deletions

View File

@@ -1,5 +1,6 @@
//Imports
import fs from "fs/promises"
import fss from "fs"
import os from "os"
import paths from "path"
import url from "url"
@@ -13,6 +14,8 @@
import opengraph from "open-graph-scraper"
import rss from "rss-parser"
import nodechartist from "node-chartist"
import GIFEncoder from "gifencoder"
import PNG from "png-js"
//Exports
export {fs, os, paths, url, util, processes, axios, git, opengraph, rss}
@@ -270,7 +273,7 @@
await new Promise(solve => setTimeout(solve, seconds*1000)) //eslint-disable-line no-promise-executor-return
}
/**Create gif from puppeteer browser */
/**Create record from puppeteer browser */
export async function record({page, width, height, frames, scale = 1, quality = 80, x = 0, y = 0, delay = 150}) {
//Register images frames
const images = []
@@ -284,4 +287,33 @@
//Post-processing
console.debug("metrics/record > applying post-processing")
return Promise.all(images.map(async buffer => (await jimp.read(buffer)).scale(scale).quality(quality).getBase64Async("image/png")))
}
/**Create gif from puppeteer browser*/
export async function gif({page, width, height, frames, x = 0, y = 0, repeat = true, delay = 150, quality = 10}) {
//Create temporary stream
const path = paths.join(os.tmpdir(), `${Math.round(Math.random()*1000000000)}.gif`)
console.debug(`metrics/puppeteergif > set write stream to "${path}"`)
if (fss.existsSync(path))
await fs.unlink(path)
//Create encoder
const encoder = new GIFEncoder(width, height)
encoder.createWriteStream().pipe(fss.createWriteStream(path))
encoder.start()
encoder.setRepeat(repeat ? 0 : -1)
encoder.setDelay(delay)
encoder.setQuality(quality)
//Register frames
for (let i = 0; i < frames; i++) {
const buffer = new PNG(await page.screenshot({clip:{width, height, x, y}}))
encoder.addFrame(await new Promise(solve => buffer.decode(pixels => solve(pixels)))) //eslint-disable-line no-promise-executor-return
if (frames%10 === 0)
console.debug(`metrics/puppeteergif > processed ${i}/${frames} frames`)
}
console.debug(`metrics/puppeteergif > processed ${frames}/${frames} frames`)
//Close encoder and convert to base64
encoder.finish()
const result = await fs.readFile(path, "base64")
await fs.unlink(path)
return `data:image/gif;base64,${result}`
}

View File

@@ -22,7 +22,8 @@ This uses puppeteer to generate collect image frames, and use CSS animations to
with:
# ... other options
plugin_skyline: yes
plugin_skyline_year: 0 # Set to 0 to display current year
plugin_skyline_frames: 60 # Use 60 frames (half-loop)
plugin_skyline_quality: 0.5 # Scale-down quality by half to reduce file-size (⚠️ higher quality increase file size)
plugin_skyline_year: 0 # Set to 0 to display current year
plugin_skyline_frames: 60 # Use 60 frames (half-loop)
plugin_skyline_quality: 0.5 # Set image quality
plugin_skyline_compatibility: yes # Support additional browsers (⚠️ increases file size and reduce optimization)
```

View File

@@ -7,7 +7,7 @@
return null
//Load inputs
let {year, frames, quality} = imports.metadata.plugins.skyline.inputs({data, account, q})
let {year, frames, quality, compatibility} = imports.metadata.plugins.skyline.inputs({data, account, q})
if (Number.isNaN(year)) {
year = new Date().getFullYear()
console.debug(`metrics/compute/${login}/plugins > skyline > year set to ${year}`)
@@ -32,13 +32,13 @@
//Generate gif
console.debug(`metrics/compute/${login}/plugins > skyline > generating frames`)
const framed = await imports.record({page, width, height, frames, scale:quality})
const animation = compatibility ? await imports.record({page, width, height, frames, scale:quality}) : await imports.gif({page, width, height, frames, quality:Math.max(1, quality*20)})
//Close puppeteer
await browser.close()
//Results
return {frames:framed}
return {animation, width, height, compatibility}
}
//Handle errors
catch (error) {

View File

@@ -29,10 +29,16 @@ inputs:
max: 120
# Image quality
# Note that it significantly increases output filesize (up to a few Mb) which can cause render/loading issues
plugin_skyline_quality:
description: Image quality
type: number
default: 0.5
min: 0.1
max: 1
max: 1
# This uses CSS animations instead of GIF to support a wider range of browser like FireFox and Safari
# Using this mode significantly increase file size as each frame is encoded separately
plugin_skyline_compatibility:
description: Compatibility mode
type: boolean
default: no

View File

@@ -7,6 +7,16 @@
modes:
- action
- name: Skyline plugin (compatibility)
uses: lowlighter/metrics@latest
with:
token: NOT_NEEDED
plugin_skyline: yes
plugin_skyline_compatibility: yes
timeout: 1800000
modes:
- action
- name: Skyline plugin (complete)
uses: lowlighter/metrics@latest
with:

View File

@@ -12,44 +12,52 @@
<%= plugins.skyline.error.message %>
</div>
<% } else { %>
<div class="skyline-animation">
<div class="frames">
<% for (const frame of plugins.skyline.frames) { %>
<div class="frame">
<img class="skyline" src="<%= frame %>" width="454" height="284" alt=""/>
</div>
<% } %>
<% if (plugins.skyline.compatibility) { %>
<div class="skyline-animation">
<div class="frames">
<% for (const frame of plugins.skyline.animation) { %>
<div class="frame">
<img class="skyline" src="<%= frame %>" height="<%= plugins.skyline.height %>" width="<%= plugins.skyline.width %>" alt=""/>
</div>
<% } %>
</div>
</div>
<% { const n = plugins.skyline.animation.length, width = plugins.skyline.width, height = plugins.skyline.height %>
<style>
@keyframes skyline-animation-frame {
100% { transform: translateX(-100%); }
}
.skyline-animation {
margin: 2px 13px 6px;
border-radius: 10px;
width: <%= width %>px;
height: <%= height %>px;
background-color: #030D21;
overflow: hidden;
}
.skyline-animation .frames {
animation: skyline-animation-frame <%= 150*n %>ms infinite;
animation-timing-function: steps(<%= n %>);
display: flex;
width: <%= n*width %>px;
height: <%= height %>px;
}
.skyline-animation .frames .frame {
display: block;
width: <%= width %>px;
flex-basis: <%= width %>px;
}
.skyline-animation .frames .frame img {
width: <%= width %>px;
}
</style>
<% } %>
<% } else { %>
<div class="skyline-animation">
<svg width="100%" height="<%= plugins.skyline.height %>" xmlns="http://www.w3.org/2000/svg">
<image href="<%= plugins.skyline.animation %>" height="<%= plugins.skyline.height %>" width="<%= plugins.skyline.width %>"/>
</svg>
</div>
</div>
<% { const n = plugins.skyline.frames.length, width = 454, height = 284 %>
<style>
@keyframes skyline-animation-frame {
100% { transform: translateX(-100%); }
}
.skyline-animation {
margin: 2px 13px 6px;
border-radius: 10px;
width: <%= width %>px;
height: <%= height %>px;
background-color: #030D21;
overflow: hidden;
}
.skyline-animation .frames {
animation: skyline-animation-frame <%= 150*n %>ms infinite;
animation-timing-function: steps(<%= n %>);
display: flex;
width: <%= n*width %>px;
height: <%= height %>px;
}
.skyline-animation .frames .frame {
display: block;
width: <%= width %>px;
flex-basis: <%= width %>px;
}
.skyline-animation .frames .frame img {
width: <%= width %>px;
}
</style>
<% } %>
<% } %>
</section>

View File

@@ -911,6 +911,14 @@
color: #666666;
}
/* Skyline */
.skyline-animation {
margin: 2px 13px 6px;
border-radius: 10px;
background-color: #030D21;
overflow: hidden;
}
/* Charts */
.ct-line {
stroke-width: 2px !important;