-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
diff --git a/src/lib/converters/ffmpeg.animated.ts b/src/lib/converters/ffmpeg.animated.ts
index 5a6f5fc..6b86551 100644
--- a/src/lib/converters/ffmpeg.animated.ts
+++ b/src/lib/converters/ffmpeg.animated.ts
@@ -1,5 +1,6 @@
import { toArgs, animatedImageFormats } from "$lib/converters/ffmpeg.codecs";
import type { ConversionSettings } from "$lib/types/conversion-settings";
+import { videoFormats } from "./vertd.svelte";
export function buildImageSequenceCommand(
outputFormat: string,
@@ -19,32 +20,52 @@ export function buildImageSequenceCommand(
];
const scaleFilter = "scale=trunc(iw/2)*2:trunc(ih/2)*2";
const isAnimatedImage = animatedImageFormats.includes(outputFormat);
+ const enableTransparency = settings.imageSequenceTransparency ?? false;
- if (
- outputFormat === "mp4" ||
- outputFormat === "mkv" ||
- outputFormat === "mov"
- ) {
- baseArgs.push("-vf", scaleFilter, "-pix_fmt", "yuv420p");
- } else if (outputFormat === "webm") {
- baseArgs.push(
- "-vf",
- scaleFilter,
- "-pix_fmt",
- "yuva420p",
- "-auto-alt-ref",
- "0",
- );
+ if (videoFormats.includes(outputFormat)) {
+ const fpsFilter = `fps=${settings.imageSequenceFPS || 15}`;
+ const blackCompositeFilter =
+ `color=c=black,format=rgb24[bg];` +
+ `[0:v]${fpsFilter},${scaleFilter},setsar=1[fg];` +
+ `[bg][fg]scale2ref[bg2][fg2];` +
+ `[bg2][fg2]overlay=format=auto:shortest=1,setsar=1`;
+
+ if (outputFormat === "webm" && enableTransparency) {
+ baseArgs.push(
+ "-filter_complex",
+ `[0:v]${fpsFilter},${scaleFilter},setsar=1`,
+ "-pix_fmt",
+ "yuva420p",
+ "-auto-alt-ref",
+ "0",
+ );
+ } else {
+ baseArgs.push(
+ "-filter_complex",
+ blackCompositeFilter,
+ "-pix_fmt",
+ "yuv420p",
+ );
+ }
} else if (outputFormat === "gif") {
+ const paletteuse = enableTransparency
+ ? "[p]paletteuse=alpha_threshold=128"
+ : "[p]paletteuse";
baseArgs.push(
"-filter_complex",
- `fps=${settings.imageSequenceFPS || 15},${scaleFilter},split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse`,
+ `fps=${settings.imageSequenceFPS || 15},${scaleFilter},split[s0][s1];[s0]palettegen[p];[s1]${paletteuse}`,
);
} else if (isAnimatedImage) {
baseArgs.push("-vf", scaleFilter);
- if (outputFormat === "apng") baseArgs.push("-plays", "0");
+ if (outputFormat === "apng") {
+ baseArgs.push("-plays", "0");
+ if (enableTransparency) baseArgs.push("-pix_fmt", "rgba");
+ } else if (outputFormat === "webp") {
+ if (enableTransparency) baseArgs.push("-pix_fmt", "rgba");
+ }
} else {
- baseArgs.push("-vf", scaleFilter, "-pix_fmt", "yuv420p");
+ const pixFmt = enableTransparency ? "yuva420p" : "yuv420p";
+ baseArgs.push("-vf", scaleFilter, "-pix_fmt", pixFmt);
}
baseArgs.push("output" + to);
diff --git a/src/lib/converters/ffmpeg.codecs.ts b/src/lib/converters/ffmpeg.codecs.ts
index 30a83eb..b3c1447 100644
--- a/src/lib/converters/ffmpeg.codecs.ts
+++ b/src/lib/converters/ffmpeg.codecs.ts
@@ -4,9 +4,6 @@ export const CONVERSION_BITRATES = ["auto", "custom", 16, 32, 64, 96, 128, 160,
// prettier-ignore
export const SAMPLE_RATES = ["auto", "custom", 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 96000,] as const;
-// prettier-ignore
-export const videoFormats = ["mkv", "mp4", "avi", "mov", "webm", "ts", "mts", "m2ts", "wmv", "mpg", "mpeg", "flv", "f4v", "vob", "m4v", "3gp", "3g2", "mxf", "ogv", "rm", "rmvb", "divx"];
-
// prettier-ignore
export const animatedImageFormats = ["gif", "webp", "apng"];
@@ -30,6 +27,7 @@ export const getCodecs = (
case ".m4v":
case ".3gp":
case ".3g2":
+ case ".nut":
return { video: "libx264", audio: "aac" };
case ".wmv":
return { video: "wmv2", audio: "wmav2" };
@@ -48,6 +46,14 @@ export const getCodecs = (
return { video: "mpeg2video", audio: "mp2" };
case ".mxf":
return { video: "mpeg2video", audio: "pcm_s16le" };
+ case ".h264":
+ return { video: "libx264", audio: "none" };
+ case ".swf":
+ return { video: "flv1", audio: "mp3" };
+ case ".amv":
+ return { video: "amv", audio: "adpcm_ima_amv" };
+ case ".asf":
+ return { video: "wmv2", audio: "wmav2" };
// audio
case ".mp3":
@@ -76,7 +82,7 @@ export const getCodecs = (
// animated images
case ".gif":
case ".webp":
- //case ".apng":
+ case ".apng":
return { video: ext.slice(1), audio: "none" };
default:
diff --git a/src/lib/converters/ffmpeg.svelte.ts b/src/lib/converters/ffmpeg.svelte.ts
index 9c9c94f..d4a170c 100644
--- a/src/lib/converters/ffmpeg.svelte.ts
+++ b/src/lib/converters/ffmpeg.svelte.ts
@@ -7,7 +7,6 @@ import { m } from "$lib/paraglide/messages";
import { Settings } from "$lib/sections/settings/index.svelte";
import { ToastManager } from "$lib/util/toast.svelte";
import {
- videoFormats,
getCodecs,
toArgs,
lossless,
@@ -19,6 +18,7 @@ import type {
SettingDefinition,
ConversionSettings,
} from "$lib/types/conversion-settings";
+import { videoFormats } from "./vertd.svelte";
// TODO: differentiate in UI? (not native formats)
export class FFmpegConverter extends Converter {
@@ -58,7 +58,7 @@ export class FFmpegConverter extends Converter {
new FormatInfo("m4b", true, true),
new FormatInfo("voc", true, true),
new FormatInfo("weba", true, true),
- ...videoFormats.map((f) => new FormatInfo(f, true, true, false, 0)),
+ ...videoFormats.map((f: string) => new FormatInfo(f, true, true, false, 0)),
];
public readonly reportsProgress = true;
@@ -194,7 +194,7 @@ export class FFmpegConverter extends Converter {
if (!to.startsWith(".")) to = `.${to}`;
const conversionSettings =
- Object.keys(settings).length > 0
+ Object.keys(settings).length > 5 // TODO: find better way to do this lmfao, rn we are just assuming all settings are present if there's at least 5 keys but ts bad
? settings
: await this.getDefaultSettings(); // use defaults if not provided
diff --git a/src/lib/converters/magick.svelte.ts b/src/lib/converters/magick.svelte.ts
index 9d072e1..04d13f4 100644
--- a/src/lib/converters/magick.svelte.ts
+++ b/src/lib/converters/magick.svelte.ts
@@ -302,7 +302,7 @@ export class MagickConverter extends Converter {
// every other format handled by magick worker
const conversionSettings = JSON.stringify(
- Object.keys(settings).length > 0
+ Object.keys(settings).length > 5
? settings // user-provided settings
: await this.getDefaultSettings(input), // use defaults if not provided
);
diff --git a/src/lib/converters/mediabunny.svelte.ts b/src/lib/converters/mediabunny.svelte.ts
index fe1c36e..325fc19 100644
--- a/src/lib/converters/mediabunny.svelte.ts
+++ b/src/lib/converters/mediabunny.svelte.ts
@@ -492,7 +492,7 @@ export class MediabunnyConverter extends Converter {
});
const conversionSettings =
- Object.keys(settings).length > 0
+ Object.keys(settings).length > 5
? settings // user-provided settings
: await this.getDefaultSettings(file); // use defaults if not provided
diff --git a/src/lib/converters/vertd.svelte.ts b/src/lib/converters/vertd.svelte.ts
index 2d73716..862526a 100644
--- a/src/lib/converters/vertd.svelte.ts
+++ b/src/lib/converters/vertd.svelte.ts
@@ -339,6 +339,11 @@ const downloadFile = async (url: string, file: VertFile): Promise
=> {
});
};
+// prettier-ignore
+export const videoFormats = ["mp4", "mkv", "webm", "avi", "wmv", "mov", "gif", "apng", "webp", "mts", "ts", "m2ts", "mpg", "mpeg", "flv", "f4v", "vob", "m4v", "3gp", "3g2", "mxf", "ogv", "rm", "rmvb", "h264", "divx", "swf", "amv", "asf", "nut"];
+const cantEncode = ["rm", "rmvb"];
+const cantDecode = [""];
+
export class VertdConverter extends Converter {
public name = "vertd";
public ready = $state(false);
@@ -358,36 +363,12 @@ export class VertdConverter extends Converter {
private cancelledConversions = new Set();
public supportedFormats = [
- new FormatInfo("mp4", true, true),
- new FormatInfo("mkv", true, true),
- new FormatInfo("webm", true, true),
- new FormatInfo("avi", true, true),
- new FormatInfo("wmv", true, true),
- new FormatInfo("mov", true, true),
- new FormatInfo("gif", true, true),
- new FormatInfo("apng", true, true),
- new FormatInfo("webp", true, true),
- new FormatInfo("mts", true, true),
- new FormatInfo("ts", true, true),
- new FormatInfo("m2ts", true, true),
- new FormatInfo("mpg", true, true),
- new FormatInfo("mpeg", true, true),
- new FormatInfo("flv", true, true),
- new FormatInfo("f4v", true, true),
- new FormatInfo("vob", true, true),
- new FormatInfo("m4v", true, true),
- new FormatInfo("3gp", true, true),
- new FormatInfo("3g2", true, true),
- new FormatInfo("mxf", true, true),
- new FormatInfo("ogv", true, true),
- new FormatInfo("rm", true, false),
- new FormatInfo("rmvb", true, false),
- new FormatInfo("h264", true, true),
- new FormatInfo("divx", true, true),
- new FormatInfo("swf", true, true),
- new FormatInfo("amv", true, true),
- new FormatInfo("asf", true, true),
- new FormatInfo("nut", true, true),
+ ...videoFormats
+ .map((f: string) => new FormatInfo(f, true, true, true, 0))
+ .filter((format) => !cantEncode.includes(format.name.slice(1)))
+ .filter((format) => !cantDecode.includes(format.name.slice(1))),
+ ...cantEncode.map((f) => new FormatInfo(f, true, false, true, 0)),
+ ...cantDecode.map((f) => new FormatInfo(f, false, true, true, 0)),
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -404,6 +385,7 @@ export class VertdConverter extends Converter {
this.log("created converter");
this.log("not rly sure how to implement this :P");
this.status = "ready";
+ this.log(JSON.stringify(this.supportedFormats, null, 2));
}
private async getServerSizeLimit(apiUrl: string): Promise {
@@ -763,7 +745,7 @@ export class VertdConverter extends Converter {
let fileUpload = input;
const conversionSettings = // vertd expects object not string json
- Object.keys(settings).length > 0
+ Object.keys(settings).length > 5
? settings // user-provided settings
: await this.getDefaultSettings(input); // use defaults if not provided