1
0
mirror of https://git.sr.ht/~cadence/bibliogram synced 2025-12-16 03:08:47 +00:00

Add video support (experimental!)

This commit is contained in:
Cadence Fish
2020-01-30 04:20:20 +13:00
parent 95cc416e08
commit a5ab771969
7 changed files with 96 additions and 43 deletions

View File

@@ -3,47 +3,69 @@ const {request} = require("../../lib/utils/request")
const {proxy} = require("pinski/plugins")
const sharp = require("sharp")
const VERIFY_SUCCESS = Symbol("VERIFY_SUCCESS")
/**
* Check that a resource is on Instagram.
* @param {URL} completeURL
*/
function verifyURL(completeURL) {
const params = completeURL.searchParams
if (!params.get("url")) return {status: "fail", value: [400, "Must supply `url` query parameter"]}
try {
var url = new URL(params.get("url"))
} catch (e) {
return {status: "fail", value: [400, "`url` query parameter is not a valid URL"]}
}
// check url protocol
if (url.protocol !== "https:") return {status: "fail", value: [400, "URL protocol must be `https:`"]}
// check url host
if (!["fbcdn.net", "cdninstagram.com"].some(host => url.host.endsWith(host))) return {status: "fail", value: [400, "URL host is not allowed"]}
return {status: "ok", url}
}
module.exports = [
{route: "/imageproxy", methods: ["GET"], code: async (input) => {
/** @type {URL} */
// check url param exists
const completeURL = input.url
const params = completeURL.searchParams
if (!params.get("url")) return [400, "Must supply `url` query parameter"]
try {
var url = new URL(params.get("url"))
} catch (e) {
return [400, "`url` query parameter is not a valid URL"]
{
route: "/imageproxy", methods: ["GET"], code: async (input) => {
const verifyResult = verifyURL(input.url)
if (verifyResult.status !== "ok") return verifyResult.value
if (!["png", "jpg"].some(ext => verifyResult.url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
const params = input.url.searchParams
const width = +params.get("width")
if (typeof width === "number" && !isNaN(width) && width > 0) {
/*
This uses sharp to force crop the image to a square.
"entropy" seems to currently work better than "attention" on the thumbnail of this shortcode: B55yH20gSl0
Some thumbnails aren't square and would otherwise be stretched on the page without this.
If I cropped the images client side, it would have to be done with CSS background-image, which means no <img srcset>.
*/
return request(verifyResult.url).then(res => {
const converter = sharp().resize(width, width, {position: "entropy"})
return {
statusCode: 200,
contentType: "image/jpeg",
headers: {
"Cache-Control": constants.caching.image_cache_control
},
stream: res.body.pipe(converter)
}
})
} else {
// No specific size was requested, so just stream proxy the file directly.
return proxy(verifyResult.url, {
"Cache-Control": constants.caching.image_cache_control
})
}
}
// check url protocol
if (url.protocol !== "https:") return [400, "URL protocol must be `https:`"]
// check url host
if (!["fbcdn.net", "cdninstagram.com"].some(host => url.host.endsWith(host))) return [400, "URL host is not allowed"]
if (!["png", "jpg"].some(ext => url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
const width = +params.get("width")
if (typeof width === "number" && !isNaN(width) && width > 0) {
/*
This uses sharp to force crop the image to a square.
"entropy" seems to currently work better than "attention" on the thumbnail of this shortcode: B55yH20gSl0
Some thumbnails aren't square and would otherwise be stretched on the page without this.
If I cropped the images client side, it would have to be done with CSS background-image, which means no <img srcset>.
*/
return request(url).then(res => {
const converter = sharp().resize(width, width, {position: "entropy"})
return {
statusCode: 200,
contentType: "image/jpeg",
headers: {
"Cache-Control": constants.caching.image_cache_control
},
stream: res.body.pipe(converter)
}
})
} else {
// No specific size was requested, so just stream proxy the file directly.
},
{
route: "/videoproxy", methods: ["GET"], code: async (input) => {
const verifyResult = verifyURL(input.url)
if (verifyResult.status !== "ok") return verifyResult.value
const url = verifyResult.url
if (!["mp4"].some(ext => url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
return proxy(url, {
"Cache-Control": constants.caching.image_cache_control
})
}
}}
}
]

View File

@@ -92,6 +92,7 @@ module.exports = [
return getOrFetchShortcode(fill[0]).then(async post => {
await post.fetchChildren()
await post.fetchExtendedOwnerP() // parallel await is okay since intermediate fetch result is cached
if (post.isVideo()) await post.fetchVideoURL()
return render(200, "pug/post.pug", {post})
}).catch(error => {
if (error === constants.symbols.NOT_FOUND) {

View File

@@ -22,5 +22,8 @@ html
if post.getCaption()
p.description= post.getCaption()
section.images-gallery
for image in post.children
img(src=image.getDisplayUrlP() alt=image.getAlt() width=image.data.dimensions.width height=image.data.dimensions.height).sized-image
for entry in post.children
if entry.isVideo()
video(src=entry.getVideoUrlP() controls preload="auto" width=entry.data.dimensions.width height=entry.data.dimensions.height).sized-video
else
img(src=entry.getDisplayUrlP() alt=entry.getAlt() width=entry.data.dimensions.width height=entry.data.dimensions.height).sized-image

View File

@@ -267,17 +267,23 @@ body
@media screen and (max-width: $layout-a-max)
flex: 1
.sized-image
.sized-image, .sized-video
color: #eee
background-color: #3b3c3d
max-height: 94vh
max-width: 100%
width: auto
height: auto
&:not(:last-child)
margin-bottom: 10px
.sized-image
width: auto
height: auto
.sized-video
width: 100%
height: 100%
.error-page
box-sizing: border-box
min-height: 100vh