feat: improve metadata stripping

This commit is contained in:
niraj-khatiwada 2026-03-25 15:38:46 +05:45
parent 885c8b74d1
commit abc0ad72f4
24 changed files with 303 additions and 209 deletions

View file

@ -143,16 +143,17 @@ pub struct VideoCompressionConfig {
pub fps: Option<String>,
pub video_codec: Option<String>,
pub transform_history: Option<MediaTransformHistory>,
pub metadata_config: Option<VideoMetadataConfig>,
pub strip_metadata: Option<bool>,
pub metadata_config: Option<MediaMetadataConfig>,
pub custom_thumbnail_path: Option<String>,
pub should_enable_custom_thumbnail: Option<bool>,
pub trim_segments: Option<Vec<VideoTrimSegment>>,
pub subtitles_config: Option<SubtitlesConfig>,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct VideoMetadataConfig {
pub struct MediaMetadataConfig {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,

View file

@ -1,8 +1,8 @@
use crate::core::domain::{
AudioConfig, BatchCompressionResult, BatchVideoCompressionProgress,
BatchVideoIndividualCompressionResult, CustomEvents, MediaTransform, MediaTransformCrop,
MediaTransformHistory, SubtitlesConfig, VideoCompressionConfig, VideoCompressionProgress,
VideoCompressionResult, VideoMetadataConfig, VideoThumbnail, VideoTrimSegment,
BatchVideoIndividualCompressionResult, CustomEvents, MediaMetadataConfig, MediaTransform,
MediaTransformCrop, MediaTransformHistory, SubtitlesConfig, VideoCompressionConfig,
VideoCompressionProgress, VideoCompressionResult, VideoThumbnail, VideoTrimSegment,
};
use crate::core::ffprobe::FFPROBE;
use crate::core::image::ImageCompressor;
@ -62,7 +62,8 @@ impl FFMPEG {
fps: Option<&str>,
video_codec: Option<&str>,
transform_history: Option<&MediaTransformHistory>,
metadata_config: Option<&VideoMetadataConfig>,
strip_metadata: Option<bool>,
metadata_config: Option<&MediaMetadataConfig>,
custom_thumbnail_path: Option<&str>,
trim_segments: Option<&Vec<VideoTrimSegment>>,
subtitles_config: Option<&SubtitlesConfig>,
@ -158,8 +159,12 @@ impl FFMPEG {
Vec::new()
};
// Preserve existing metadata
cmd_args.extend_from_slice(&["-map_metadata", "0"]);
let should_strip_metadata = strip_metadata.unwrap_or(false);
if should_strip_metadata {
cmd_args.extend_from_slice(&["-map_metadata", "-1"]);
} else {
cmd_args.extend_from_slice(&["-map_metadata", "0"]);
}
cmd_args.extend_from_slice(&[
"-hide_banner",
@ -503,7 +508,7 @@ impl FFMPEG {
let mut metadata_args: Vec<String> = Vec::new();
if !is_gif_target {
if !is_gif_target && !should_strip_metadata {
if let Some(metadata) = metadata_config {
if let Some(ref title) = metadata.title {
metadata_args.push("-metadata".to_string());
@ -771,6 +776,7 @@ impl FFMPEG {
let thumbnail_path = video_options.custom_thumbnail_path.as_deref();
let trim_segments = video_options.trim_segments.as_ref();
let subtitles_config = video_options.subtitles_config.as_ref();
let strip_metadata = video_options.strip_metadata;
match ffmpeg_instance
.compress_video(
@ -785,6 +791,7 @@ impl FFMPEG {
fps,
video_codec,
transform_history,
strip_metadata,
metadata_config,
thumbnail_path,
trim_segments,

View file

@ -129,7 +129,6 @@ impl ImageCompressor {
let transform_input_path = if !original_extension.eq("svg")
&& (dimensions.is_some() || transform_history.is_some())
{
log::info!("[image] Applying transformations before compression");
let transform_temp_filename =
format!("{}_transformed.{}", image_id, original_extension);
let transform_temp_path: PathBuf = [

View file

@ -1,7 +1,7 @@
use crate::core::{
domain::{
AudioConfig, BatchCompressionResult, MediaTransformHistory, SubtitlesConfig,
VideoCompressionConfig, VideoCompressionResult, VideoMetadataConfig, VideoThumbnail,
AudioConfig, BatchCompressionResult, MediaMetadataConfig, MediaTransformHistory,
SubtitlesConfig, VideoCompressionConfig, VideoCompressionResult, VideoThumbnail,
VideoTrimSegment,
},
ffmpeg::{self},
@ -22,10 +22,11 @@ pub async fn compress_video(
fps: Option<&str>,
video_codec: Option<&str>,
transform_history: Option<MediaTransformHistory>,
metadata_config: Option<VideoMetadataConfig>,
metadata_config: Option<MediaMetadataConfig>,
custom_thumbnail_path: Option<&str>,
trim_segments: Option<Vec<VideoTrimSegment>>,
subtitles_config: Option<SubtitlesConfig>,
strip_metadata: Option<bool>,
) -> Result<VideoCompressionResult, String> {
let mut ffmpeg = ffmpeg::FFMPEG::new(&app)?;
if let Ok(files) =
@ -49,6 +50,7 @@ pub async fn compress_video(
fps,
video_codec,
transform_history.as_ref(),
strip_metadata,
metadata_config.as_ref(),
custom_thumbnail_path,
trim_segments.as_ref(),

View file

@ -67,6 +67,7 @@ pub async fn compress_media_batch(
let thumbnail_path = video_config.custom_thumbnail_path.as_deref();
let trim_segments = video_config.trim_segments.as_ref();
let subtitles_config = video_config.subtitles_config.as_ref();
let strip_metadata = video_config.strip_metadata;
let mut ffmpeg_instance = FFMPEG::new(&app)
.map_err(|e| format!("Failed to create ffmpeg instance: {}", e))?;
@ -84,6 +85,7 @@ pub async fn compress_media_batch(
fps,
video_codec,
transform_history,
strip_metadata,
metadata_config,
thumbnail_path,
trim_segments,

View file

@ -5,11 +5,11 @@ import { proxy } from 'valtio'
import {
App,
ImageConfig,
MediaMetadataConfig,
VideoConfig,
VideoMetadataConfig,
} from '../../types/app'
export const videoMetadataConfigInitialState: VideoMetadataConfig = {
export const videoMetadataConfigInitialState: MediaMetadataConfig = {
title: '',
album: '',
artist: '',
@ -41,7 +41,7 @@ export const videoConfigInitialState: VideoConfig = {
shouldEnableAudioTrackSelection: false,
quality: 50,
shouldEnableQuality: false,
shouldPreserveMetadata: false,
shouldStripMetadata: true,
metadataConfig: null,
customThumbnailPath: null,
shouldEnableCustomThumbnail: false,
@ -69,7 +69,7 @@ export const imageConfigInitialState: ImageConfig = {
convertToExtension: '-',
isLossless: false,
quality: 50,
stripMetadata: true,
shouldStripMetadata: true,
svgScaleFactor: 4,
shouldEnableAdvancedSvgSetting: false,
svgConfig: svgSettingInitialState,

View file

@ -7,7 +7,7 @@ import { snapshot, useSnapshot } from 'valtio'
import Button from '@/components/Button'
import { compressMediaBatch } from '@/tauri/commands/media'
import { VideoMetadataConfig } from '@/types/app'
import { MediaMetadataConfig } from '@/types/app'
import {
CustomEvents,
ImageCompressionConfig,
@ -150,11 +150,11 @@ function StartCompression() {
? ((v.config.transformVideoConfig?.transformHistory ??
[]) as MediaTransformHistory[])
: null,
stripMetadata: v.config?.shouldStripMetadata,
metadataConfig:
!v.config?.shouldPreserveMetadata &&
v.config?.metadataConfig
!v.config?.shouldStripMetadata && v.config?.metadataConfig
? Object.entries(
v.config?.metadataConfig as VideoMetadataConfig,
v.config?.metadataConfig as MediaMetadataConfig,
).reduce(
(a, [key, value]: [string, any]) => {
a[key] = value?.length > 0 ? value : null
@ -215,7 +215,7 @@ function StartCompression() {
quality: v.config.isLossless
? 100
: (v.config.quality ?? 100),
stripMetadata: v.config.stripMetadata,
stripMetadata: v.config.shouldStripMetadata,
svgScaleFactor: v.config.svgScaleFactor ?? null,
svgConfig: v.config?.shouldEnableAdvancedSvgSetting
? (v.config.svgConfig ?? null)

View file

@ -1,20 +1,33 @@
import { Divider } from '@heroui/react'
import { Divider, Tab } from '@heroui/react'
import { useState } from 'react'
import { useSnapshot } from 'valtio'
import Tabs from '@/components/Tabs'
import { appProxy } from '@/routes/(root)/-state'
import { ImageExtension as ImageExtensionType } from '@/types/compression'
import CompressionQuality from './CompressionQuality'
import ImageDimensions from './ImageDimensions'
import ImageExtension from './ImageExtension'
import ImageMetadata from './ImageMetadata'
import SvgConfig from './SvgConfig'
import SvgScaleFactor from './SvgScaleFactor'
import TransformImage from './TransformImage'
import CompressionQuality from './image/CompressionQuality'
import ImageDimensions from './image/Dimensions'
import ImageExtension from './image/Extension'
import SvgConfig from './image/SvgConfig'
import SvgScaleFactor from './image/SvgScaleFactor'
import TransformImage from './image/TransformImage'
import Others from './others/-index'
type ImageSettingsProps = {
mediaIndex: number
}
const TABS = {
image: {
id: 'image',
title: 'Image',
},
others: {
id: 'others',
title: 'Others',
},
} as const
function ImageSettings({ mediaIndex }: ImageSettingsProps) {
const {
state: { media, commonConfigForBatchCompression },
@ -27,59 +40,85 @@ function ImageSettings({ mediaIndex }: ImageSettingsProps) {
const { convertToExtension } =
config ?? commonConfigForBatchCompression.imageConfig ?? {}
const [tab, setTab] = useState<keyof typeof TABS>('image')
return (
<div className="space-y-3 my-3">
<div>
<CompressionQuality mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
<>
<section>
<Tabs
aria-label="Compression Settings"
size="sm"
selectedKey={tab}
onSelectionChange={(t) => setTab(t as keyof typeof TABS)}
className="w-full"
fullWidth
classNames={{
tab: 'h-6',
tabContent: 'text-[11px]',
}}
>
{Object.values(TABS).map((t) => (
<Tab key={t.id} value={t.id} title={t.title} />
))}
</Tabs>
<div className="my-4">
{tab === 'image' ? (
<div>
<div>
<CompressionQuality mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
{(['svg'] as ImageExtensionType[]).includes(
image?.extension as ImageExtensionType,
) &&
(['png', 'jpg', 'jpeg', 'webp'] as ImageExtensionType[]).includes(
convertToExtension as ImageExtensionType,
) ? (
<div>
<SvgScaleFactor mediaIndex={mediaIndex} />
<Divider className="my-3 !mt-6" />
{(['svg'] as ImageExtensionType[]).includes(
image?.extension as ImageExtensionType,
) &&
(['png', 'jpg', 'jpeg', 'webp'] as ImageExtensionType[]).includes(
convertToExtension as ImageExtensionType,
) ? (
<div>
<SvgScaleFactor mediaIndex={mediaIndex} />
<Divider className="my-3 !mt-6" />
</div>
) : null}
{(
['png', 'jpg', 'jpeg', 'webp', 'gif'] as ImageExtensionType[]
).includes(imageExtension as ImageExtensionType) &&
!(['svg'] as ImageExtensionType[]).includes(
convertToExtension as ImageExtensionType,
) ? (
<>
<div>
<ImageDimensions mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
<div>
<TransformImage mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
</>
) : null}
{convertToExtension === 'svg' ? (
<div>
<SvgConfig mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
) : null}
<div className="!mt-8">
<ImageExtension mediaIndex={mediaIndex} />
</div>
</div>
) : null}
{tab === 'others' ? (
<div>
<Others mediaIndex={mediaIndex} />
</div>
) : null}
</div>
) : null}
{(['png', 'jpg', 'jpeg', 'webp', 'gif'] as ImageExtensionType[]).includes(
imageExtension as ImageExtensionType,
) &&
!(['svg'] as ImageExtensionType[]).includes(
convertToExtension as ImageExtensionType,
) ? (
<>
<div>
<ImageDimensions mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
<div>
<TransformImage mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
</>
) : null}
<div>
<ImageMetadata mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
{convertToExtension === 'svg' ? (
<div>
<SvgConfig mediaIndex={mediaIndex} />
<Divider className="my-3" />
</div>
) : null}
<div className="!mt-8">
<ImageExtension mediaIndex={mediaIndex} />
</div>
</div>
</section>
</>
)
}

View file

@ -1,75 +0,0 @@
import { useCallback } from 'react'
import { useSnapshot } from 'valtio'
import Switch from '@/components/Switch'
import { ImageExtension } from '@/types/compression'
import { appProxy, normalizeBatchMediaConfig } from '../../../-state'
type ImageMetadataProps = {
mediaIndex: number
}
const ImageMetadata = ({ mediaIndex }: ImageMetadataProps) => {
const {
state: {
isCompressing,
isProcessCompleted,
media,
commonConfigForBatchCompression,
},
} = useSnapshot(appProxy)
const image =
media.length > 0 && mediaIndex >= 0 && media[mediaIndex].type == 'image'
? media[mediaIndex]
: null
const { config } = image ?? {}
const { stripMetadata, convertToExtension } =
config ?? commonConfigForBatchCompression.imageConfig ?? {}
const handleValueChange = useCallback(
(preserveMetadata: boolean) => {
const shouldStripMetadata = !preserveMetadata
if (
mediaIndex >= 0 &&
appProxy.state.media[mediaIndex].type === 'image' &&
appProxy.state.media[mediaIndex]?.config
) {
appProxy.state.media[mediaIndex].config.stripMetadata =
shouldStripMetadata
appProxy.state.media[mediaIndex].isConfigDirty = true
} else {
if (appProxy.state.media.length > 1) {
appProxy.state.commonConfigForBatchCompression.imageConfig.stripMetadata =
shouldStripMetadata
normalizeBatchMediaConfig()
}
}
},
[mediaIndex],
)
const shouldDisableInput =
media.length === 0 ||
isCompressing ||
isProcessCompleted ||
(['gif', 'svg', 'webp'] as ImageExtension[]).includes(
image?.extension as ImageExtension,
) ||
(['svg', 'webp'] as ImageExtension[]).includes(
convertToExtension as ImageExtension,
)
return (
<Switch
isSelected={!stripMetadata}
onValueChange={handleValueChange}
isDisabled={shouldDisableInput}
>
<p className="text-gray-600 dark:text-gray-400 text-sm mr-2 w-full">
Preserve Metadata
</p>
</Switch>
)
}
export default ImageMetadata

View file

@ -6,12 +6,12 @@ import { useSnapshot } from 'valtio'
import Slider from '@/components/Slider'
import Switch from '@/components/Switch'
import { useSyncState } from '@/hooks/useSyncState'
import { slideDownTransition } from '@/utils/animation'
import {
appProxy,
imageConfigInitialState,
normalizeBatchMediaConfig,
} from '../../../-state'
} from '@/routes/(root)/-state'
import { slideDownTransition } from '@/utils/animation'
type CompressionQualityProps = {
mediaIndex: number

View file

@ -9,11 +9,11 @@ import Switch from '@/components/Switch'
import { appProxy } from '@/routes/(root)/-state'
import { slideDownTransition } from '@/utils/animation'
type ImageDimensionsProps = {
type DimensionsProps = {
mediaIndex: number
}
function ImageDimensions({ mediaIndex }: ImageDimensionsProps) {
function Dimensions({ mediaIndex }: DimensionsProps) {
if (mediaIndex < 0) return
const {
@ -131,7 +131,7 @@ function ImageDimensions({ mediaIndex }: ImageDimensionsProps) {
return
}
const targetImage = appProxy.state.media[mediaIndex]
const targetImageDimensions = targetImage.config?.shouldTransformImage
const targetDimensions = targetImage.config?.shouldTransformImage
? {
width:
targetImage?.config?.transformImageConfig?.transforms?.crop
@ -142,14 +142,13 @@ function ImageDimensions({ mediaIndex }: ImageDimensionsProps) {
}
: targetImage?.dimensions
if (
targetImageDimensions == null ||
Number.isNaN(targetImageDimensions?.width) ||
Number.isNaN(targetImageDimensions?.height)
targetDimensions == null ||
Number.isNaN(targetDimensions?.width) ||
Number.isNaN(targetDimensions?.height)
) {
return null
}
const aspectRatio =
targetImageDimensions.width! / targetImageDimensions.height!
const aspectRatio = targetDimensions.width! / targetDimensions.height!
const _dimensions: [number, number] =
type === 'width'
? [value, Math.round(value / aspectRatio)]
@ -248,4 +247,4 @@ function ImageDimensions({ mediaIndex }: ImageDimensionsProps) {
)
}
export default React.memo(ImageDimensions)
export default React.memo(Dimensions)

View file

@ -6,12 +6,12 @@ import Select from '@/components/Select'
import { appProxy, normalizeBatchMediaConfig } from '@/routes/(root)/-state'
import { extensions } from '@/types/compression'
type ImageExtensionProps = {
type ExtensionProps = {
mediaIndex: number
disabled?: boolean
}
const ImageExtension = ({ mediaIndex, disabled }: ImageExtensionProps) => {
const Extension = ({ mediaIndex, disabled }: ExtensionProps) => {
const {
state: {
media,
@ -90,4 +90,4 @@ const ImageExtension = ({ mediaIndex, disabled }: ImageExtensionProps) => {
)
}
export default ImageExtension
export default Extension

View file

@ -8,12 +8,12 @@ import Slider from '@/components/Slider'
import Switch from '@/components/Switch'
import Tooltip from '@/components/Tooltip'
import { useSyncState } from '@/hooks/useSyncState'
import { slideDownTransition } from '@/utils/animation'
import {
appProxy,
normalizeBatchMediaConfig,
svgSettingInitialState,
} from '../../../-state'
} from '@/routes/(root)/-state'
import { slideDownTransition } from '@/utils/animation'
type SvgConfigProps = {
mediaIndex: number

View file

@ -6,11 +6,11 @@ import Icon from '@/components/Icon'
import Switch from '@/components/Switch'
import { appProxy } from '@/routes/(root)/-state'
type TransformImageProps = {
type TransformProps = {
mediaIndex: number
}
function TransformImage({ mediaIndex }: TransformImageProps) {
function Transform({ mediaIndex }: TransformProps) {
if (mediaIndex < 0) return
const {
@ -98,4 +98,4 @@ function TransformImage({ mediaIndex }: TransformImageProps) {
)
}
export default TransformImage
export default Transform

View file

@ -0,0 +1,15 @@
import Metadata from './Metadata'
type OthersProps = {
mediaIndex: number
}
function Others({ mediaIndex }: OthersProps) {
return (
<div>
<Metadata mediaIndex={mediaIndex} />
</div>
)
}
export default Others

View file

@ -0,0 +1,103 @@
import cloneDeep from 'lodash/cloneDeep'
import { useCallback } from 'react'
import { useSnapshot } from 'valtio'
import Switch from '@/components/Switch'
import {
appProxy,
normalizeBatchMediaConfig,
videoMetadataConfigInitialState,
} from '@/routes/(root)/-state'
import { ImageExtension } from '@/types/compression'
type MetadataProps = {
mediaIndex: number
}
function Metadata({ mediaIndex }: MetadataProps) {
const {
state: {
isCompressing,
isProcessCompleted,
media,
commonConfigForBatchCompression,
isLoadingMediaFiles,
},
} = useSnapshot(appProxy)
const image =
media.length > 0 && mediaIndex >= 0 && media[mediaIndex].type == 'image'
? media[mediaIndex]
: null
const { config } = image ?? {}
const { shouldStripMetadata, convertToExtension } =
config ?? commonConfigForBatchCompression.imageConfig ?? {}
const handleStripMetadataToggle = useCallback(() => {
if (
mediaIndex >= 0 &&
appProxy.state.media[mediaIndex].type === 'image' &&
appProxy.state.media[mediaIndex]?.config
) {
appProxy.state.media[mediaIndex].config.shouldStripMetadata =
!appProxy.state.media[mediaIndex].config.shouldStripMetadata
appProxy.state.media[mediaIndex].isConfigDirty = true
if (appProxy.state.media[mediaIndex].config.shouldStripMetadata) {
appProxy.state.media[mediaIndex].config.metadataConfig = null
} else {
appProxy.state.media[mediaIndex].config.metadataConfig = cloneDeep(
videoMetadataConfigInitialState,
)
}
} else {
if (appProxy.state.media.length > 1) {
appProxy.state.commonConfigForBatchCompression.imageConfig.shouldStripMetadata =
!appProxy.state.commonConfigForBatchCompression.imageConfig
.shouldStripMetadata
if (
appProxy.state.commonConfigForBatchCompression.imageConfig
.shouldStripMetadata
) {
appProxy.state.commonConfigForBatchCompression.imageConfig.metadataConfig =
null
} else {
appProxy.state.commonConfigForBatchCompression.imageConfig.metadataConfig =
cloneDeep(videoMetadataConfigInitialState)
}
normalizeBatchMediaConfig()
}
}
}, [mediaIndex])
const shouldDisableInput =
media.length === 0 ||
isCompressing ||
isProcessCompleted ||
isLoadingMediaFiles ||
(['gif', 'svg', 'webp'] as ImageExtension[]).includes(
image?.extension as ImageExtension,
) ||
(['svg', 'webp'] as ImageExtension[]).includes(
convertToExtension as ImageExtension,
)
return (
<>
<Switch
isSelected={shouldStripMetadata}
onValueChange={handleStripMetadataToggle}
isDisabled={shouldDisableInput}
>
<div className="flex justify-center items-center">
<span className="text-gray-600 dark:text-gray-400 block mr-2 text-sm">
Strip Metadata
</span>
</div>
</Switch>
</>
)
}
export default Metadata

View file

@ -4,7 +4,6 @@ import { useSnapshot } from 'valtio'
import Tabs from '@/components/Tabs'
import { getAudioStreams } from '@/tauri/commands/ffprobe'
import { appProxy } from '../../../-state'
import AudioBitrate from './audio/AudioBitrate'
import AudioChannels from './audio/AudioChannels'
import AudioCodec from './audio/AudioCodec'
@ -14,12 +13,13 @@ import Others from './others/-index'
import CompressionPreset from './video/CompressionPreset'
import CompressionQuality from './video/CompressionQuality'
import CustomThumbnail from './video/CustomThumbnail'
import VideoDimensions from './video/Dimensions'
import VideoExtension from './video/Extension'
import VideoFPS from './video/FPS'
import TransformVideo from './video/TransformVideo'
import TrimVideo from './video/TrimVideo'
import VideoCodec from './video/VideoCodec'
import VideoDimensions from './video/VideoDimensions'
import VideoExtension from './video/VideoExtension'
import VideoFPS from './video/VideoFPS'
import { appProxy } from '../../../-state'
type VideoSettingsProps = {
mediaIndex: number // if mediaIndex < 0, we'll only show settings that applies to all videos

View file

@ -9,7 +9,7 @@ import Divider from '@/components/Divider'
import Switch from '@/components/Switch'
import TextArea from '@/components/TextArea'
import TextInput from '@/components/TextInput'
import type { VideoMetadataConfig } from '@/types/app'
import type { MediaMetadataConfig } from '@/types/app'
import { slideDownTransition } from '@/utils/animation'
import {
appProxy,
@ -36,12 +36,12 @@ function Metadata({ mediaIndex }: MetadataProps) {
? media[mediaIndex]
: null
const { config } = video ?? {}
const { shouldPreserveMetadata, metadataConfig, convertToExtension } =
const { shouldStripMetadata, metadataConfig, convertToExtension } =
config ?? commonConfigForBatchCompression.videoConfig ?? {}
const updateMetadataField = useCallback(
(
field: keyof VideoMetadataConfig,
field: keyof MediaMetadataConfig,
value: string | boolean | null | undefined,
) => {
if (
@ -98,17 +98,17 @@ function Metadata({ mediaIndex }: MetadataProps) {
[mediaIndex],
)
const handlePreserveMetadataToggle = useCallback(() => {
const handleStripMetadataToggle = useCallback(() => {
if (
mediaIndex >= 0 &&
appProxy.state.media[mediaIndex].type === 'video' &&
appProxy.state.media[mediaIndex]?.config
) {
appProxy.state.media[mediaIndex].config.shouldPreserveMetadata =
!appProxy.state.media[mediaIndex].config.shouldPreserveMetadata
appProxy.state.media[mediaIndex].config.shouldStripMetadata =
!appProxy.state.media[mediaIndex].config.shouldStripMetadata
appProxy.state.media[mediaIndex].isConfigDirty = true
if (appProxy.state.media[mediaIndex].config.shouldPreserveMetadata) {
if (appProxy.state.media[mediaIndex].config.shouldStripMetadata) {
appProxy.state.media[mediaIndex].config.metadataConfig = null
} else {
appProxy.state.media[mediaIndex].config.metadataConfig = cloneDeep(
@ -117,13 +117,13 @@ function Metadata({ mediaIndex }: MetadataProps) {
}
} else {
if (appProxy.state.media.length > 1) {
appProxy.state.commonConfigForBatchCompression.videoConfig.shouldPreserveMetadata =
appProxy.state.commonConfigForBatchCompression.videoConfig.shouldStripMetadata =
!appProxy.state.commonConfigForBatchCompression.videoConfig
.shouldPreserveMetadata
.shouldStripMetadata
if (
appProxy.state.commonConfigForBatchCompression.videoConfig
.shouldPreserveMetadata
.shouldStripMetadata
) {
appProxy.state.commonConfigForBatchCompression.videoConfig.metadataConfig =
null
@ -147,18 +147,18 @@ function Metadata({ mediaIndex }: MetadataProps) {
return (
<>
<Switch
isSelected={shouldPreserveMetadata}
onValueChange={handlePreserveMetadataToggle}
isSelected={shouldStripMetadata}
onValueChange={handleStripMetadataToggle}
isDisabled={shouldDisableInput}
>
<div className="flex justify-center items-center">
<span className="text-gray-600 dark:text-gray-400 block mr-2 text-sm">
Preserve Metadata
Strip Metadata
</span>
</div>
</Switch>
<AnimatePresence mode="wait">
{!shouldPreserveMetadata ? (
{!shouldStripMetadata ? (
<Card className="px-2 my-2 pb-4 shadow-none border-1 dark:border-none">
<motion.div {...slideDownTransition} className="space-y-4 mt-2">
<div className="text-zinc-700 dark:text-zinc-400">

View file

@ -13,7 +13,7 @@ type VideoDimensionsProps = {
mediaIndex: number
}
function VideoDimensions({ mediaIndex }: VideoDimensionsProps) {
function Dimensions({ mediaIndex }: VideoDimensionsProps) {
if (mediaIndex < 0) return
const {
@ -248,4 +248,4 @@ function VideoDimensions({ mediaIndex }: VideoDimensionsProps) {
)
}
export default React.memo(VideoDimensions)
export default React.memo(Dimensions)

View file

@ -12,7 +12,7 @@ type VideoExtensionProps = {
mediaIndex: number
}
function VideoExtension({ mediaIndex }: VideoExtensionProps) {
function Extension({ mediaIndex }: VideoExtensionProps) {
const {
state: {
media,
@ -99,4 +99,4 @@ function VideoExtension({ mediaIndex }: VideoExtensionProps) {
)
}
export default VideoExtension
export default Extension

View file

@ -9,13 +9,13 @@ import Switch from '@/components/Switch'
import { slideDownTransition } from '@/utils/animation'
import { appProxy, normalizeBatchMediaConfig } from '../../../../-state'
const FPS = [24, 25, 30, 50, 60] as const
const fps = [24, 25, 30, 50, 60] as const
type VideoFPSProps = {
mediaIndex: number
}
function VideoFPS({ mediaIndex }: VideoFPSProps) {
function FPS({ mediaIndex }: VideoFPSProps) {
const {
state: {
media,
@ -29,7 +29,7 @@ function VideoFPS({ mediaIndex }: VideoFPSProps) {
media.length > 0 && mediaIndex >= 0 && media[mediaIndex].type === 'video'
? media[mediaIndex]
: null
const { config, fps } = video ?? {}
const { config, fps: videoFps } = video ?? {}
const { shouldEnableCustomFPS, customFPS, convertToExtension } =
config ?? commonConfigForBatchCompression.videoConfig ?? {}
@ -79,7 +79,7 @@ function VideoFPS({ mediaIndex }: VideoFPSProps) {
isProcessCompleted ||
isLoadingMediaFiles
const initialFpsValue = customFPS ?? fps ?? 30
const initialFpsValue = customFPS ?? videoFps ?? 30
return (
<>
@ -135,7 +135,7 @@ function VideoFPS({ mediaIndex }: VideoFPSProps) {
label: '!text-gray-600 dark:!text-gray-400 text-xs',
}}
>
{FPS?.map((f) => (
{fps?.map((f) => (
<SelectItem
key={String(f)}
textValue={String(f)}
@ -153,4 +153,4 @@ function VideoFPS({ mediaIndex }: VideoFPSProps) {
)
}
export default VideoFPS
export default FPS

View file

@ -15,7 +15,7 @@ import {
VideoStream,
} from '@/types/compression'
export type VideoMetadataConfig = {
export type MediaMetadataConfig = {
title?: string | null
artist?: string | null
album?: string | null
@ -73,8 +73,8 @@ export type VideoConfig = {
shouldTrimVideo?: boolean
trimConfig?: TimelineAction[]
isVideoTrimEditMode?: boolean
shouldPreserveMetadata?: boolean
metadataConfig?: VideoMetadataConfig | null
shouldStripMetadata?: boolean
metadataConfig?: MediaMetadataConfig | null
customThumbnailPath?: string | null
shouldEnableCustomThumbnail?: boolean
shouldEnableCustomChannel?: boolean
@ -133,7 +133,8 @@ export type ImageConfig = {
convertToExtension: keyof typeof extensions.image | '-'
isLossless: boolean
quality: number
stripMetadata: boolean
shouldStripMetadata: boolean
metadataConfig?: MediaMetadataConfig | null
svgScaleFactor?: number
svgConfig?: SvgConfig
shouldEnableAdvancedSvgSetting?: boolean

View file

@ -1,4 +1,4 @@
import { AudioConfig, SubtitlesConfig, VideoMetadataConfig } from './app'
import { AudioConfig, MediaMetadataConfig, SubtitlesConfig } from './app'
import { FileMetadata } from './fs'
export const extensions = {
@ -218,7 +218,8 @@ export type VideoCompressionConfig = {
fps?: string | null
videoCodec?: string | null
transformHistory?: MediaTransformHistory[] | null
metadataConfig?: VideoMetadataConfig | null
stripMetadata?: boolean
metadataConfig?: MediaMetadataConfig | null
customThumbnailPath?: string | null
trimSegments?: VideoTrimSegment[] | null
subtitlesConfig?: SubtitlesConfig | null