fix imagesheet
This commit is contained in:
parent
9f605ef426
commit
0f5488d65a
17
README.md
17
README.md
@ -42,7 +42,16 @@ JSON object:
|
||||
- `channel`: (object)
|
||||
- `name`: (string) i.e. `"NyanCat"`
|
||||
- `id`: (string) i.e. `"UCsW85RAS2_Twg_lEPyv7G8A"`
|
||||
- `icon_index`?: (string) The index of the channel icon in the image sheet. because it is deduplicated, it is not one-to-one
|
||||
- `icon`?: (object)
|
||||
- `x`: (integer) px from left
|
||||
- `y`: (integer) px from top
|
||||
- `width`: (integer)
|
||||
- `height`: (integer)
|
||||
- `thumbnail`?: (object)
|
||||
- `x`: (integer) px from left
|
||||
- `y`: (integer) px from top
|
||||
- `width`: (integer)
|
||||
- `height`: (integer)
|
||||
- `imagesheet_vrcurl`?: (integer) index of the vrcurl for the collage of thumbnails and/or icons
|
||||
- `nextpage_vrcurl`: (integer) index of the vrcurl that will serve the JSON for the next page of results
|
||||
|
||||
@ -52,7 +61,7 @@ JSON object:
|
||||
- `{pool}`: must be same as pool param in search endpoint.
|
||||
- `{index}`: vrcurl index number
|
||||
|
||||
Response may be 302 redirect to youtube url, `image/jpeg` for imagesheet or `application/json` for next page
|
||||
Response may be 302 redirect to youtube url, `image/png` for imagesheet or `application/json` for next page
|
||||
|
||||
# VRCUrls
|
||||
|
||||
@ -77,6 +86,4 @@ All resources (youtube urls etc) referenced in the search results will be substi
|
||||
|
||||
Video thumbnails and channel icons are collated together into one image and served at a VRCUrl to be loaded by VRCImageDownloader.
|
||||
|
||||
Thumbnails are 360x202, arranged vertically in the same order as the JSON results.
|
||||
|
||||
Channel icons are 68x68 arranged vertically on the right of thumbnails.
|
||||
Use the x, y, width and height values from the json to crop the image from the sheet.
|
@ -1,6 +1,6 @@
|
||||
import { searchYouTubeVideos, continueYouTubeVideoSearch } from "./simpleYoutubeSearch.js";
|
||||
import { putVrcUrl } from "./vrcurl.js";
|
||||
import { makeImageSheetVrcUrl } from "./imagesheet.js";
|
||||
import { makeImageSheetVrcUrl, thumbnailWidth, thumbnailHeight, iconWidth, iconHeight } from "./imagesheet.js";
|
||||
|
||||
var cache = {};
|
||||
|
||||
@ -27,7 +27,7 @@ async function VRCYoutubeSearch(pool, query, options = {}) {
|
||||
var {videos, continuationData} = typeof query == "object" ? await continueYouTubeVideoSearch(query) : await searchYouTubeVideos(query);
|
||||
|
||||
if (options.thumbnails) {
|
||||
var thumbnailUrls = videos.map(video => video.thumbnails.find(x => x.width == 360 && x.height == 202)?.url || video.thumbnails[0]?.url);
|
||||
var thumbnailUrls = videos.map(video => video.thumbnailUrl);
|
||||
}
|
||||
|
||||
if (options.icons) {
|
||||
@ -38,15 +38,37 @@ async function VRCYoutubeSearch(pool, query, options = {}) {
|
||||
iconUrls = [...iconUrls];
|
||||
}
|
||||
|
||||
if (thumbnailUrls || iconUrls) {
|
||||
var {vrcurl: imagesheet_vrcurl, thumbnails, icons} = await makeImageSheetVrcUrl(pool, thumbnailUrls, iconUrls);
|
||||
data.imagesheet_vrcurl = imagesheet_vrcurl;
|
||||
}
|
||||
|
||||
for (let video of videos) {
|
||||
video.vrcurl = await putVrcUrl(pool, {type: "redirect", url: `https://www.youtube.com/watch?v=${video.id}`});
|
||||
video.channel.icon_index = iconUrls?.indexOf(video.channel.iconUrl);
|
||||
delete video.thumbnails;
|
||||
if (thumbnails?.length) {
|
||||
let thumbnail = thumbnails.find(x => x.url == video.thumbnailUrl);
|
||||
video.thumbnail = {
|
||||
x: thumbnail?.x,
|
||||
y: thumbnail?.y,
|
||||
width: thumbnailWidth,
|
||||
height: thumbnailHeight
|
||||
};
|
||||
}
|
||||
if (icons?.length) {
|
||||
let icon = icons.find(x => x.url == video.channel.iconUrl);
|
||||
video.channel.icon = {
|
||||
x: icon?.x,
|
||||
y: icon?.y,
|
||||
width: iconWidth,
|
||||
height: iconHeight
|
||||
};
|
||||
}
|
||||
delete video.thumbnailUrl;
|
||||
delete video.channel.iconUrl;
|
||||
data.results.push(video);
|
||||
}
|
||||
|
||||
if (thumbnailUrls || iconUrls) data.imagesheet_vrcurl = await makeImageSheetVrcUrl(pool, thumbnailUrls, iconUrls);
|
||||
|
||||
|
||||
data.nextpage_vrcurl = await putVrcUrl(pool, {
|
||||
type: "ytContinuation",
|
||||
|
2
app.js
2
app.js
@ -50,7 +50,7 @@ router.get("/vrcurl/:pool/:num", async ctx => {
|
||||
return;
|
||||
}
|
||||
ctx.body = buf;
|
||||
ctx.type = "image/jpeg";
|
||||
ctx.type = "image/png";
|
||||
break;
|
||||
case "ytContinuation":
|
||||
ctx.body = await cachedVRCYoutubeSearch(ctx.params.pool, dest.continuationData, dest.options);
|
||||
|
@ -3,37 +3,66 @@ import { putVrcUrl } from './vrcurl.js';
|
||||
|
||||
var store = {};
|
||||
|
||||
async function createImageSheet(thumbnailUrls = [], iconUrls = []) {
|
||||
const thumbnailWidth = 360;
|
||||
const thumbnailHeight = 202;
|
||||
const iconWidth = 68;
|
||||
const iconHeight = 68;
|
||||
const canvasWidth = (thumbnailUrls.length ? thumbnailWidth : 0) + (iconUrls.length ? iconWidth : 0);
|
||||
const canvasHeight = Math.max(thumbnailHeight * thumbnailUrls.length, iconHeight * iconUrls.length);
|
||||
|
||||
var canvas = createCanvas(canvasWidth, canvasHeight);
|
||||
export const thumbnailWidth = 360;
|
||||
export const thumbnailHeight = 202;
|
||||
export const iconWidth = 68;
|
||||
export const iconHeight = 68;
|
||||
const maxSheetWidth = 2048;
|
||||
const maxSheetHeight = 2048;
|
||||
const maxThumbnailRowLen = Math.floor(maxSheetWidth / thumbnailWidth); // 5
|
||||
const maxThumbnailColLen = Math.floor(maxSheetHeight / thumbnailHeight); // 10
|
||||
const maxIconRowLen = Math.floor(maxSheetWidth / iconWidth); // 30
|
||||
const maxIconColLen = Math.floor(maxSheetHeight / iconHeight); // 30
|
||||
|
||||
|
||||
async function createImageSheet(thumbnailUrls = [], iconUrls = []) {
|
||||
|
||||
|
||||
var thumbnails = thumbnailUrls.map((url, index) => {
|
||||
const x = index % maxThumbnailRowLen * thumbnailWidth;
|
||||
const y = Math.floor(index / maxThumbnailRowLen) * thumbnailHeight;
|
||||
return {x, y, url};
|
||||
});
|
||||
|
||||
const iconStartY = thumbnails.length ? thumbnails.at(-1).y + thumbnailHeight : 0;
|
||||
|
||||
var icons = iconUrls.map((url, index) => {
|
||||
const x = index % maxIconRowLen * iconWidth;
|
||||
const y = iconStartY + Math.floor(index / maxIconRowLen);
|
||||
return {x, y, url};
|
||||
});
|
||||
|
||||
const canvasWidth = Math.max(
|
||||
Math.min(thumbnails.length, maxThumbnailRowLen) * thumbnailWidth,
|
||||
Math.min(icons.length, maxIconRowLen) * iconWidth
|
||||
);
|
||||
const canvasHeight = icons.length ? icons.at(-1).y + iconHeight : thumbnails.length ? thumbnails.at(-1).y + thumbnailHeight : 0;
|
||||
|
||||
var canvas = createCanvas(Math.min(maxSheetWidth, canvasWidth), Math.min(maxSheetHeight, canvasHeight));
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var promises = [];
|
||||
|
||||
if (thumbnailUrls.length) {
|
||||
promises = promises.concat(thumbnailUrls.map((url, index) => (async function(){
|
||||
console.debug("load thumbnail", url);
|
||||
if (thumbnails.length) {
|
||||
promises = promises.concat(thumbnails.map(({x, y, url}) => (async function(){
|
||||
var image = await loadImage(url);
|
||||
ctx.drawImage(image, 0, index * thumbnailHeight, thumbnailWidth, thumbnailHeight);
|
||||
ctx.drawImage(image, x, y, thumbnailWidth, thumbnailHeight);
|
||||
})().catch(error => console.error(error.stack))));
|
||||
}
|
||||
|
||||
if (iconUrls.length) {
|
||||
promises = promises.concat(iconUrls.map((url, index) => (async function(){
|
||||
console.debug("load icon", url);
|
||||
if (icons.length) {
|
||||
promises = promises.concat(icons.map(({x, y, url}) => (async function(){
|
||||
var image = await loadImage(url);
|
||||
ctx.drawImage(image, thumbnailUrls.length ? thumbnailWidth : 0, index * iconHeight, iconWidth, iconHeight);
|
||||
ctx.drawImage(image, x, y, iconWidth, iconHeight);
|
||||
})().catch(error => console.error(error.stack))));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
return canvas.toBuffer("image/jpeg");
|
||||
return {
|
||||
imagesheet: canvas.toBuffer("image/png"),
|
||||
thumbnails, icons
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -50,9 +79,13 @@ export async function makeImageSheetVrcUrl(pool, thumbnailUrls, iconUrls) {
|
||||
promise.catch(error => {
|
||||
console.error(error.stack);
|
||||
});
|
||||
return num;
|
||||
var {thumbnails, icons} = await promise;
|
||||
return {
|
||||
vrcurl: num,
|
||||
thumbnails, icons
|
||||
}
|
||||
}
|
||||
|
||||
export async function getImageSheet(pool, num) {
|
||||
return await store[`${pool}:${num}`];
|
||||
return (await store[`${pool}:${num}`])?.imagesheet;
|
||||
}
|
@ -55,7 +55,7 @@ function parseVideoRendererData(data) {
|
||||
live: Boolean(data.badges?.find(x => x.metadataBadgeRenderer?.style == "BADGE_STYLE_TYPE_LIVE_NOW")),
|
||||
title: data.title?.runs?.[0]?.text,
|
||||
description: data.detailedMetadataSnippets?.[0]?.snippetText?.runs?.reduce((str, obj) => str += obj.text, ""),
|
||||
thumbnails: data.thumbnail?.thumbnails,
|
||||
thumbnailUrl: data.thumbnail?.thumbnails?.find(x => x.width == 360 && x.height == 202)?.url || data.thumbnail?.thumbnails?.[0]?.url,
|
||||
uploaded: data.publishedTimeText?.simpleText,
|
||||
lengthText: data.lengthText?.simpleText,
|
||||
longLengthText: data.lengthText?.accessibility?.accessibilityData?.label,
|
||||
|
@ -8,7 +8,12 @@
|
||||
</style>
|
||||
</head><body>
|
||||
|
||||
<label>search: <input id="input" type="text" value="nyan cat" /> <button id="start">start</button></label>
|
||||
<div>
|
||||
<label>search: <input id="input" type="text" value="nyan cat" /></label>
|
||||
<label><input id="thumbnails" type="checkbox" checked>thumbnails</label>
|
||||
<label><input id="icons" type="checkbox" checked>icons</label>
|
||||
<button id="start">start</button>
|
||||
</div>
|
||||
|
||||
<div id="output"></div>
|
||||
|
||||
@ -21,7 +26,7 @@ var lastData;
|
||||
|
||||
start.onclick = () => {
|
||||
output.innerHTML = "";
|
||||
loadData(`/search?pool=test1000&thumbnails=yes&icons=yes&input=${encodeURIComponent(input.value)}`);
|
||||
loadData(`/search?pool=test1000&thumbnails=${thumbnails.checked}&icons=${icons.checked}&input=${encodeURIComponent(input.value)}`);
|
||||
};
|
||||
nextpage.onclick = () => loadData(`/vrcurl/test1000/${lastData.nextpage_vrcurl}`);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user