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;
@ -266,7 +270,7 @@ export class VertdConverter extends Converter {
new FormatInfo("mov", true, true), new FormatInfo("mov", true, true),
new FormatInfo("gif", true, true), new FormatInfo("gif", true, true),
new FormatInfo("apng", true, true), new FormatInfo("apng", true, true),
new FormatInfo("webp", true, true), new FormatInfo("webp", true, true),
new FormatInfo("mts", true, true), new FormatInfo("mts", true, true),
new FormatInfo("ts", true, true), new FormatInfo("ts", true, true),
new FormatInfo("m2ts", true, true), new FormatInfo("m2ts", true, true),
@ -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;

View File

@ -69,4 +69,4 @@ export default defineConfig(({ command }) => {
__COMMIT_HASH__: JSON.stringify(commitHash), __COMMIT_HASH__: JSON.stringify(commitHash),
}, },
}; };
}); });