Fix #26: Scale images to the correct size and use more efficient image formats
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
import { IGNORE_USERS, MASTODON_INSTANCE } from '$env/static/private';
|
||||
import { enableVerboseLog, log } from '$lib/log';
|
||||
import type { Account, AccountAvatar, Post, Tag } from '$lib/mastodon/response';
|
||||
import type { Account, AccountAvatar, Post, SongThumbnailImage, Tag } from '$lib/mastodon/response';
|
||||
import type { SongInfo } from '$lib/odesliResponse';
|
||||
import { TimelineReader } from '$lib/server/timeline';
|
||||
import sqlite3 from 'sqlite3';
|
||||
@ -48,6 +48,13 @@ type AccountAvatarRow = {
|
||||
sizeDescriptor: string;
|
||||
};
|
||||
|
||||
type SongThumbnailAvatarRow = {
|
||||
song_thumbnailUrl: string;
|
||||
file: string;
|
||||
sizeDescriptor: string;
|
||||
kind: number;
|
||||
};
|
||||
|
||||
type Migration = {
|
||||
id: number;
|
||||
name: string;
|
||||
@ -280,11 +287,23 @@ function getMigrations(): Migration[] {
|
||||
name: 'resized avatars',
|
||||
statement: `
|
||||
CREATE TABLE accountsavatars (
|
||||
file TEXT NOT NULL PRIMARY KEY,
|
||||
file TEXT NOT NULL PRIMARY KEY,
|
||||
account_url TEXT NOT NULL,
|
||||
sizeDescriptor TEXT NOT NULL,
|
||||
FOREIGN KEY (account_url) REFERENCES accounts(url)
|
||||
);`
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'resized song thumbnails',
|
||||
statement: `
|
||||
CREATE TABLE songsthumbnails (
|
||||
file TEXT NOT NULL PRIMARY KEY,
|
||||
song_thumbnailUrl TEXT NOT NULL,
|
||||
sizeDescriptor TEXT NOT NULL,
|
||||
kind INTEGER NOT NULL,
|
||||
FOREIGN KEY (song_thumbnailUrl) REFERENCES songs(thumbnailUrl)
|
||||
);`
|
||||
}
|
||||
];
|
||||
}
|
||||
@ -487,7 +506,7 @@ function getPostData(filterQuery: string, params: FilterParameter): Promise<Post
|
||||
});
|
||||
}
|
||||
|
||||
function getTagData(postIdsParams: String, postIds: string[]): Promise<Map<string, Tag[]>> {
|
||||
function getTagData(postIdsParams: string, postIds: string[]): Promise<Map<string, Tag[]>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT post_id, tags.url, tags.tag
|
||||
@ -552,7 +571,7 @@ function getSongData(postIdsParams: String, postIds: string[]): Promise<Map<stri
|
||||
}
|
||||
|
||||
function getAvatarData(
|
||||
accountUrlsParams: String,
|
||||
accountUrlsParams: string,
|
||||
accountUrls: string[]
|
||||
): Promise<Map<string, AccountAvatar[]>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -585,6 +604,44 @@ function getAvatarData(
|
||||
});
|
||||
}
|
||||
|
||||
function getSongThumbnailData(
|
||||
thumbUrlsParams: string,
|
||||
thumbUrls: string[]
|
||||
): Promise<Map<string, SongThumbnailImage[]>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT song_thumbnailUrl, file, sizeDescriptor, kind
|
||||
FROM songsthumbnails
|
||||
WHERE song_thumbnailUrl IN (${thumbUrlsParams});`,
|
||||
thumbUrls,
|
||||
(err, rows: SongThumbnailAvatarRow[]) => {
|
||||
if (err != null) {
|
||||
log.error('Error loading avatars', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const thumbnailMap: Map<string, SongThumbnailImage[]> = rows.reduce(
|
||||
(result: Map<string, SongThumbnailImage[]>, item) => {
|
||||
const info: SongThumbnailImage = {
|
||||
songThumbnailUrl: item.song_thumbnailUrl,
|
||||
file: item.file,
|
||||
sizeDescriptor: item.sizeDescriptor,
|
||||
kind: item.kind
|
||||
};
|
||||
result.set(item.song_thumbnailUrl, [
|
||||
...(result.get(item.song_thumbnailUrl) || []),
|
||||
info
|
||||
]);
|
||||
return result;
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
resolve(thumbnailMap);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPosts(
|
||||
since: string | null,
|
||||
before: string | null,
|
||||
@ -634,6 +691,12 @@ async function getPostsInternal(
|
||||
const postIds = rows.map((r: PostRow) => r.url);
|
||||
const tagMap = await getTagData(postIdsParams, postIds);
|
||||
const songMap = await getSongData(postIdsParams, postIds);
|
||||
for (const entry of songMap) {
|
||||
for (const songInfo of entry[1]) {
|
||||
const thumbs = await getSongThumbnails(songInfo);
|
||||
songInfo.resizedThumbnails = thumbs;
|
||||
}
|
||||
}
|
||||
|
||||
const accountUrls = [...new Set(rows.map((r: PostRow) => r.account_url))];
|
||||
const accountUrlsParams = accountUrls.map(() => '?').join(', ');
|
||||
@ -679,6 +742,36 @@ export async function removeAvatars(accountUrl: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveSongThumbnail(thumb: SongThumbnailImage): Promise<void> {
|
||||
// Will be null if file already existed
|
||||
if (thumb.file === null) {
|
||||
return;
|
||||
}
|
||||
const params: FilterParameter = {
|
||||
$songId: thumb.songThumbnailUrl,
|
||||
$file: thumb.file,
|
||||
$sizeDescriptor: thumb.sizeDescriptor,
|
||||
$kind: thumb.kind.valueOf()
|
||||
};
|
||||
const sql = `
|
||||
INSERT INTO songsthumbnails
|
||||
(song_thumbnailUrl, file, sizeDescriptor, kind) VALUES ($songId, $file, $sizeDescriptor, $kind)
|
||||
ON CONFLICT(file) DO UPDATE SET
|
||||
song_thumbnailUrl=excluded.song_thumbnailUrl,
|
||||
sizeDescriptor=excluded.sizeDescriptor,
|
||||
kind=excluded.kind;`;
|
||||
await waitReady();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveAvatar(avatar: AccountAvatar): Promise<void> {
|
||||
// Will be null if file already existed
|
||||
if (avatar.file === null) {
|
||||
@ -745,3 +838,11 @@ export async function getAvatars(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSongThumbnails(song: SongInfo): Promise<SongThumbnailImage[]> {
|
||||
if (!song.thumbnailUrl) {
|
||||
return [];
|
||||
}
|
||||
const rows = await getSongThumbnailData('?', [song.thumbnailUrl]);
|
||||
return rows.get(song.thumbnailUrl) ?? [];
|
||||
}
|
||||
|
Reference in New Issue
Block a user