feat: more conversion settings

This commit is contained in:
Maya 2026-02-15 12:31:13 +03:00
parent 1ef2639ca5
commit 3e4ff6bdbe
8 changed files with 177 additions and 20 deletions

View File

@ -88,19 +88,32 @@
"quality": "Quality", "quality": "Quality",
"depth": "Color depth", "depth": "Color depth",
"color_space": "Color space", "color_space": "Color space",
"transparency": "Transparency", "transparency": "Transparency"
"metadata": "Metadata"
}, },
"audio": { "audio": {
"quality": "Quality", "bitrate": "Bitrate",
"rate": "Sample rate", "sample_rate": "Sample rate",
"metadata": "Metadata" "channels": "Audio channels"
}, },
"video": { "video": {
"quality": "Quality", "quality": "Quality",
"metadata": "Metadata" "metadata": "Metadata",
"speed": "Conversion speed",
"speed_very_slow": "Very Slow",
"speed_slower": "Slower",
"speed_slow": "Slow",
"speed_medium": "Medium",
"speed_fast": "Fast",
"speed_ultra_fast": "Ultra Fast",
"fps": "Frame rate (FPS)",
"fps_placeholder": "Auto",
"resolution": "Resolution",
"resolution_placeholder": "Auto (e.g., 1920x1080)"
}, },
"document": { "document": {
"something": "Something"
},
"common": {
"metadata": "Metadata" "metadata": "Metadata"
} }
}, },

View File

