mirror of
https://git.sr.ht/~cadence/bibliogram
synced 2024-11-22 16:17:29 +00:00
Add video support (experimental!)
This commit is contained in:
parent
95cc416e08
commit
a5ab771969
@ -1,5 +1,5 @@
|
|||||||
const constants = require("../constants")
|
const constants = require("../constants")
|
||||||
const {proxyImage, proxyExtendedOwner} = require("../utils/proxyurl")
|
const {proxyImage, proxyVideo} = require("../utils/proxyurl")
|
||||||
|
|
||||||
class TimelineBaseMethods {
|
class TimelineBaseMethods {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -21,10 +21,18 @@ class TimelineBaseMethods {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isVideo() {
|
||||||
|
return this.data.__typename === "GraphVideo"
|
||||||
|
}
|
||||||
|
|
||||||
getDisplayUrlP() {
|
getDisplayUrlP() {
|
||||||
return proxyImage(this.data.display_url)
|
return proxyImage(this.data.display_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVideoUrlP() {
|
||||||
|
return proxyVideo(this.data.video_url)
|
||||||
|
}
|
||||||
|
|
||||||
getAlt() {
|
getAlt() {
|
||||||
return this.data.accessibility_caption || "No image description available."
|
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() {
|
async fetchFeedData() {
|
||||||
const children = await this.fetchChildren()
|
const children = await this.fetchChildren()
|
||||||
return {
|
return {
|
||||||
|
@ -5,6 +5,12 @@ function proxyImage(url, width) {
|
|||||||
return "/imageproxy?"+params.toString()
|
return "/imageproxy?"+params.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function proxyVideo(url) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set("url", url)
|
||||||
|
return "/videoproxy?"+params.toString()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../types").ExtendedOwner} owner
|
* @param {import("../types").ExtendedOwner} owner
|
||||||
*/
|
*/
|
||||||
@ -15,4 +21,5 @@ function proxyExtendedOwner(owner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports.proxyImage = proxyImage
|
module.exports.proxyImage = proxyImage
|
||||||
|
module.exports.proxyVideo = proxyVideo
|
||||||
module.exports.proxyExtendedOwner = proxyExtendedOwner
|
module.exports.proxyExtendedOwner = proxyExtendedOwner
|
||||||
|
@ -3,47 +3,69 @@ const {request} = require("../../lib/utils/request")
|
|||||||
const {proxy} = require("pinski/plugins")
|
const {proxy} = require("pinski/plugins")
|
||||||
const sharp = require("sharp")
|
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 = [
|
module.exports = [
|
||||||
{route: "/imageproxy", methods: ["GET"], code: async (input) => {
|
{
|
||||||
/** @type {URL} */
|
route: "/imageproxy", methods: ["GET"], code: async (input) => {
|
||||||
// check url param exists
|
const verifyResult = verifyURL(input.url)
|
||||||
const completeURL = input.url
|
if (verifyResult.status !== "ok") return verifyResult.value
|
||||||
const params = completeURL.searchParams
|
if (!["png", "jpg"].some(ext => verifyResult.url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
|
||||||
if (!params.get("url")) return [400, "Must supply `url` query parameter"]
|
const params = input.url.searchParams
|
||||||
try {
|
const width = +params.get("width")
|
||||||
var url = new URL(params.get("url"))
|
if (typeof width === "number" && !isNaN(width) && width > 0) {
|
||||||
} catch (e) {
|
/*
|
||||||
return [400, "`url` query parameter is not a valid URL"]
|
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
|
route: "/videoproxy", methods: ["GET"], code: async (input) => {
|
||||||
if (!["fbcdn.net", "cdninstagram.com"].some(host => url.host.endsWith(host))) return [400, "URL host is not allowed"]
|
const verifyResult = verifyURL(input.url)
|
||||||
if (!["png", "jpg"].some(ext => url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
|
if (verifyResult.status !== "ok") return verifyResult.value
|
||||||
const width = +params.get("width")
|
const url = verifyResult.url
|
||||||
if (typeof width === "number" && !isNaN(width) && width > 0) {
|
if (!["mp4"].some(ext => url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
|
||||||
/*
|
|
||||||
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.
|
|
||||||
return proxy(url, {
|
return proxy(url, {
|
||||||
"Cache-Control": constants.caching.image_cache_control
|
"Cache-Control": constants.caching.image_cache_control
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
]
|
]
|
||||||
|
@ -92,6 +92,7 @@ module.exports = [
|
|||||||
return getOrFetchShortcode(fill[0]).then(async post => {
|
return getOrFetchShortcode(fill[0]).then(async post => {
|
||||||
await post.fetchChildren()
|
await post.fetchChildren()
|
||||||
await post.fetchExtendedOwnerP() // parallel await is okay since intermediate fetch result is cached
|
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})
|
return render(200, "pug/post.pug", {post})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error === constants.symbols.NOT_FOUND) {
|
if (error === constants.symbols.NOT_FOUND) {
|
||||||
|
@ -22,5 +22,8 @@ html
|
|||||||
if post.getCaption()
|
if post.getCaption()
|
||||||
p.description= post.getCaption()
|
p.description= post.getCaption()
|
||||||
section.images-gallery
|
section.images-gallery
|
||||||
for image in post.children
|
for entry in post.children
|
||||||
img(src=image.getDisplayUrlP() alt=image.getAlt() width=image.data.dimensions.width height=image.data.dimensions.height).sized-image
|
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)
|
@media screen and (max-width: $layout-a-max)
|
||||||
flex: 1
|
flex: 1
|
||||||
|
|
||||||
.sized-image
|
.sized-image, .sized-video
|
||||||
color: #eee
|
color: #eee
|
||||||
background-color: #3b3c3d
|
background-color: #3b3c3d
|
||||||
max-height: 94vh
|
max-height: 94vh
|
||||||
max-width: 100%
|
max-width: 100%
|
||||||
width: auto
|
|
||||||
height: auto
|
|
||||||
|
|
||||||
&:not(:last-child)
|
&:not(:last-child)
|
||||||
margin-bottom: 10px
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.sized-image
|
||||||
|
width: auto
|
||||||
|
height: auto
|
||||||
|
|
||||||
|
.sized-video
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
|
||||||
.error-page
|
.error-page
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
min-height: 100vh
|
min-height: 100vh
|
||||||
|
Loading…
Reference in New Issue
Block a user