Compare commits
No commits in common. "ff6ff97889a40f6c01216093b753ab426a9ad41c" and "d3ff95957c5540033c54cb3f6d739336d258c580" have entirely different histories.
ff6ff97889
...
d3ff95957c
@ -1,6 +1,6 @@
|
|||||||
import { searchYouTubeVideos, continueYouTubeVideoSearch, getYouTubePlaylist, continueYouTubePlaylist } from "./simpleYoutubeSearch.js";
|
import { searchYouTubeVideos, continueYouTubeVideoSearch, getYouTubePlaylist, continueYouTubePlaylist } from "./simpleYoutubeSearch.js";
|
||||||
import { putVrcUrl } from "./vrcurl.js";
|
import { putVrcUrl } from "./vrcurl.js";
|
||||||
import { makeImageSheetVrcUrl } from "./imagesheet.js";
|
import { makeImageSheetVrcUrl, iconWidth, iconHeight } from "./imagesheet.js";
|
||||||
import { getTrending } from "./trending.js";
|
import { getTrending } from "./trending.js";
|
||||||
|
|
||||||
var cache = {};
|
var cache = {};
|
||||||
@ -52,25 +52,27 @@ async function VRCYoutubeSearch(pool, query, options = {}) {
|
|||||||
var {videos, continuationData} = playlistId ? await getYouTubePlaylist(playlistId) : await searchYouTubeVideos(query);
|
var {videos, continuationData} = playlistId ? await getYouTubePlaylist(playlistId) : await searchYouTubeVideos(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var images = [];
|
|
||||||
|
|
||||||
if (options.thumbnails) {
|
if (options.thumbnails) {
|
||||||
videos.forEach(video => video.thumbnail.url && images.push(video.thumbnail));
|
var thumbnailUrls = videos.map(video => video.thumbnail.url);
|
||||||
|
var smallestThumbnail = videos.map(video => video.thumbnail).reduce((smallest, selected) => selected.height < smallest.height ? selected : smallest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.icons) {
|
if (options.icons) {
|
||||||
let iconUrls = new Set();
|
var iconUrls = new Set();
|
||||||
videos.forEach(video => video.channel.iconUrl && iconUrls.add(video.channel.iconUrl));
|
for (let video of videos) {
|
||||||
iconUrls.forEach(url => images.push({
|
iconUrls.add(video.channel.iconUrl);
|
||||||
width: 68,//todo pass from yt data not hardcode
|
}
|
||||||
height: 68,
|
iconUrls = [...iconUrls];
|
||||||
url
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (images.length) {
|
if (thumbnailUrls?.length || iconUrls?.length) {
|
||||||
try {
|
try {
|
||||||
var {vrcurl: imagesheet_vrcurl} = await makeImageSheetVrcUrl(pool, images);
|
var {vrcurl: imagesheet_vrcurl, thumbnails, icons} = await makeImageSheetVrcUrl(pool, {
|
||||||
|
thumbnailUrls,
|
||||||
|
iconUrls,
|
||||||
|
thumbnailWidth: smallestThumbnail.width,
|
||||||
|
thumbnailHeight: smallestThumbnail.height
|
||||||
|
});
|
||||||
data.imagesheet_vrcurl = imagesheet_vrcurl;
|
data.imagesheet_vrcurl = imagesheet_vrcurl;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.stack);
|
console.error(error.stack);
|
||||||
@ -79,20 +81,24 @@ async function VRCYoutubeSearch(pool, query, options = {}) {
|
|||||||
|
|
||||||
for (let video of videos) {
|
for (let video of videos) {
|
||||||
video.vrcurl = await putVrcUrl(pool, {type: "redirect", url: `https://www.youtube.com/watch?v=${video.id}`});
|
video.vrcurl = await putVrcUrl(pool, {type: "redirect", url: `https://www.youtube.com/watch?v=${video.id}`});
|
||||||
let thumbnail = images.find(image => image.url == video.thumbnail.url);
|
if (thumbnails?.length) {
|
||||||
video.thumbnail = thumbnail ? {
|
let thumbnail = thumbnails.find(x => x.url == video.thumbnail.url);
|
||||||
x: thumbnail?.x,
|
video.thumbnail = {
|
||||||
y: thumbnail?.y,
|
x: thumbnail?.x,
|
||||||
width: thumbnail?.width,
|
y: thumbnail?.y,
|
||||||
height: thumbnail?.height
|
width: smallestThumbnail?.width,
|
||||||
} : undefined;
|
height: smallestThumbnail?.height
|
||||||
let icon = images.find(image => image.url == video.channel.iconUrl);
|
};
|
||||||
video.channel.icon = icon ? {
|
}
|
||||||
x: icon?.x,
|
if (icons?.length) {
|
||||||
y: icon?.y,
|
let icon = icons.find(x => x.url == video.channel.iconUrl);
|
||||||
width: icon?.width,
|
video.channel.icon = {
|
||||||
height: icon?.height
|
x: icon?.x,
|
||||||
} : undefined;
|
y: icon?.y,
|
||||||
|
width: iconWidth,
|
||||||
|
height: iconHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
if (options.captions) {
|
if (options.captions) {
|
||||||
video.captions_vrcurl = await putVrcUrl(pool, {type: "captions", videoId: video.id});
|
video.captions_vrcurl = await putVrcUrl(pool, {type: "captions", videoId: video.id});
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,75 @@
|
|||||||
import { createCanvas, loadImage } from 'canvas';
|
import { createCanvas, loadImage } from 'canvas';
|
||||||
import potpack from 'potpack';
|
|
||||||
import { putVrcUrl } from './vrcurl.js';
|
import { putVrcUrl } from './vrcurl.js';
|
||||||
|
|
||||||
var store = {};
|
var store = {};
|
||||||
|
|
||||||
async function createImageSheet(images /*[{width, height, url}]*/) {
|
|
||||||
images.forEach(image => {
|
const maxSheetWidth = 2048;
|
||||||
image.w = image.width;
|
const maxSheetHeight = 2048;
|
||||||
image.h = image.height;
|
export const iconWidth = 68;
|
||||||
});
|
export const iconHeight = 68;
|
||||||
var {w, h, fill} = potpack(images);
|
//const maxIconRowLen = Math.floor(maxSheetWidth / iconWidth);
|
||||||
if (w > 2048) {
|
const maxIconRowLen = 3;
|
||||||
console.warn("Imagesheet exceeded max width");
|
//const maxIconColLen = Math.floor(maxSheetHeight / iconHeight);
|
||||||
w = 2048;
|
|
||||||
}
|
async function createImageSheet({thumbnailUrls = [], iconUrls = [], thumbnailWidth = 360, thumbnailHeight = 202}) {
|
||||||
if (h > 2048) {
|
|
||||||
console.warn("Imagesheet exceeded max height");
|
const maxThumbnailRowLen = Math.floor(maxSheetWidth / thumbnailWidth);
|
||||||
h = 2048;
|
//const maxThumbnailColLen = Math.floor(maxSheetHeight / thumbnailHeight);
|
||||||
}
|
|
||||||
var canvas = createCanvas(w, h);
|
var thumbnails = thumbnailUrls.map((url, index) => ({
|
||||||
|
x: index % maxThumbnailRowLen * thumbnailWidth,
|
||||||
|
y: Math.floor(index / maxThumbnailRowLen) * thumbnailHeight,
|
||||||
|
url
|
||||||
|
}));
|
||||||
|
|
||||||
|
const iconStartX = thumbnailWidth * Math.min(maxThumbnailRowLen, thumbnails.length);
|
||||||
|
|
||||||
|
var icons = iconUrls.map((url, index) => ({
|
||||||
|
x: iconStartX + index % maxIconRowLen * iconWidth,
|
||||||
|
y: Math.floor(index / maxIconRowLen) * iconHeight,
|
||||||
|
url
|
||||||
|
}));
|
||||||
|
|
||||||
|
const canvasWidth = Math.max(
|
||||||
|
Math.min(thumbnails.length, maxThumbnailRowLen) * thumbnailWidth,
|
||||||
|
iconStartX + Math.min(icons.length, maxIconRowLen) * iconWidth
|
||||||
|
);
|
||||||
|
const canvasHeight = Math.max(thumbnails.length ? thumbnails.at(-1).y + thumbnailHeight : 0, icons.length ? icons.at(-1)?.y + iconHeight : 0);
|
||||||
|
|
||||||
|
var canvas = createCanvas(Math.min(maxSheetWidth, canvasWidth), Math.min(maxSheetHeight, canvasHeight));
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
await Promise.all(images.map(({x, y, w, h, url}) => (async function(){
|
var promises = [];
|
||||||
if (!url) return;
|
|
||||||
var image = await loadImage(url);
|
|
||||||
ctx.drawImage(image, x, y, w, h);
|
|
||||||
})().catch(error => console.error(error.stack))));
|
|
||||||
|
|
||||||
|
if (thumbnails.length) {
|
||||||
|
promises = promises.concat(thumbnails.map(({x, y, url}) => (async function(){
|
||||||
|
if (!url) return;
|
||||||
|
var image = await loadImage(url);
|
||||||
|
ctx.drawImage(image, x, y, thumbnailWidth, thumbnailHeight);
|
||||||
|
})().catch(error => console.error(error.stack))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons.length) {
|
||||||
|
promises = promises.concat(icons.map(({x, y, url}) => (async function(){
|
||||||
|
if (!url) return;
|
||||||
|
var image = await loadImage(url);
|
||||||
|
ctx.drawImage(image, x, y, iconWidth, iconHeight);
|
||||||
|
})().catch(error => console.error(error.stack))));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
return {
|
return {
|
||||||
imagesheet: canvas.toBuffer("image/png"),
|
imagesheet: canvas.toBuffer("image/png"),
|
||||||
images
|
thumbnails, icons
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function makeImageSheetVrcUrl(pool, images) {
|
|
||||||
|
export async function makeImageSheetVrcUrl(pool, opts) {
|
||||||
var num = await putVrcUrl(pool, {type: "imagesheet"});
|
var num = await putVrcUrl(pool, {type: "imagesheet"});
|
||||||
var key = `${pool}:${num}`;
|
var key = `${pool}:${num}`;
|
||||||
var promise = createImageSheet(images);
|
var promise = createImageSheet(opts);
|
||||||
store[key] = promise;
|
store[key] = promise;
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -11,8 +11,7 @@
|
|||||||
"fast-xml-parser": "^4.3.4",
|
"fast-xml-parser": "^4.3.4",
|
||||||
"keyv": "^4.5.4",
|
"keyv": "^4.5.4",
|
||||||
"koa": "^2.14.2",
|
"koa": "^2.14.2",
|
||||||
"koa-send": "^5.0.1",
|
"koa-send": "^5.0.1"
|
||||||
"potpack": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
@ -1263,11 +1262,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
|
||||||
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
|
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
|
||||||
},
|
},
|
||||||
"node_modules/potpack": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="
|
|
||||||
},
|
|
||||||
"node_modules/promise-inflight": {
|
"node_modules/promise-inflight": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
"fast-xml-parser": "^4.3.4",
|
"fast-xml-parser": "^4.3.4",
|
||||||
"keyv": "^4.5.4",
|
"keyv": "^4.5.4",
|
||||||
"koa": "^2.14.2",
|
"koa": "^2.14.2",
|
||||||
"koa-send": "^5.0.1",
|
"koa-send": "^5.0.1"
|
||||||
"potpack": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
|
7
util.js
7
util.js
@ -40,12 +40,7 @@ export function parseVideoRendererData(data) {
|
|||||||
description: data.detailedMetadataSnippets?.[0]?.snippetText?.runs?.concatRunsText()
|
description: data.detailedMetadataSnippets?.[0]?.snippetText?.runs?.concatRunsText()
|
||||||
|| data.descriptionSnippet?.runs?.concatRunsText(),
|
|| data.descriptionSnippet?.runs?.concatRunsText(),
|
||||||
//thumbnailUrl: data.thumbnail?.thumbnails?.find(x => (x.width == 360 && x.height == 202) || (x.width == 246 && x.height == 138))?.url || data.thumbnail?.thumbnails?.[0]?.url,
|
//thumbnailUrl: data.thumbnail?.thumbnails?.find(x => (x.width == 360 && x.height == 202) || (x.width == 246 && x.height == 138))?.url || data.thumbnail?.thumbnails?.[0]?.url,
|
||||||
//thumbnail: data.thumbnail?.thumbnails?.find(x => (x.width == 360 && x.height == 202) || (x.width == 246 && x.height == 138)) || data.thumbnail?.thumbnails?.[0],
|
thumbnail: data.thumbnail?.thumbnails?.find(x => (x.width == 360 && x.height == 202) || (x.width == 246 && x.height == 138)) || data.thumbnail?.thumbnails?.[0],
|
||||||
thumbnail: {
|
|
||||||
url: `https://i.ytimg.com/vi/${data.videoId}/mqdefault.jpg`,
|
|
||||||
width: 320,
|
|
||||||
height: 180
|
|
||||||
},
|
|
||||||
uploaded: data.publishedTimeText?.simpleText || data.videoInfo?.runs?.[2]?.text,
|
uploaded: data.publishedTimeText?.simpleText || data.videoInfo?.runs?.[2]?.text,
|
||||||
lengthText: data.lengthText?.simpleText,
|
lengthText: data.lengthText?.simpleText,
|
||||||
longLengthText: data.lengthText?.accessibility?.accessibilityData?.label,
|
longLengthText: data.lengthText?.accessibility?.accessibilityData?.label,
|
||||||
|
Loading…
Reference in New Issue
Block a user