Fix #26: Scale images to the correct size and use more efficient image formats

This commit is contained in:
2023-06-14 20:37:31 +02:00
parent 61d24ddd7f
commit 3103d3e098
7 changed files with 324 additions and 20 deletions

View File

@ -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) ?? [];
}