@ -107,6 +107,7 @@
checked={file.conversionSettings[ checked={file.conversionSettings[
setting.key setting.key
] ?? setting.default} ] ?? setting.default}
placeholder={setting.placeholder}
onchange={(e) => onchange={(e) =>
handleSettingChange( handleSettingChange(
setting.key, setting.key,
@ -120,6 +121,7 @@
value={file.conversionSettings[ value={file.conversionSettings[
setting.key setting.key
] ?? setting.default} ] ?? setting.default}
placeholder={setting.placeholder}
oninput={(e) => oninput={(e) =>
handleSettingChange( handleSettingChange(
setting.key, setting.key,

View File

@ -55,7 +55,7 @@ export class Converter {
* Can be overridden per converter for format-specific settings. * Can be overridden per converter for format-specific settings.
* @param input The input file. * @param input The input file.
*/ */
public getAvailableSettings(input: VertFile): SettingDefinition[] { public async getAvailableSettings(): Promise<SettingDefinition[]> {
return []; return [];
} }
@ -63,9 +63,10 @@ export class Converter {
* Get default settings for a conversion. * Get default settings for a conversion.
* @param input The input file. * @param input The input file.
*/ */
public getDefaultSettings(input: VertFile): ConversionSettings { public async getDefaultSettings(): Promise<ConversionSettings> {
const defaults: ConversionSettings = {}; const defaults: ConversionSettings = {};
this.getAvailableSettings(input).forEach((setting) => { const settings = await this.getAvailableSettings();
settings.forEach((setting) => {
defaults[setting.key] = setting.default; defaults[setting.key] = setting.default;
}); });
return defaults; return defaults;

View File

@ -6,6 +6,7 @@ import { error, log } from "$lib/util/logger";
import { m } from "$lib/paraglide/messages"; import { m } from "$lib/paraglide/messages";
import { Settings } from "$lib/sections/settings/index.svelte"; import { Settings } from "$lib/sections/settings/index.svelte";
import { ToastManager } from "$lib/util/toast.svelte"; import { ToastManager } from "$lib/util/toast.svelte";
import type { SettingDefinition, ConversionSettings } from "$lib/types/conversion-settings";
// TODO: differentiate in UI? (not native formats) // TODO: differentiate in UI? (not native formats)
const videoFormats = [ const videoFormats = [
@ -105,6 +106,63 @@ export class FFmpegConverter extends Converter {
} }
} }
public async getAvailableSettings(): Promise<SettingDefinition[]> {
// audio - bitrate, sample rate, channels, normalize, trim silence
// TODO: detect bitrate, sample rate, audio channels and set default/max accordingly
const bitrate: SettingDefinition = {
key: "bitrate",
label: m["convert.settings.audio.bitrate"](),
type: "select",
default: "auto",
options: CONVERSION_BITRATES.map((b) => ({
value: b.toString(),
label: b.toString(),
})),
};
const sampleRate: SettingDefinition = {
key: "sampleRate",
label: m["convert.settings.audio.sample_rate"](),
type: "select",
default: "auto",
options: SAMPLE_RATES.map((r) => ({
value: r.toString(),
label: r.toString(),
})),
};
const channels: SettingDefinition = {
key: "channels",
label: m["convert.settings.audio.channels"](),
type: "number",
default: 2,
min: 1,
max: 8,
};
const metadata: SettingDefinition = {
key: "metadata",
label: m["convert.settings.common.metadata"](),
type: "boolean",
default: true,
};
// resize, crop, rotate - prob want a ui
return [bitrate, sampleRate, channels, metadata];
}
public async getDefaultSettings(): Promise<ConversionSettings> {
const defaults: ConversionSettings = {};
const settings = await this.getAvailableSettings();
settings.forEach((setting) => {
defaults[setting.key] = setting.default;
});
return defaults;
}
public async convert(input: VertFile, to: string): Promise<VertFile> { public async convert(input: VertFile, to: string): Promise<VertFile> {
if (!to.startsWith(".")) to = `.${to}`; if (!to.startsWith(".")) to = `.${to}`;

View File

@ -116,8 +116,7 @@ export class MagickConverter extends Converter {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars public async getAvailableSettings(): Promise<SettingDefinition[]> {
public getAvailableSettings(input: VertFile): SettingDefinition[] {
// images - quality/compression/quantize/interlace/depth-DPI, resize, crop, rotate, flip/flop, autoOrient?, color space/bit depth, transparency settings // images - quality/compression/quantize/interlace/depth-DPI, resize, crop, rotate, flip/flop, autoOrient?, color space/bit depth, transparency settings
const quality: SettingDefinition = { const quality: SettingDefinition = {
@ -173,7 +172,7 @@ export class MagickConverter extends Converter {
const metadata: SettingDefinition = { const metadata: SettingDefinition = {
key: "metadata", key: "metadata",
label: m["convert.settings.image.metadata"](), label: m["convert.settings.common.metadata"](),
type: "boolean", type: "boolean",
default: true, default: true,
}; };
@ -183,9 +182,10 @@ export class MagickConverter extends Converter {
return [quality, depth, colorSpace, transparency, metadata]; return [quality, depth, colorSpace, transparency, metadata];
} }
public getDefaultSettings(input: VertFile): ConversionSettings { public async getDefaultSettings(): Promise<ConversionSettings> {
const defaults: ConversionSettings = {}; const defaults: ConversionSettings = {};
this.getAvailableSettings(input).forEach((setting) => { const settings = await this.getAvailableSettings();
settings.forEach((setting) => {
defaults[setting.key] = setting.default; defaults[setting.key] = setting.default;
}); });
return defaults; return defaults;

View File

@ -8,6 +8,10 @@ import { Converter, FormatInfo } from "./converter.svelte";
import { PUB_DISABLE_FAILURE_BLOCKS } from "$env/static/public"; import { PUB_DISABLE_FAILURE_BLOCKS } from "$env/static/public";
import { ToastManager } from "$lib/util/toast.svelte"; import { ToastManager } from "$lib/util/toast.svelte";
import { converters } from "./index"; import { converters } from "./index";
import type {
SettingDefinition,
ConversionSettings,
} from "$lib/types/conversion-settings";
interface UploadResponse { interface UploadResponse {
id: string; id: string;
@ -347,6 +351,77 @@ export class VertdConverter extends Converter {
Settings.instance.save(); Settings.instance.save();
} }
public async getAvailableSettings(): Promise<SettingDefinition[]> {
// video - bitrate, fps, resolution, trim, crop, rotate, flip/flop, audio settings?
const qualityOptions = [
{
value: "verySlow",
label: m["convert.settings.video.speed_very_slow"](),
},
{
value: "slower",
label: m["convert.settings.video.speed_slower"](),
},
{ value: "slow", label: m["convert.settings.video.speed_slow"]() },
{
value: "medium",
label: m["convert.settings.video.speed_medium"](),
},
{ value: "fast", label: m["convert.settings.video.speed_fast"]() },
{
value: "ultraFast",
label: m["convert.settings.video.speed_ultra_fast"](),
},
];
const quality: SettingDefinition = {
key: "vertdSpeed",
label: m["convert.settings.video.speed"](),
type: "select",
default: "medium",
options: qualityOptions,
};
// TODO: for fps and resolution, set placeholder to detected values
const fps: SettingDefinition = {
key: "fps",
label: m["convert.settings.video.fps"](),
placeholder: m["convert.settings.video.fps_placeholder"](),
type: "number",
min: 1,
};
const resolution: SettingDefinition = {
key: "resolution",
label: m["convert.settings.video.resolution"](),
placeholder: m["convert.settings.video.resolution_placeholder"](),
type: "string",
};
const metadata: SettingDefinition = {
key: "metadata",
label: m["convert.settings.common.metadata"](),
type: "boolean",
default: true,
};
// trim/crop/rotate - also have another ui for this prob
// import all audio settings?
return [quality, fps, resolution, metadata];
}
public async getDefaultSettings(): Promise<ConversionSettings> {
const defaults: ConversionSettings = {};
const settings = await this.getAvailableSettings();
settings.forEach((setting) => {
defaults[setting.key] = setting.default;
});
return defaults;
}
public async convert(input: VertFile, to: string): Promise<VertFile> { public async convert(input: VertFile, to: string): Promise<VertFile> {
if (to.startsWith(".")) to = to.slice(1); if (to.startsWith(".")) to = to.slice(1);
@ -357,10 +432,17 @@ export class VertdConverter extends Converter {
// https://trac.ffmpeg.org/ticket/4907 // https://trac.ffmpeg.org/ticket/4907
if (input.from === ".webp") { if (input.from === ".webp") {
this.log(`animated webp detected, converting to gif first`); this.log(`animated webp detected, converting to gif first`);
const magickConverter = converters.find((c) => c.name === "imagemagick"); const magickConverter = converters.find(
(c) => c.name === "imagemagick",
);
if (magickConverter) { if (magickConverter) {
try { try {
fileUpload = await magickConverter.convert(input, ".gif", 100); fileUpload = await magickConverter.convert(
input,
".gif",
input.conversionSettings,
100,
);
this.log(`successfully converted webp to gif`); this.log(`successfully converted webp to gif`);
} catch (e) { } catch (e) {
this.log(`failed to convert webp to gif: ${e}`); this.log(`failed to convert webp to gif: ${e}`);

View File

@ -3,9 +3,10 @@ export type SettingType = "number" | "select" | "boolean" | "string" | "range";
export interface SettingDefinition { export interface SettingDefinition {
key: string; key: string;
label: () => string; label: string;
type: SettingType; type: SettingType;
default: any; default?: any;
placeholder?: string;
min?: number; min?: number;
max?: number; max?: number;
step?: number; step?: number;