mirror of https://github.com/VERT-sh/VERT.git
parent
e8cdb18dd5
commit
76ff9cc704
|
@ -5,6 +5,7 @@ import { browser } from "$app/environment";
|
||||||
import { error, log } from "$lib/logger";
|
import { error, log } from "$lib/logger";
|
||||||
import { addToast } from "$lib/store/ToastProvider";
|
import { addToast } from "$lib/store/ToastProvider";
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
|
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||||
|
|
||||||
// TODO: differentiate in UI? (not native formats)
|
// TODO: differentiate in UI? (not native formats)
|
||||||
const videoFormats = [
|
const videoFormats = [
|
||||||
|
@ -203,15 +204,23 @@ export class FFmpegConverter extends Converter {
|
||||||
const outputFormat = to.slice(1);
|
const outputFormat = to.slice(1);
|
||||||
|
|
||||||
const lossless = ["flac", "alac", "wav"];
|
const lossless = ["flac", "alac", "wav"];
|
||||||
let audioBitrateArgs: string[];
|
const userSetting = Settings.instance.settings.ffmpegQuality;
|
||||||
if (
|
log(["converters", this.name], `using user setting for audio bitrate: ${userSetting}`);
|
||||||
lossless.includes(inputFormat) &&
|
let audioBitrateArgs: string[] = [];
|
||||||
!lossless.includes(outputFormat)
|
|
||||||
) {
|
if (userSetting !== "auto") {
|
||||||
|
// user's setting
|
||||||
|
audioBitrateArgs = ["-b:a", `${userSetting}k`];
|
||||||
|
} else {
|
||||||
|
// detect bitrate of original file and use
|
||||||
|
if (lossless.includes(inputFormat) && !lossless.includes(outputFormat)) {
|
||||||
audioBitrateArgs = ["-b:a", "320k"];
|
audioBitrateArgs = ["-b:a", "320k"];
|
||||||
|
log(["converters", this.name], `using default audio bitrate: 320k`);
|
||||||
} else {
|
} else {
|
||||||
const inputBitrate = await this.detectAudioBitrate(ffmpeg);
|
const inputBitrate = await this.detectAudioBitrate(ffmpeg);
|
||||||
audioBitrateArgs = inputBitrate ? ["-b:a", `${inputBitrate}k`] : [];
|
audioBitrateArgs = inputBitrate ? ["-b:a", `${inputBitrate}k`] : [];
|
||||||
|
log(["converters", this.name], `using detected audio bitrate: ${inputBitrate}k`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// video to audio
|
// video to audio
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { VertFile } from "$lib/types";
|
||||||
import MagickWorker from "$lib/workers/magick?worker&url";
|
import MagickWorker from "$lib/workers/magick?worker&url";
|
||||||
import { Converter, FormatInfo } from "./converter.svelte";
|
import { Converter, FormatInfo } from "./converter.svelte";
|
||||||
import { imageFormats } from "./magick-automated";
|
import { imageFormats } from "./magick-automated";
|
||||||
|
import { Settings } from "$lib/sections/settings/index.svelte";
|
||||||
|
|
||||||
export class MagickConverter extends Converter {
|
export class MagickConverter extends Converter {
|
||||||
private worker: Worker = browser
|
private worker: Worker = browser
|
||||||
|
@ -108,7 +109,11 @@ export class MagickConverter extends Converter {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
...args: any[]
|
...args: any[]
|
||||||
): Promise<VertFile> {
|
): Promise<VertFile> {
|
||||||
const compression: number | undefined = args.at(0);
|
let compression: number | undefined = args.at(0);
|
||||||
|
if (compression == null) {
|
||||||
|
compression = Settings.instance.settings.magickQuality;
|
||||||
|
log(["converters", this.name], `using user setting for quality: ${compression}%`);
|
||||||
|
}
|
||||||
log(["converters", this.name], `converting ${input.name} to ${to}`);
|
log(["converters", this.name], `converting ${input.name} to ${to}`);
|
||||||
|
|
||||||
// handle converting from SVG manually because magick-wasm doesn't support it
|
// handle converting from SVG manually because magick-wasm doesn't support it
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={100}
|
max={100}
|
||||||
|
extension={"%"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|
|
@ -26,6 +26,7 @@ magickPromise
|
||||||
const handleMessage = async (message: any): Promise<any> => {
|
const handleMessage = async (message: any): Promise<any> => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "convert": {
|
case "convert": {
|
||||||
|
const compression: number | undefined = message.compression;
|
||||||
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
||||||
message.to = message.to.toLowerCase();
|
message.to = message.to.toLowerCase();
|
||||||
if (message.to === ".jfif") message.to = ".jpeg";
|
if (message.to === ".jfif") message.to = ".jpeg";
|
||||||
|
@ -66,7 +67,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
const convertedImgs: Uint8Array[] = [];
|
const convertedImgs: Uint8Array[] = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
imgs.map(async (img, i) => {
|
imgs.map(async (img, i) => {
|
||||||
const output = await magickConvert(img, message.to);
|
const output = await magickConvert(img, message.to, compression);
|
||||||
convertedImgs[i] = output;
|
convertedImgs[i] = output;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -74,7 +75,10 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
const zip = makeZip(
|
const zip = makeZip(
|
||||||
convertedImgs.map(
|
convertedImgs.map(
|
||||||
(img, i) =>
|
(img, i) =>
|
||||||
new File([img], `image${i}.${message.to.slice(1)}`),
|
new File(
|
||||||
|
[new Uint8Array(img)],
|
||||||
|
`image${i}.${message.to.slice(1)}`,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
"images.zip",
|
"images.zip",
|
||||||
);
|
);
|
||||||
|
@ -104,9 +108,13 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
message.to,
|
message.to,
|
||||||
|
compression
|
||||||
);
|
);
|
||||||
files.push(
|
files.push(
|
||||||
new File([blob], `image${i}${message.to}`),
|
new File(
|
||||||
|
[new Uint8Array(blob)],
|
||||||
|
`image${i}${message.to}`,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -150,6 +158,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
const converted = await magickConvert(
|
const converted = await magickConvert(
|
||||||
img,
|
img,
|
||||||
message.to,
|
message.to,
|
||||||
|
compression
|
||||||
);
|
);
|
||||||
outputs.push(converted);
|
outputs.push(converted);
|
||||||
break;
|
break;
|
||||||
|
@ -163,7 +172,10 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
const zip = makeZip(
|
const zip = makeZip(
|
||||||
outputs.map(
|
outputs.map(
|
||||||
(img, i) =>
|
(img, i) =>
|
||||||
new File([img], `image${i}.${message.to.slice(1)}`),
|
new File(
|
||||||
|
[new Uint8Array(img)],
|
||||||
|
`image${i}.${message.to.slice(1)}`,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
"images.zip",
|
"images.zip",
|
||||||
);
|
);
|
||||||
|
@ -210,7 +222,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const converted = await magickConvert(img, message.to);
|
const converted = await magickConvert(img, message.to, compression);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "finished",
|
type: "finished",
|
||||||
|
@ -228,7 +240,10 @@ const readToEnd = async (reader: ReadableStreamDefaultReader<Uint8Array>) => {
|
||||||
if (value) chunks.push(value);
|
if (value) chunks.push(value);
|
||||||
done = d;
|
done = d;
|
||||||
}
|
}
|
||||||
const blob = new Blob(chunks, { type: "application/zip" });
|
const blob = new Blob(
|
||||||
|
chunks.map((chunk) => new Uint8Array(chunk)),
|
||||||
|
{ type: "application/zip" },
|
||||||
|
);
|
||||||
const arrayBuffer = await blob.arrayBuffer();
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
return new Uint8Array(arrayBuffer);
|
return new Uint8Array(arrayBuffer);
|
||||||
};
|
};
|
||||||
|
@ -272,8 +287,12 @@ const magickToBlob = async (img: IMagickImage): Promise<Blob> => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
reject(new Error("Pixel data is null"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const imageData = new ImageData(
|
const imageData = new ImageData(
|
||||||
new Uint8ClampedArray(data?.buffer || new ArrayBuffer(0)),
|
new Uint8ClampedArray(data),
|
||||||
img.width,
|
img.width,
|
||||||
img.height,
|
img.height,
|
||||||
);
|
);
|
||||||
|
@ -288,7 +307,11 @@ const magickToBlob = async (img: IMagickImage): Promise<Blob> => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const magickConvert = async (img: IMagickImage, to: string) => {
|
const magickConvert = async (
|
||||||
|
img: IMagickImage,
|
||||||
|
to: string,
|
||||||
|
compression?: number,
|
||||||
|
) => {
|
||||||
const intermediary = await magickToBlob(img);
|
const intermediary = await magickToBlob(img);
|
||||||
const buf = new Uint8Array(await intermediary.arrayBuffer());
|
const buf = new Uint8Array(await intermediary.arrayBuffer());
|
||||||
let fmt = to.slice(1).toUpperCase();
|
let fmt = to.slice(1).toUpperCase();
|
||||||
|
@ -296,6 +319,8 @@ const magickConvert = async (img: IMagickImage, to: string) => {
|
||||||
|
|
||||||
const result = await new Promise<Uint8Array>((resolve) => {
|
const result = await new Promise<Uint8Array>((resolve) => {
|
||||||
ImageMagick.read(buf, MagickFormat.Png, (image) => {
|
ImageMagick.read(buf, MagickFormat.Png, (image) => {
|
||||||
|
// magick-wasm automatically clamps (https://github.com/dlemstra/magick-wasm/blob/76fc6f2b0c0497d2ddc251bbf6174b4dc92ac3ea/src/magick-image.ts#L2480)
|
||||||
|
if (compression) image.quality = compression;
|
||||||
image.write(fmt as unknown as MagickFormat, (o) => {
|
image.write(fmt as unknown as MagickFormat, (o) => {
|
||||||
resolve(structuredClone(o));
|
resolve(structuredClone(o));
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue