mirror of https://github.com/VERT-sh/VERT.git
fix: transparent images issues
This commit is contained in:
parent
f210bb886e
commit
2f30c454dc
|
|
@ -61,7 +61,8 @@
|
|||
},
|
||||
"image_sequence": {
|
||||
"image_sequence": "Image Sequence",
|
||||
"fps": "FPS"
|
||||
"fps": "FPS",
|
||||
"transparency": "Transparency"
|
||||
},
|
||||
"large_file_warning": "Due to browser / device limitations, video to audio conversion is disabled for this file as it is larger than {limit}GB. We recommend using Firefox or Safari for files of this size since they have less limitations.",
|
||||
"external_warning": {
|
||||
|
|
|
|||
|
|
@ -49,11 +49,16 @@
|
|||
let imageSequenceFPS = $state(
|
||||
file?.conversionSettings?.imageSequenceFPS ?? 15,
|
||||
);
|
||||
// svelte-ignore state_referenced_locally
|
||||
let imageSequenceTransparency = $state(
|
||||
file?.conversionSettings?.imageSequenceTransparency ?? false,
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (!file) return;
|
||||
file.conversionSettings.imageSequence = imageSequence;
|
||||
file.conversionSettings.imageSequenceFPS = imageSequenceFPS;
|
||||
file.conversionSettings.imageSequenceTransparency = imageSequenceTransparency;
|
||||
});
|
||||
|
||||
const normalize = (str: string) => str.replace(/^\./, "").toLowerCase();
|
||||
|
|
@ -570,29 +575,52 @@
|
|||
>
|
||||
{m["convert.archive_file.extract"]()}
|
||||
</button>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex items-center gap-2 flex-1 min-w-0 h-full"
|
||||
>
|
||||
<FancyInput
|
||||
type="checkbox"
|
||||
class="!w-fit"
|
||||
bind:checked={imageSequence}
|
||||
/>
|
||||
<label for="extract-sequence" class="text-sm">
|
||||
{m["convert.image_sequence.image_sequence"]()}
|
||||
</label>
|
||||
<!-- FIXME this is terrible -->
|
||||
<div class="flex flex-col flex-wrap gap-1">
|
||||
<!-- first row -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex items-center gap-2 flex-1 min-w-0 h-full"
|
||||
>
|
||||
<FancyInput
|
||||
type="checkbox"
|
||||
class="!w-fit"
|
||||
bind:checked={imageSequence}
|
||||
/>
|
||||
<label for="extract-sequence" class="text-sm">
|
||||
{m[
|
||||
"convert.image_sequence.image_sequence"
|
||||
]()}
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-[80px] shrink-0">
|
||||
<FancyInput
|
||||
thin
|
||||
inputClass="!h-9 !text-xs"
|
||||
type="number"
|
||||
extension="FPS"
|
||||
placeholder="15"
|
||||
bind:value={imageSequenceFPS}
|
||||
disabled={!imageSequence}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[80px] shrink-0">
|
||||
<FancyInput
|
||||
thin
|
||||
inputClass="!h-9 !text-xs"
|
||||
type="number"
|
||||
extension="FPS"
|
||||
placeholder="15"
|
||||
bind:value={imageSequenceFPS}
|
||||
disabled={!imageSequence}
|
||||
/>
|
||||
|
||||
<!-- second row -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex items-center gap-2 flex-1 min-w-0 h-full"
|
||||
>
|
||||
<FancyInput
|
||||
type="checkbox"
|
||||
class="!w-fit"
|
||||
bind:checked={imageSequenceTransparency}
|
||||
/>
|
||||
<label for="extract-sequence" class="text-sm">
|
||||
{m["convert.image_sequence.transparency"]()}
|
||||
</label>
|
||||
</div>
|
||||
<!-- second thing -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -339,6 +339,11 @@ const downloadFile = async (url: string, file: VertFile): Promise<Blob> => {
|
|||
});
|
||||
};
|
||||
|
||||
// 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<string>();
|
||||
|
||||
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<number | null> {
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue