mirror of https://github.com/VERT-sh/VERT.git
feat: metadata setting
need to update vertd next optimizes metadata code too (oops)
This commit is contained in:
parent
c516bf636b
commit
8e68f023d4
|
@ -77,7 +77,7 @@
|
|||
"vertd_not_found": "Could not find the vertd instance to start video conversion. Are you sure the instance URL is set correctly?",
|
||||
"worker_downloading": "The {type} converter is currently being initialized, please wait a few moments.",
|
||||
"worker_error": "The {type} converter had an error during initialization, please try again later.",
|
||||
"worker_timeout": "The {type} converter is taking longer than expected to initialize, please wait a few moments.",
|
||||
"worker_timeout": "The {type} converter is taking longer than expected to initialize, please wait a few more moments or refresh the page.",
|
||||
"audio": "audio",
|
||||
"doc": "document",
|
||||
"image": "image"
|
||||
|
@ -104,6 +104,10 @@
|
|||
"filename_format": "File name format",
|
||||
"filename_description": "This will determine the name of the file on download, <b>not including the file extension.</b> You can put these following templates in the format, which will be replaced with the relevant information: <b>%name%</b> for the original file name, <b>%extension%</b> for the original file extension, and <b>%date%</b> for a date string of when the file was converted.",
|
||||
"placeholder": "VERT_%name%",
|
||||
"metadata": "File metadata",
|
||||
"metadata_description": "This changes whether any metadata (EXIF, song info, etc.) on the original file is preserved in converted files.",
|
||||
"keep": "Keep",
|
||||
"remove": "Remove",
|
||||
"quality": "Conversion quality",
|
||||
"quality_description": "This changes the default output quality of the converted files (in its category). Higher values may result in longer conversion times and file size.",
|
||||
"quality_video": "This changes the default output quality of the converted video files. Higher values may result in longer conversion times and file size.",
|
||||
|
|
|
@ -83,9 +83,9 @@ export class FFmpegConverter extends Converter {
|
|||
(async () => {
|
||||
const baseURL =
|
||||
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/esm";
|
||||
|
||||
|
||||
this.status = "downloading";
|
||||
|
||||
|
||||
await this.ffmpeg.load({
|
||||
coreURL: `${baseURL}/ffmpeg-core.js`,
|
||||
wasmURL: `${baseURL}/ffmpeg-core.wasm`,
|
||||
|
@ -289,8 +289,23 @@ export class FFmpegConverter extends Converter {
|
|||
const userSampleRate = Settings.instance.settings.ffmpegSampleRate;
|
||||
const customSampleRate =
|
||||
Settings.instance.settings.ffmpegCustomSampleRate ?? 44100;
|
||||
const keepMetadata = Settings.instance.settings.metadata;
|
||||
|
||||
let audioBitrateArgs: string[] = [];
|
||||
let sampleRateArgs: string[] = [];
|
||||
let metadataArgs: string[] = [];
|
||||
|
||||
log(["converters", this.name], `keep metadata: ${keepMetadata}`);
|
||||
if (!keepMetadata) {
|
||||
metadataArgs = [
|
||||
"-map_metadata", // remove metadata
|
||||
"-1",
|
||||
"-map_chapters", // remove chapters
|
||||
"-1",
|
||||
"-map", // remove cover art
|
||||
"a",
|
||||
];
|
||||
}
|
||||
|
||||
const isLosslessToLossy =
|
||||
lossless.includes(inputFormat) && !lossless.includes(outputFormat);
|
||||
|
@ -366,6 +381,7 @@ export class FFmpegConverter extends Converter {
|
|||
"input",
|
||||
"-map",
|
||||
"0:a:0",
|
||||
...metadataArgs,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
|
@ -379,7 +395,9 @@ export class FFmpegConverter extends Converter {
|
|||
`Converting audio ${input.from} to video ${to}`,
|
||||
);
|
||||
|
||||
const hasAlbumArt = await this.extractAlbumArt(ffmpeg);
|
||||
const hasAlbumArt = keepMetadata
|
||||
? await this.extractAlbumArt(ffmpeg)
|
||||
: false;
|
||||
const codecArgs = toArgs(to);
|
||||
|
||||
if (hasAlbumArt) {
|
||||
|
@ -402,6 +420,7 @@ export class FFmpegConverter extends Converter {
|
|||
"-r",
|
||||
"1",
|
||||
...codecArgs,
|
||||
...metadataArgs,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
|
@ -421,6 +440,7 @@ export class FFmpegConverter extends Converter {
|
|||
"-r",
|
||||
"1",
|
||||
...codecArgs,
|
||||
...metadataArgs,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
|
@ -439,6 +459,7 @@ export class FFmpegConverter extends Converter {
|
|||
"input",
|
||||
"-c:a",
|
||||
audioCodec,
|
||||
...metadataArgs,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
|
|
|
@ -139,6 +139,8 @@ export class MagickConverter extends Converter {
|
|||
}
|
||||
|
||||
// every other format handled by magick worker
|
||||
const keepMetadata: boolean = Settings.instance.settings.metadata ?? true;
|
||||
log(["converters", this.name], `keep metadata: ${keepMetadata}`);
|
||||
const msg = {
|
||||
type: "convert",
|
||||
input: {
|
||||
|
@ -149,6 +151,7 @@ export class MagickConverter extends Converter {
|
|||
},
|
||||
to,
|
||||
compression,
|
||||
keepMetadata,
|
||||
} as WorkerMessage;
|
||||
const res = await this.sendMessage(msg);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import FancyTextInput from "$lib/components/functional/FancyInput.svelte";
|
||||
import Panel from "$lib/components/visual/Panel.svelte";
|
||||
import { RefreshCwIcon } from "lucide-svelte";
|
||||
import { PauseIcon, PlayIcon, RefreshCwIcon } from "lucide-svelte";
|
||||
import type { ISettings } from "./index.svelte";
|
||||
import {
|
||||
CONVERSION_BITRATES,
|
||||
|
@ -12,6 +12,7 @@
|
|||
import { m } from "$lib/paraglide/messages";
|
||||
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
||||
import FancyInput from "$lib/components/functional/FancyInput.svelte";
|
||||
import { effects } from "$lib/store/index.svelte";
|
||||
|
||||
const { settings }: { settings: ISettings } = $props();
|
||||
</script>
|
||||
|
@ -43,6 +44,43 @@
|
|||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-base font-bold">
|
||||
{m["settings.conversion.metadata"]()}
|
||||
</p>
|
||||
<p class="text-sm text-muted font-normal italic">
|
||||
{m["settings.conversion.metadata_description"]()}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 w-full">
|
||||
<div class="flex gap-3 w-full">
|
||||
<button
|
||||
onclick={() => (settings.metadata = true)}
|
||||
class="btn {$effects
|
||||
? ''
|
||||
: '!scale-100'} {settings.metadata
|
||||
? 'selected'
|
||||
: ''} flex-1 p-4 rounded-lg text-black dynadark:text-white flex items-center justify-center"
|
||||
>
|
||||
<PlayIcon size="24" class="inline-block mr-2" />
|
||||
{m["settings.conversion.keep"]()}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onclick={() => (settings.metadata = false)}
|
||||
class="btn {$effects
|
||||
? ''
|
||||
: '!scale-100'} {settings.metadata
|
||||
? ''
|
||||
: 'selected'} flex-1 p-4 rounded-lg text-black dynadark:text-white flex items-center justify-center"
|
||||
>
|
||||
<PauseIcon size="24" class="inline-block mr-2" />
|
||||
{m["settings.conversion.remove"]()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-base font-bold">
|
||||
|
@ -100,7 +138,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-sm font-bold select-none"> </p>
|
||||
<p class="text-sm font-bold select-none">
|
||||
|
||||
</p>
|
||||
<FancyInput
|
||||
bind:value={
|
||||
settings.ffmpegCustomSampleRate as unknown as string
|
||||
|
|
|
@ -7,8 +7,10 @@ export { default as Conversion } from "./Conversion.svelte";
|
|||
export { default as Vertd } from "./Vertd.svelte";
|
||||
export { default as Privacy } from "./Privacy.svelte";
|
||||
|
||||
// TODO: clean up settings & button code (componetize)
|
||||
export interface ISettings {
|
||||
filenameFormat: string;
|
||||
metadata: boolean;
|
||||
plausible: boolean;
|
||||
vertdURL: string;
|
||||
vertdSpeed: ConversionSpeed; // videos
|
||||
|
@ -23,6 +25,7 @@ export class Settings {
|
|||
|
||||
public settings: ISettings = $state({
|
||||
filenameFormat: "VERT_%name%",
|
||||
metadata: true,
|
||||
plausible: true,
|
||||
vertdURL: PUB_VERTD_URL,
|
||||
vertdSpeed: "slow",
|
||||
|
|
|
@ -5,6 +5,7 @@ interface ConvertMessage {
|
|||
input: VertFile;
|
||||
to: string;
|
||||
compression: number | null;
|
||||
keepMetadata?: boolean;
|
||||
}
|
||||
|
||||
interface FinishedMessage {
|
||||
|
|
|
@ -26,6 +26,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
|||
switch (message.type) {
|
||||
case "convert": {
|
||||
const compression: number | undefined = message.compression;
|
||||
const keepMetadata: boolean = message.keepMetadata ?? true;
|
||||
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
||||
message.to = message.to.toLowerCase();
|
||||
if (message.to === ".jfif") message.to = ".jpeg";
|
||||
|
@ -69,6 +70,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
|||
const output = await magickConvert(
|
||||
img,
|
||||
message.to,
|
||||
keepMetadata,
|
||||
compression,
|
||||
);
|
||||
convertedImgs[i] = output;
|
||||
|
@ -111,6 +113,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
|||
}),
|
||||
),
|
||||
message.to,
|
||||
keepMetadata,
|
||||
compression,
|
||||
);
|
||||
files.push(
|
||||
|
@ -161,6 +164,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
|||
const converted = await magickConvert(
|
||||
img,
|
||||
message.to,
|
||||
keepMetadata,
|
||||
compression,
|
||||
);
|
||||
outputs.push(converted);
|
||||
|
@ -225,57 +229,11 @@ const handleMessage = async (message: any): Promise<any> => {
|
|||
}),
|
||||
);
|
||||
|
||||
// extract metadata
|
||||
let metadata: Map<string, string> | undefined;
|
||||
try {
|
||||
metadata = new Map();
|
||||
|
||||
const exifProfile = img.getProfile("exif");
|
||||
if (exifProfile) {
|
||||
metadata.set("exif:profile", "true");
|
||||
}
|
||||
|
||||
const iccProfile = img.getProfile("icc");
|
||||
if (iccProfile) {
|
||||
metadata.set("icc:profile", "true");
|
||||
}
|
||||
|
||||
const attributeNames = img.attributeNames;
|
||||
if (attributeNames && attributeNames.length > 0) {
|
||||
for (const attrName of attributeNames) {
|
||||
try {
|
||||
if (
|
||||
attrName.startsWith("exif:") ||
|
||||
attrName.startsWith("icc:") ||
|
||||
attrName.startsWith("date:") ||
|
||||
attrName.startsWith("tiff:") ||
|
||||
attrName.startsWith("xmp:") ||
|
||||
attrName.startsWith("iptc:")
|
||||
) {
|
||||
const value = img.getAttribute(attrName);
|
||||
if (value) {
|
||||
metadata.set(attrName, value);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Parsed ${metadata.size} metadata values`);
|
||||
|
||||
if (metadata.size === 0) metadata = undefined;
|
||||
} catch (e) {
|
||||
console.warn("Failed to extract metadata:", e);
|
||||
metadata = undefined;
|
||||
}
|
||||
|
||||
const converted = await magickConvert(
|
||||
img,
|
||||
message.to,
|
||||
keepMetadata,
|
||||
compression,
|
||||
metadata,
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -305,8 +263,8 @@ const readToEnd = async (reader: ReadableStreamDefaultReader<Uint8Array>) => {
|
|||
const magickConvert = async (
|
||||
img: IMagickImage,
|
||||
to: string,
|
||||
keepMetadata: boolean,
|
||||
compression?: number,
|
||||
originalMetadata?: Map<string, string>,
|
||||
) => {
|
||||
let fmt = to.slice(1).toUpperCase();
|
||||
if (fmt === "JFIF") fmt = "JPEG";
|
||||
|
@ -314,16 +272,7 @@ const magickConvert = async (
|
|||
const result = await new Promise<Uint8Array>((resolve) => {
|
||||
// magick-wasm automatically clamps (https://github.com/dlemstra/magick-wasm/blob/76fc6f2b0c0497d2ddc251bbf6174b4dc92ac3ea/src/magick-image.ts#L2480)
|
||||
if (compression) img.quality = compression;
|
||||
|
||||
if (originalMetadata) {
|
||||
originalMetadata.forEach((value, key) => {
|
||||
try {
|
||||
if (!key.endsWith(":profile")) img.setAttribute(key, value);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to set metadata ${key}: ${e}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!keepMetadata) img.strip();
|
||||
|
||||
img.write(fmt as unknown as MagickFormat, (o: Uint8Array) => {
|
||||
resolve(structuredClone(o));
|
||||
|
|
Loading…
Reference in New Issue