From 8e68f023d4c6e21e8644f445c6bf441771ddda90 Mon Sep 17 00:00:00 2001 From: Maya Date: Sun, 7 Sep 2025 06:20:30 +0300 Subject: [PATCH] feat: metadata setting need to update vertd next optimizes metadata code too (oops) --- messages/en.json | 6 +- src/lib/converters/ffmpeg.svelte.ts | 27 ++++++++- src/lib/converters/magick.svelte.ts | 3 + src/lib/sections/settings/Conversion.svelte | 44 +++++++++++++- src/lib/sections/settings/index.svelte.ts | 3 + src/lib/types/conversion-worker.ts | 1 + src/lib/workers/magick.ts | 65 +++------------------ 7 files changed, 85 insertions(+), 64 deletions(-) diff --git a/messages/en.json b/messages/en.json index 04c4f6d..fb43805 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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, not including the file extension. You can put these following templates in the format, which will be replaced with the relevant information: %name% for the original file name, %extension% for the original file extension, and %date% 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.", diff --git a/src/lib/converters/ffmpeg.svelte.ts b/src/lib/converters/ffmpeg.svelte.ts index 7d16a15..b1a38ce 100644 --- a/src/lib/converters/ffmpeg.svelte.ts +++ b/src/lib/converters/ffmpeg.svelte.ts @@ -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, diff --git a/src/lib/converters/magick.svelte.ts b/src/lib/converters/magick.svelte.ts index 244328e..1bc3460 100644 --- a/src/lib/converters/magick.svelte.ts +++ b/src/lib/converters/magick.svelte.ts @@ -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); diff --git a/src/lib/sections/settings/Conversion.svelte b/src/lib/sections/settings/Conversion.svelte index d6391dc..258fbc1 100644 --- a/src/lib/sections/settings/Conversion.svelte +++ b/src/lib/sections/settings/Conversion.svelte @@ -1,7 +1,7 @@ @@ -43,6 +44,43 @@ type="text" /> +
+
+

+ {m["settings.conversion.metadata"]()} +

+

+ {m["settings.conversion.metadata_description"]()} +

+
+
+
+ + + +
+
+

@@ -100,7 +138,9 @@ />

-

  

+

+    +

=> { 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 => { const output = await magickConvert( img, message.to, + keepMetadata, compression, ); convertedImgs[i] = output; @@ -111,6 +113,7 @@ const handleMessage = async (message: any): Promise => { }), ), message.to, + keepMetadata, compression, ); files.push( @@ -161,6 +164,7 @@ const handleMessage = async (message: any): Promise => { const converted = await magickConvert( img, message.to, + keepMetadata, compression, ); outputs.push(converted); @@ -225,57 +229,11 @@ const handleMessage = async (message: any): Promise => { }), ); - // extract metadata - let metadata: Map | 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) => { const magickConvert = async ( img: IMagickImage, to: string, + keepMetadata: boolean, compression?: number, - originalMetadata?: Map, ) => { let fmt = to.slice(1).toUpperCase(); if (fmt === "JFIF") fmt = "JPEG"; @@ -314,16 +272,7 @@ const magickConvert = async ( const result = await new Promise((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));