mirror of
https://git.sr.ht/~cadence/bibliogram
synced 2024-11-22 08:07:30 +00:00
Add video support (experimental!)
This commit is contained in:
parent
95cc416e08
commit
a5ab771969
@ -1,5 +1,5 @@
|
||||
const constants = require("../constants")
|
||||
const {proxyImage, proxyExtendedOwner} = require("../utils/proxyurl")
|
||||
const {proxyImage, proxyVideo} = require("../utils/proxyurl")
|
||||
|
||||
class TimelineBaseMethods {
|
||||
constructor() {
|
||||
@ -21,10 +21,18 @@ class TimelineBaseMethods {
|
||||
}
|
||||
}
|
||||
|
||||
isVideo() {
|
||||
return this.data.__typename === "GraphVideo"
|
||||
}
|
||||
|
||||
getDisplayUrlP() {
|
||||
return proxyImage(this.data.display_url)
|
||||
}
|
||||
|
||||
getVideoUrlP() {
|
||||
return proxyVideo(this.data.video_url)
|
||||
}
|
||||
|
||||
getAlt() {
|
||||
return this.data.accessibility_caption || "No image description available."
|
||||
}
|
||||
|
@ -203,6 +203,12 @@ class TimelineEntry extends TimelineBaseMethods {
|
||||
}
|
||||
}
|
||||
|
||||
fetchVideoURL() {
|
||||
if (!this.isVideo()) return Promise.resolve(null)
|
||||
else if (this.data.video_url) return Promise.resolve(this.getVideoUrlP())
|
||||
else return this.update().then(() => this.getVideoUrlP())
|
||||
}
|
||||
|
||||
async fetchFeedData() {
|
||||
const children = await this.fetchChildren()
|
||||
return {
|
||||
|
@ -5,6 +5,12 @@ function proxyImage(url, width) {
|
||||
return "/imageproxy?"+params.toString()
|
||||
}
|
||||
|
||||
function proxyVideo(url) {
|
||||
const params = new URLSearchParams()
|
||||
params.set("url", url)
|
||||
return "/videoproxy?"+params.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../types").ExtendedOwner} owner
|
||||
*/
|
||||
@ -15,4 +21,5 @@ function proxyExtendedOwner(owner) {
|
||||
}
|
||||
|
||||
module.exports.proxyImage = proxyImage
|
||||
module.exports.proxyVideo = proxyVideo
|
||||
module.exports.proxyExtendedOwner = proxyExtendedOwner
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}}
|
||||
}
|
||||
]
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user