mirror of https://github.com/VERT-sh/VERT.git
feat: experimental audio to video
This commit is contained in:
parent
5fb9dbcf35
commit
2191c95500
|
@ -5,8 +5,9 @@ export class FormatInfo {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
name: string,
|
name: string,
|
||||||
public fromSupported: boolean,
|
public fromSupported = true,
|
||||||
public toSupported: boolean,
|
public toSupported = true,
|
||||||
|
public isNative = true,
|
||||||
) {
|
) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
if (!this.name.startsWith(".")) {
|
if (!this.name.startsWith(".")) {
|
||||||
|
|
|
@ -5,23 +5,27 @@ import { browser } from "$app/environment";
|
||||||
import { error, log } from "$lib/logger";
|
import { error, log } from "$lib/logger";
|
||||||
import { addToast } from "$lib/store/ToastProvider";
|
import { addToast } from "$lib/store/ToastProvider";
|
||||||
|
|
||||||
|
const videoFormats = ["mp4", "mkv", "avi", "mov", "webm"];
|
||||||
|
|
||||||
export class FFmpegConverter extends Converter {
|
export class FFmpegConverter extends Converter {
|
||||||
private ffmpeg: FFmpeg = null!;
|
private ffmpeg: FFmpeg = null!;
|
||||||
public name = "ffmpeg";
|
public name = "ffmpeg";
|
||||||
public ready = $state(false);
|
public ready = $state(false);
|
||||||
|
|
||||||
public supportedFormats = [
|
public supportedFormats = [
|
||||||
new FormatInfo("mp3", true, true),
|
new FormatInfo("mp3"),
|
||||||
new FormatInfo("wav", true, true),
|
new FormatInfo("wav"),
|
||||||
new FormatInfo("flac", true, true),
|
new FormatInfo("flac"),
|
||||||
new FormatInfo("ogg", true, true),
|
new FormatInfo("ogg"),
|
||||||
new FormatInfo("aac", true, true),
|
new FormatInfo("aac"),
|
||||||
new FormatInfo("m4a", true, true),
|
new FormatInfo("m4a"),
|
||||||
new FormatInfo("wma", true, true),
|
// TODO: audio to video where it uses the album cover
|
||||||
new FormatInfo("amr", true, true),
|
...videoFormats.map((f) => new FormatInfo(f, true, true, false)),
|
||||||
new FormatInfo("ac3", true, true),
|
new FormatInfo("wma"),
|
||||||
new FormatInfo("alac", true, true),
|
new FormatInfo("amr"),
|
||||||
new FormatInfo("aiff", true, true),
|
new FormatInfo("ac3"),
|
||||||
|
new FormatInfo("alac"),
|
||||||
|
new FormatInfo("aiff"),
|
||||||
];
|
];
|
||||||
|
|
||||||
public readonly reportsProgress = true;
|
public readonly reportsProgress = true;
|
||||||
|
@ -57,6 +61,9 @@ export class FFmpegConverter extends Converter {
|
||||||
ffmpeg.on("progress", (progress) => {
|
ffmpeg.on("progress", (progress) => {
|
||||||
input.progress = progress.progress * 100;
|
input.progress = progress.progress * 100;
|
||||||
});
|
});
|
||||||
|
ffmpeg.on("log", (l) => {
|
||||||
|
log(["converters", this.name], l.message);
|
||||||
|
});
|
||||||
const baseURL =
|
const baseURL =
|
||||||
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm";
|
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm";
|
||||||
await ffmpeg.load({
|
await ffmpeg.load({
|
||||||
|
@ -69,7 +76,33 @@ export class FFmpegConverter extends Converter {
|
||||||
["converters", this.name],
|
["converters", this.name],
|
||||||
`wrote ${input.name} to ffmpeg virtual fs`,
|
`wrote ${input.name} to ffmpeg virtual fs`,
|
||||||
);
|
);
|
||||||
await ffmpeg.exec(["-i", "input", "output" + to]);
|
if (videoFormats.includes(input.from.slice(1))) {
|
||||||
|
// create an audio track from the video
|
||||||
|
await ffmpeg.exec(["-i", "input", "-map", "0:a:0", "output" + to]);
|
||||||
|
} else if (videoFormats.includes(to.slice(1))) {
|
||||||
|
// nab the album art
|
||||||
|
await ffmpeg.exec([
|
||||||
|
"-i",
|
||||||
|
"input",
|
||||||
|
"-an",
|
||||||
|
"-vcodec",
|
||||||
|
"copy",
|
||||||
|
"cover.png",
|
||||||
|
]);
|
||||||
|
await ffmpeg.exec([
|
||||||
|
"-i",
|
||||||
|
"input",
|
||||||
|
"-i",
|
||||||
|
"cover.png",
|
||||||
|
"-loop",
|
||||||
|
"1",
|
||||||
|
...toArgs(to),
|
||||||
|
"output" + to,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
await ffmpeg.exec(["-i", "input", "output" + to]);
|
||||||
|
}
|
||||||
|
|
||||||
log(["converters", this.name], `executed ffmpeg command`);
|
log(["converters", this.name], `executed ffmpeg command`);
|
||||||
const output = (await ffmpeg.readFile(
|
const output = (await ffmpeg.readFile(
|
||||||
"output" + to,
|
"output" + to,
|
||||||
|
@ -82,3 +115,49 @@ export class FFmpegConverter extends Converter {
|
||||||
return new VertFile(new File([output], input.name), to);
|
return new VertFile(new File([output], input.name), to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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): string[] => {
|
||||||
|
const encoder = getEncoder(ext);
|
||||||
|
const args = ["-c:v", encoder];
|
||||||
|
switch (encoder) {
|
||||||
|
case "libx264": {
|
||||||
|
args.push(
|
||||||
|
"-preset",
|
||||||
|
"ultrafast",
|
||||||
|
"-crf",
|
||||||
|
"18",
|
||||||
|
"-tune",
|
||||||
|
"stillimage",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "libvpx": {
|
||||||
|
args.push("-c:v", "libvpx-vp9", "-c:a", "libvorbis");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEncoder = (ext: string): string => {
|
||||||
|
switch (ext) {
|
||||||
|
case ".mkv":
|
||||||
|
case ".mp4":
|
||||||
|
case ".avi":
|
||||||
|
case ".mov":
|
||||||
|
return "libx264";
|
||||||
|
case ".webm":
|
||||||
|
return "libvpx";
|
||||||
|
default:
|
||||||
|
return "copy";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -62,18 +62,18 @@ export class PandocConverter extends Converter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public supportedFormats = [
|
public supportedFormats = [
|
||||||
new FormatInfo("docx", true, true),
|
new FormatInfo("docx"),
|
||||||
new FormatInfo("doc", true, true),
|
new FormatInfo("doc"),
|
||||||
new FormatInfo("md", true, true),
|
new FormatInfo("md"),
|
||||||
new FormatInfo("html", true, true),
|
new FormatInfo("html"),
|
||||||
new FormatInfo("rtf", true, true),
|
new FormatInfo("rtf"),
|
||||||
new FormatInfo("csv", true, true),
|
new FormatInfo("csv"),
|
||||||
new FormatInfo("tsv", true, true),
|
new FormatInfo("tsv"),
|
||||||
new FormatInfo("json", true, true),
|
new FormatInfo("json"),
|
||||||
new FormatInfo("rst", true, true),
|
new FormatInfo("rst"),
|
||||||
new FormatInfo("epub", true, true),
|
new FormatInfo("epub"),
|
||||||
new FormatInfo("odt", true, true),
|
new FormatInfo("odt"),
|
||||||
new FormatInfo("docbook", true, true),
|
new FormatInfo("docbook"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,14 +202,14 @@ export class VertdConverter extends Converter {
|
||||||
public reportsProgress = true;
|
public reportsProgress = true;
|
||||||
|
|
||||||
public supportedFormats = [
|
public supportedFormats = [
|
||||||
new FormatInfo("mkv", true, true),
|
new FormatInfo("mkv"),
|
||||||
new FormatInfo("mp4", true, true),
|
new FormatInfo("mp4"),
|
||||||
new FormatInfo("webm", true, true),
|
new FormatInfo("webm"),
|
||||||
new FormatInfo("avi", true, true),
|
new FormatInfo("avi"),
|
||||||
new FormatInfo("wmv", true, true),
|
new FormatInfo("wmv"),
|
||||||
new FormatInfo("mov", true, true),
|
new FormatInfo("mov"),
|
||||||
new FormatInfo("gif", true, true),
|
new FormatInfo("gif"),
|
||||||
new FormatInfo("mts", true, true),
|
new FormatInfo("mts"),
|
||||||
];
|
];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|
|
@ -17,31 +17,31 @@ export class VipsConverter extends Converter {
|
||||||
public ready = $state(false);
|
public ready = $state(false);
|
||||||
|
|
||||||
public supportedFormats = [
|
public supportedFormats = [
|
||||||
new FormatInfo("png", true, true),
|
new FormatInfo("png"),
|
||||||
new FormatInfo("jpeg", true, true),
|
new FormatInfo("jpeg"),
|
||||||
new FormatInfo("jpg", true, true),
|
new FormatInfo("jpg"),
|
||||||
new FormatInfo("webp", true, true),
|
new FormatInfo("webp"),
|
||||||
new FormatInfo("gif", true, true),
|
new FormatInfo("gif"),
|
||||||
new FormatInfo("ico", true, false),
|
new FormatInfo("ico", true, false),
|
||||||
new FormatInfo("cur", true, false),
|
new FormatInfo("cur", true, false),
|
||||||
new FormatInfo("ani", true, false),
|
new FormatInfo("ani", true, false),
|
||||||
new FormatInfo("heic", true, false),
|
new FormatInfo("heic", true, false),
|
||||||
new FormatInfo("nef", true, false),
|
new FormatInfo("nef", true, false),
|
||||||
new FormatInfo("cr2", true, false),
|
new FormatInfo("cr2", true, false),
|
||||||
new FormatInfo("hdr", true, true),
|
new FormatInfo("hdr"),
|
||||||
new FormatInfo("jpe", true, true),
|
new FormatInfo("jpe"),
|
||||||
new FormatInfo("dng", true, false),
|
new FormatInfo("dng", true, false),
|
||||||
new FormatInfo("mat", true, true),
|
new FormatInfo("mat"),
|
||||||
new FormatInfo("pbm", true, true),
|
new FormatInfo("pbm"),
|
||||||
new FormatInfo("pfm", true, true),
|
new FormatInfo("pfm"),
|
||||||
new FormatInfo("pgm", true, true),
|
new FormatInfo("pgm"),
|
||||||
new FormatInfo("pnm", true, true),
|
new FormatInfo("pnm"),
|
||||||
new FormatInfo("ppm", true, true),
|
new FormatInfo("ppm"),
|
||||||
new FormatInfo("raw", false, true),
|
new FormatInfo("raw", false, true),
|
||||||
new FormatInfo("tif", true, true),
|
new FormatInfo("tif"),
|
||||||
new FormatInfo("tiff", true, true),
|
new FormatInfo("tiff"),
|
||||||
new FormatInfo("jfif", true, true),
|
new FormatInfo("jfif"),
|
||||||
new FormatInfo("avif", true, true),
|
new FormatInfo("avif"),
|
||||||
];
|
];
|
||||||
|
|
||||||
public readonly reportsProgress = false;
|
public readonly reportsProgress = false;
|
||||||
|
|
|
@ -34,11 +34,24 @@ export class VertFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
public findConverter() {
|
public findConverter() {
|
||||||
const converter = this.converters.find(
|
const converter = this.converters.find((converter) => {
|
||||||
(converter) =>
|
if (
|
||||||
converter.formatStrings().includes(this.from) &&
|
!converter.formatStrings().includes(this.from) ||
|
||||||
converter.formatStrings().includes(this.to),
|
!converter.formatStrings().includes(this.to)
|
||||||
);
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theirFrom = converter.supportedFormats.find(
|
||||||
|
(f) => f.name === this.from,
|
||||||
|
);
|
||||||
|
const theirTo = converter.supportedFormats.find(
|
||||||
|
(f) => f.name === this.to,
|
||||||
|
);
|
||||||
|
if (!theirFrom || !theirTo) return false;
|
||||||
|
if (!theirFrom.isNative && !theirTo.isNative) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,7 +227,15 @@
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={availableConverters
|
options={availableConverters
|
||||||
.flatMap((c) =>
|
.flatMap((c) =>
|
||||||
c.formatStrings((f) => f.toSupported),
|
// c.formatStrings((f) => f.toSupported),
|
||||||
|
c.supportedFormats.find(
|
||||||
|
(f) => f.name === file.from,
|
||||||
|
)?.isNative
|
||||||
|
? c.formatStrings((f) => f.toSupported)
|
||||||
|
: c.formatStrings(
|
||||||
|
(f) =>
|
||||||
|
f.toSupported && f.isNative,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.filter(
|
.filter(
|
||||||
(format) =>
|
(format) =>
|
||||||
|
|
Loading…
Reference in New Issue