mirror of https://github.com/VERT-sh/VERT.git
feat: image sequences
This commit is contained in:
parent
95ab5092cf
commit
f210bb886e
|
|
@ -41,8 +41,20 @@
|
||||||
let searchQuery = $state("");
|
let searchQuery = $state("");
|
||||||
let rootCategory: string | null = null;
|
let rootCategory: string | null = null;
|
||||||
|
|
||||||
let imageSequence = $state(false);
|
// svelte-ignore state_referenced_locally
|
||||||
let imageSequenceFPS = $state(15);
|
let imageSequence = $state(
|
||||||
|
file?.conversionSettings?.imageSequence ?? false,
|
||||||
|
);
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
|
let imageSequenceFPS = $state(
|
||||||
|
file?.conversionSettings?.imageSequenceFPS ?? 15,
|
||||||
|
);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!file) return;
|
||||||
|
file.conversionSettings.imageSequence = imageSequence;
|
||||||
|
file.conversionSettings.imageSequenceFPS = imageSequenceFPS;
|
||||||
|
});
|
||||||
|
|
||||||
const normalize = (str: string) => str.replace(/^\./, "").toLowerCase();
|
const normalize = (str: string) => str.replace(/^\./, "").toLowerCase();
|
||||||
|
|
||||||
|
|
@ -59,7 +71,7 @@
|
||||||
|
|
||||||
// if imageSequence is checked, filter image category to animated formats only
|
// if imageSequence is checked, filter image category to animated formats only
|
||||||
if (imageSequence && cat === "image") {
|
if (imageSequence && cat === "image") {
|
||||||
const animatedFormats = [".webp", ".gif"]; // .apng not supported by magick-wasm rn
|
const animatedFormats = [".webp", ".gif", ".apng"]; // .apng not supported by magick-wasm rn
|
||||||
formats = formats.filter((f) => animatedFormats.includes(f));
|
formats = formats.filter((f) => animatedFormats.includes(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,7 +264,6 @@
|
||||||
onselect?.(allUnfilteredFormats[0]);
|
onselect?.(allUnfilteredFormats[0]);
|
||||||
} else {
|
} else {
|
||||||
// no formats available, keeping previous selection
|
// no formats available, keeping previous selection
|
||||||
|
|
||||||
// i feel like this is all very scuffed and we need a better search and filtering system
|
// i feel like this is all very scuffed and we need a better search and filtering system
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { toArgs, animatedImageFormats } from "$lib/converters/ffmpeg.codecs";
|
||||||
|
import type { ConversionSettings } from "$lib/types/conversion-settings";
|
||||||
|
|
||||||
|
export function buildImageSequenceCommand(
|
||||||
|
outputFormat: string,
|
||||||
|
settings: ConversionSettings,
|
||||||
|
isAlac: boolean,
|
||||||
|
): string[] {
|
||||||
|
const to = `.${outputFormat}`;
|
||||||
|
const codecArgs = toArgs(to, isAlac);
|
||||||
|
const baseArgs = [
|
||||||
|
"-f",
|
||||||
|
"concat",
|
||||||
|
"-safe",
|
||||||
|
"0",
|
||||||
|
"-i",
|
||||||
|
"frames.txt",
|
||||||
|
...codecArgs,
|
||||||
|
];
|
||||||
|
const scaleFilter = "scale=trunc(iw/2)*2:trunc(ih/2)*2";
|
||||||
|
const isAnimatedImage = animatedImageFormats.includes(outputFormat);
|
||||||
|
|
||||||
|
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",
|
||||||
|
);
|
||||||
|
} else if (outputFormat === "gif") {
|
||||||
|
baseArgs.push(
|
||||||
|
"-filter_complex",
|
||||||
|
`fps=${settings.imageSequenceFPS || 15},${scaleFilter},split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse`,
|
||||||
|
);
|
||||||
|
} else if (isAnimatedImage) {
|
||||||
|
baseArgs.push("-vf", scaleFilter);
|
||||||
|
if (outputFormat === "apng") baseArgs.push("-plays", "0");
|
||||||
|
} else {
|
||||||
|
baseArgs.push("-vf", scaleFilter, "-pix_fmt", "yuv420p");
|
||||||
|
}
|
||||||
|
|
||||||
|
baseArgs.push("output" + to);
|
||||||
|
return baseArgs;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
// prettier-ignore
|
||||||
|
export const CONVERSION_BITRATES = ["auto", "custom", 16, 32, 64, 96, 128, 160, 192, 256, 320] as const;
|
||||||
|
|
||||||
|
// 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"];
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
export const lossless = ["flac", "m4a", "caf", "alac", "wav", "dsd", "dsf", "dff"];
|
||||||
|
|
||||||
|
export const getCodecs = (
|
||||||
|
ext: string,
|
||||||
|
isAlac: boolean = false,
|
||||||
|
): { video: string; audio: string } => {
|
||||||
|
switch (ext) {
|
||||||
|
// video <-> audio
|
||||||
|
case ".mp4":
|
||||||
|
case ".mkv":
|
||||||
|
case ".mov":
|
||||||
|
case ".mts":
|
||||||
|
case ".ts":
|
||||||
|
case ".m2ts":
|
||||||
|
case ".flv":
|
||||||
|
case ".f4v":
|
||||||
|
case ".m4v":
|
||||||
|
case ".3gp":
|
||||||
|
case ".3g2":
|
||||||
|
return { video: "libx264", audio: "aac" };
|
||||||
|
case ".wmv":
|
||||||
|
return { video: "wmv2", audio: "wmav2" };
|
||||||
|
case ".webm":
|
||||||
|
case ".ogv":
|
||||||
|
return {
|
||||||
|
video: ext === ".webm" ? "libvpx" : "libtheora",
|
||||||
|
audio: "libvorbis",
|
||||||
|
};
|
||||||
|
case ".avi":
|
||||||
|
case ".divx":
|
||||||
|
return { video: "mpeg4", audio: "libmp3lame" };
|
||||||
|
case ".mpg":
|
||||||
|
case ".mpeg":
|
||||||
|
case ".vob":
|
||||||
|
return { video: "mpeg2video", audio: "mp2" };
|
||||||
|
case ".mxf":
|
||||||
|
return { video: "mpeg2video", audio: "pcm_s16le" };
|
||||||
|
|
||||||
|
// audio
|
||||||
|
case ".mp3":
|
||||||
|
return { video: "libx264", audio: "libmp3lame" };
|
||||||
|
case ".flac":
|
||||||
|
return { video: "libx264", audio: "flac" };
|
||||||
|
case ".wav":
|
||||||
|
return { video: "libx264", audio: "pcm_s16le" };
|
||||||
|
case ".ogg":
|
||||||
|
case ".oga":
|
||||||
|
return { video: "libx264", audio: "libvorbis" };
|
||||||
|
case ".opus":
|
||||||
|
return { video: "libx264", audio: "libopus" };
|
||||||
|
case ".aac":
|
||||||
|
return { video: "libx264", audio: "aac" };
|
||||||
|
case ".m4a":
|
||||||
|
return {
|
||||||
|
video: "libx264",
|
||||||
|
audio: isAlac ? "alac" : "aac",
|
||||||
|
};
|
||||||
|
case ".alac":
|
||||||
|
return { video: "libx264", audio: "alac" };
|
||||||
|
case ".wma":
|
||||||
|
return { video: "libx264", audio: "wmav2" };
|
||||||
|
|
||||||
|
// animated images
|
||||||
|
case ".gif":
|
||||||
|
case ".webp":
|
||||||
|
//case ".apng":
|
||||||
|
return { video: ext.slice(1), audio: "none" };
|
||||||
|
|
||||||
|
default:
|
||||||
|
return { video: "copy", audio: "copy" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// and here i was, thinking i'd be done with ffmpeg after finishing vertd
|
||||||
|
// but OH NO we just HAD to have someone suggest to allow album art video generation.
|
||||||
|
//
|
||||||
|
// i hate you SO much.
|
||||||
|
// - love, maddie
|
||||||
|
export const toArgs = (ext: string, isAlac: boolean = false): string[] => {
|
||||||
|
const codecs = getCodecs(ext, isAlac);
|
||||||
|
const args = ["-c:v", codecs.video];
|
||||||
|
|
||||||
|
switch (codecs.video) {
|
||||||
|
case "libx264": {
|
||||||
|
args.push(
|
||||||
|
"-preset",
|
||||||
|
"ultrafast",
|
||||||
|
"-crf",
|
||||||
|
"18",
|
||||||
|
"-tune",
|
||||||
|
"stillimage",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "libvpx": {
|
||||||
|
args.push("-c:v", "libvpx-vp9");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "mpeg2video": {
|
||||||
|
// for mpeg, mpg, vob, mxf
|
||||||
|
if (ext === ".mxf") args.push("-ar", "48000"); // force 48kHz sample rate
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only add audio codec if not a no-audio format
|
||||||
|
if (codecs.audio !== "none") {
|
||||||
|
args.push("-c:a", codecs.audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecs.audio === "aac") args.push("-strict", "experimental");
|
||||||
|
|
||||||
|
if (ext === ".divx") args.unshift("-f", "avi");
|
||||||
|
if (ext === ".mxf") args.push("-strict", "unofficial");
|
||||||
|
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConversionBitrate = (typeof CONVERSION_BITRATES)[number];
|
||||||
|
export type SampleRate = (typeof SAMPLE_RATES)[number];
|
||||||
|
|
@ -6,37 +6,21 @@ 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 {
|
||||||
|
videoFormats,
|
||||||
|
getCodecs,
|
||||||
|
toArgs,
|
||||||
|
lossless,
|
||||||
|
CONVERSION_BITRATES,
|
||||||
|
SAMPLE_RATES,
|
||||||
|
} from "./ffmpeg.codecs";
|
||||||
|
import { buildImageSequenceCommand } from "./ffmpeg.animated";
|
||||||
import type {
|
import type {
|
||||||
SettingDefinition,
|
SettingDefinition,
|
||||||
ConversionSettings,
|
ConversionSettings,
|
||||||
} from "$lib/types/conversion-settings";
|
} from "$lib/types/conversion-settings";
|
||||||
|
|
||||||
// TODO: differentiate in UI? (not native formats)
|
// TODO: differentiate in UI? (not native formats)
|
||||||
const videoFormats = [
|
|
||||||
"mkv",
|
|
||||||
"mp4",
|
|
||||||
"avi",
|
|
||||||
"mov",
|
|
||||||
"webm",
|
|
||||||
"ts",
|
|
||||||
"mts",
|
|
||||||
"m2ts",
|
|
||||||
"wmv",
|
|
||||||
"mpg",
|
|
||||||
"mpeg",
|
|
||||||
"flv",
|
|
||||||
"f4v",
|
|
||||||
"vob",
|
|
||||||
"m4v",
|
|
||||||
"3gp",
|
|
||||||
"3g2",
|
|
||||||
"mxf",
|
|
||||||
"ogv",
|
|
||||||
"rm",
|
|
||||||
"rmvb",
|
|
||||||
"divx",
|
|
||||||
];
|
|
||||||
|
|
||||||
export class FFmpegConverter extends Converter {
|
export class FFmpegConverter extends Converter {
|
||||||
private ffmpeg: FFmpeg = null!;
|
private ffmpeg: FFmpeg = null!;
|
||||||
public name = "ffmpeg";
|
public name = "ffmpeg";
|
||||||
|
|
@ -415,23 +399,62 @@ export class FFmpegConverter extends Converter {
|
||||||
const inputFormat = input.from.slice(1);
|
const inputFormat = input.from.slice(1);
|
||||||
const outputFormat = to.slice(1);
|
const outputFormat = to.slice(1);
|
||||||
const m4a = isAlac || to === ".m4a";
|
const m4a = isAlac || to === ".m4a";
|
||||||
|
const isImageSequence = input.isZip() && settings.imageSequence;
|
||||||
|
|
||||||
const lossless = [
|
|
||||||
"flac",
|
|
||||||
"m4a",
|
|
||||||
"caf",
|
|
||||||
"alac",
|
|
||||||
"wav",
|
|
||||||
"dsd",
|
|
||||||
"dsf",
|
|
||||||
"dff",
|
|
||||||
];
|
|
||||||
const userBitrate = settings.bitrate;
|
const userBitrate = settings.bitrate;
|
||||||
const customBitrate = settings.customBitrate;
|
const customBitrate = settings.customBitrate;
|
||||||
const userSampleRate = settings.sampleRate;
|
const userSampleRate = settings.sampleRate;
|
||||||
const customSampleRate = settings.customSampleRate;
|
const customSampleRate = settings.customSampleRate;
|
||||||
const keepMetadata = settings.metadata;
|
const keepMetadata = settings.metadata;
|
||||||
|
|
||||||
|
// image sequences -> animated image // video
|
||||||
|
if (isImageSequence) {
|
||||||
|
this.log(`converting image sequence ${input.name} to ${to}`);
|
||||||
|
|
||||||
|
const { extractZip } = await import("$lib/util/file");
|
||||||
|
const entries = (await extractZip(input.file)).sort((a, b) =>
|
||||||
|
a.filename.localeCompare(b.filename, undefined, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!entries.length)
|
||||||
|
throw new Error("No images found in zip archive");
|
||||||
|
|
||||||
|
const imageFiles: Array<{ name: string }> = [];
|
||||||
|
|
||||||
|
for (const [index, entry] of entries.entries()) {
|
||||||
|
const fileName =
|
||||||
|
entry.filename.split("/").pop() ?? entry.filename;
|
||||||
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
||||||
|
if (!ext) continue;
|
||||||
|
|
||||||
|
const paddedName = `img${String(index + 1).padStart(5, "0")}.${ext}`;
|
||||||
|
await ffmpeg.writeFile(paddedName, entry.data);
|
||||||
|
imageFiles.push({ name: paddedName });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageFiles.length)
|
||||||
|
throw new Error("No images found in zip archive");
|
||||||
|
|
||||||
|
const listContent = imageFiles
|
||||||
|
.map(
|
||||||
|
(image) =>
|
||||||
|
`file '${image.name}'\nduration ${1 / (settings.imageSequenceFPS || 15)}`,
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
await ffmpeg.writeFile(
|
||||||
|
"frames.txt",
|
||||||
|
`${listContent}\nfile '${imageFiles[imageFiles.length - 1].name}'\n`,
|
||||||
|
);
|
||||||
|
this.log(`wrote ${imageFiles.length} images to ffmpeg virtual fs`);
|
||||||
|
|
||||||
|
return buildImageSequenceCommand(outputFormat, settings, isAlac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// else normal single file conversion
|
||||||
|
|
||||||
let audioBitrateArgs: string[] = [];
|
let audioBitrateArgs: string[] = [];
|
||||||
let sampleRateArgs: string[] = [];
|
let sampleRateArgs: string[] = [];
|
||||||
let channelsArgs: string[] = [];
|
let channelsArgs: string[] = [];
|
||||||
|
|
@ -683,138 +706,3 @@ export class FFmpegConverter extends Converter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// and here i was, thinking i'd be done with ffmpeg after finishing vertd
|
|
||||||
// but OH NO we just HAD to have someone suggest to allow album art video generation.
|
|
||||||
//
|
|
||||||
// i hate you SO much.
|
|
||||||
// - love, maddie
|
|
||||||
const toArgs = (ext: string, isAlac: boolean = false): string[] => {
|
|
||||||
const codecs = getCodecs(ext, isAlac);
|
|
||||||
const args = ["-c:v", codecs.video];
|
|
||||||
|
|
||||||
switch (codecs.video) {
|
|
||||||
case "libx264": {
|
|
||||||
args.push(
|
|
||||||
"-preset",
|
|
||||||
"ultrafast",
|
|
||||||
"-crf",
|
|
||||||
"18",
|
|
||||||
"-tune",
|
|
||||||
"stillimage",
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "libvpx": {
|
|
||||||
args.push("-c:v", "libvpx-vp9");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "mpeg2video": {
|
|
||||||
// for mpeg, mpg, vob, mxf
|
|
||||||
if (ext === ".mxf") args.push("-ar", "48000"); // force 48kHz sample rate
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push("-c:a", codecs.audio);
|
|
||||||
|
|
||||||
if (codecs.audio === "aac") args.push("-strict", "experimental");
|
|
||||||
|
|
||||||
if (ext === ".divx") args.unshift("-f", "avi");
|
|
||||||
if (ext === ".mxf") args.push("-strict", "unofficial");
|
|
||||||
|
|
||||||
return args;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCodecs = (
|
|
||||||
ext: string,
|
|
||||||
isAlac: boolean = false,
|
|
||||||
): { video: string; audio: string } => {
|
|
||||||
switch (ext) {
|
|
||||||
// video <-> audio
|
|
||||||
case ".mp4":
|
|
||||||
case ".mkv":
|
|
||||||
case ".mov":
|
|
||||||
case ".mts":
|
|
||||||
case ".ts":
|
|
||||||
case ".m2ts":
|
|
||||||
case ".flv":
|
|
||||||
case ".f4v":
|
|
||||||
case ".m4v":
|
|
||||||
case ".3gp":
|
|
||||||
case ".3g2":
|
|
||||||
return { video: "libx264", audio: "aac" };
|
|
||||||
case ".wmv":
|
|
||||||
return { video: "wmv2", audio: "wmav2" };
|
|
||||||
case ".webm":
|
|
||||||
case ".ogv":
|
|
||||||
return {
|
|
||||||
video: ext === ".webm" ? "libvpx" : "libtheora",
|
|
||||||
audio: "libvorbis",
|
|
||||||
};
|
|
||||||
case ".avi":
|
|
||||||
case ".divx":
|
|
||||||
return { video: "mpeg4", audio: "libmp3lame" };
|
|
||||||
case ".mpg":
|
|
||||||
case ".mpeg":
|
|
||||||
case ".vob":
|
|
||||||
return { video: "mpeg2video", audio: "mp2" };
|
|
||||||
case ".mxf":
|
|
||||||
return { video: "mpeg2video", audio: "pcm_s16le" };
|
|
||||||
|
|
||||||
// audio
|
|
||||||
case ".mp3":
|
|
||||||
return { video: "libx264", audio: "libmp3lame" };
|
|
||||||
case ".flac":
|
|
||||||
return { video: "libx264", audio: "flac" };
|
|
||||||
case ".wav":
|
|
||||||
return { video: "libx264", audio: "pcm_s16le" };
|
|
||||||
case ".ogg":
|
|
||||||
case ".oga":
|
|
||||||
return { video: "libx264", audio: "libvorbis" };
|
|
||||||
case ".opus":
|
|
||||||
return { video: "libx264", audio: "libopus" };
|
|
||||||
case ".aac":
|
|
||||||
return { video: "libx264", audio: "aac" };
|
|
||||||
case ".m4a":
|
|
||||||
return {
|
|
||||||
video: "libx264",
|
|
||||||
audio: isAlac ? "alac" : "aac",
|
|
||||||
};
|
|
||||||
case ".alac":
|
|
||||||
return { video: "libx264", audio: "alac" };
|
|
||||||
case ".wma":
|
|
||||||
return { video: "libx264", audio: "wmav2" };
|
|
||||||
|
|
||||||
default:
|
|
||||||
return { video: "libx264", audio: "aac" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONVERSION_BITRATES = [
|
|
||||||
"auto",
|
|
||||||
"custom",
|
|
||||||
320,
|
|
||||||
256,
|
|
||||||
192,
|
|
||||||
128,
|
|
||||||
96,
|
|
||||||
64,
|
|
||||||
32,
|
|
||||||
] as const;
|
|
||||||
export type ConversionBitrate = (typeof CONVERSION_BITRATES)[number];
|
|
||||||
|
|
||||||
export const SAMPLE_RATES = [
|
|
||||||
"auto",
|
|
||||||
"custom",
|
|
||||||
48000,
|
|
||||||
44100,
|
|
||||||
32000,
|
|
||||||
22050,
|
|
||||||
16000,
|
|
||||||
11025,
|
|
||||||
8000,
|
|
||||||
] as const;
|
|
||||||
export type SampleRate = (typeof SAMPLE_RATES)[number];
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import type {
|
||||||
SettingDefinition,
|
SettingDefinition,
|
||||||
ConversionSettings,
|
ConversionSettings,
|
||||||
} from "$lib/types/conversion-settings";
|
} from "$lib/types/conversion-settings";
|
||||||
import { CONVERSION_BITRATES, SAMPLE_RATES } from "./ffmpeg.svelte";
|
import { CONVERSION_BITRATES, SAMPLE_RATES } from "./ffmpeg.codecs";
|
||||||
import { ToastManager } from "$lib/util/toast.svelte";
|
import { ToastManager } from "$lib/util/toast.svelte";
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import type {
|
||||||
SettingDefinition,
|
SettingDefinition,
|
||||||
ConversionSettings,
|
ConversionSettings,
|
||||||
} from "$lib/types/conversion-settings";
|
} from "$lib/types/conversion-settings";
|
||||||
import { CONVERSION_BITRATES, SAMPLE_RATES } from "./ffmpeg.svelte";
|
import { CONVERSION_BITRATES, SAMPLE_RATES } from "./ffmpeg.codecs";
|
||||||
import { formatBytes } from "$lib/util/file";
|
import { formatBytes } from "$lib/util/file";
|
||||||
|
|
||||||
interface UploadResponse {
|
interface UploadResponse {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
type ConversionBitrate,
|
type ConversionBitrate,
|
||||||
SAMPLE_RATES,
|
SAMPLE_RATES,
|
||||||
type SampleRate,
|
type SampleRate,
|
||||||
} from "$lib/converters/ffmpeg.svelte";
|
} from "$lib/converters/ffmpeg.codecs";
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
||||||
import FancyInput from "$lib/components/functional/FancyInput.svelte";
|
import FancyInput from "$lib/components/functional/FancyInput.svelte";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { PUB_VERTD_URL } from "$env/static/public";
|
import { PUB_VERTD_URL } from "$env/static/public";
|
||||||
import type { ConversionBitrate } from "$lib/converters/ffmpeg.svelte";
|
import type { ConversionBitrate } from "$lib/converters/ffmpeg.codecs";
|
||||||
import type { ConversionSpeed } from "$lib/converters/vertd.svelte";
|
import type { ConversionSpeed } from "$lib/converters/vertd.svelte";
|
||||||
import { readSettings } from "$lib/util/settings";
|
import { readSettings } from "$lib/util/settings";
|
||||||
import { VertdInstance } from "./vertdSettings.svelte";
|
import { VertdInstance } from "./vertdSettings.svelte";
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,13 @@ class Files {
|
||||||
this.files.push(vf);
|
this.files.push(vf);
|
||||||
this._addThumbnail(vf);
|
this._addThumbnail(vf);
|
||||||
|
|
||||||
|
// set converter
|
||||||
|
// TODO: this is weird, we rely on conversionSettings for the right converter but zip archives obv dont have settings to change
|
||||||
|
vf.conversionSettings = {
|
||||||
|
...vf.conversionSettings,
|
||||||
|
converter: vf.converters[0].name,
|
||||||
|
};
|
||||||
|
|
||||||
ToastManager.add({
|
ToastManager.add({
|
||||||
type: "success",
|
type: "success",
|
||||||
message: m["convert.archive_file.detected"]({
|
message: m["convert.archive_file.detected"]({
|
||||||
|
|
|
||||||
|
|
@ -175,33 +175,48 @@ export class VertFile {
|
||||||
|
|
||||||
if (!this.converters.length) throw new Error("No converters found");
|
if (!this.converters.length) throw new Error("No converters found");
|
||||||
|
|
||||||
const customConverter = this.converters.find(
|
let converter: Converter | undefined;
|
||||||
(c) => c.name === this.conversionSettings.converter,
|
const isImageSequence =
|
||||||
);
|
this.conversionSettings.imageSequence && this.isZip();
|
||||||
let converter = customConverter;
|
|
||||||
|
|
||||||
if (!converter) {
|
// force ffmpeg for image sequences
|
||||||
const compatibleConverters = this.findConverters([
|
// TODO: should allow vertd as well probably(?) but maybe in the future
|
||||||
this.from,
|
if (isImageSequence) {
|
||||||
this.to,
|
converter = converters.find((c) => c.name === "ffmpeg");
|
||||||
]);
|
if (!converter) {
|
||||||
if (compatibleConverters.length) {
|
throw new Error(
|
||||||
converter = compatibleConverters[0];
|
"FFmpeg converter not found for image sequence conversion",
|
||||||
log(
|
|
||||||
["file", "convert"],
|
|
||||||
`found compatible converter: ${converter.name}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log(
|
|
||||||
["file", "convert"],
|
|
||||||
`no compatible converter found for ${this.from} to ${this.to}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log(
|
const customConverter = this.converters.find(
|
||||||
["file", "convert"],
|
(c) => c.name === this.conversionSettings.converter,
|
||||||
`using custom converter from settings: ${converter.name}`,
|
|
||||||
);
|
);
|
||||||
|
converter = customConverter;
|
||||||
|
|
||||||
|
if (!converter) {
|
||||||
|
const compatibleConverters = this.findConverters([
|
||||||
|
this.from,
|
||||||
|
this.to,
|
||||||
|
]);
|
||||||
|
if (compatibleConverters.length) {
|
||||||
|
converter = compatibleConverters[0];
|
||||||
|
log(
|
||||||
|
["file", "convert"],
|
||||||
|
`found compatible converter: ${converter.name}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log(
|
||||||
|
["file", "convert"],
|
||||||
|
`no compatible converter found for ${this.from} to ${this.to}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log(
|
||||||
|
["file", "convert"],
|
||||||
|
`using custom converter from settings: ${converter.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!converter) throw new Error("No converter found");
|
if (!converter) throw new Error("No converter found");
|
||||||
|
|
@ -224,14 +239,15 @@ export class VertFile {
|
||||||
try {
|
try {
|
||||||
// for zips: extract > convert each > re-zip
|
// for zips: extract > convert each > re-zip
|
||||||
// else convert normally
|
// else convert normally
|
||||||
res = this.isZip()
|
res =
|
||||||
? await this.convertZip(converter)
|
this.isZip() && !this.conversionSettings.imageSequence
|
||||||
: await converter.convert(
|
? await this.convertZip(converter)
|
||||||
this,
|
: await converter.convert(
|
||||||
this.to,
|
this,
|
||||||
this.conversionSettings,
|
this.to,
|
||||||
...args,
|
this.conversionSettings,
|
||||||
);
|
...args,
|
||||||
|
);
|
||||||
this.result = res;
|
this.result = res;
|
||||||
if (this.fallbackToastId !== null) {
|
if (this.fallbackToastId !== null) {
|
||||||
ToastManager.remove(this.fallbackToastId);
|
ToastManager.remove(this.fallbackToastId);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue