Merge branch 'master' of https://github.com/lowlighter/metrics
This commit is contained in:
@@ -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}`
|
||||
}
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user