Skip to content

Commit 1f393d9

Browse files
committedAug 25, 2024·
working on audio
1 parent b083a29 commit 1f393d9

18 files changed

+344
-175
lines changed
 

‎.prettierignore

-1
This file was deleted.

‎README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ Roadmap:
2727
2. audio converter
2828
3. image converter
2929

30-
![homepage](./homepage.png)
31-
![homepage2](./homepage2.png)
30+
![homepage](.assets//homepage.png)
31+
![homepage2](.assets/homepage2.png)
3232

3333
techs:
3434

‎homepage.png ‎assets/homepage.png

File renamed without changes.

‎homepage2.png ‎assets/homepage2.png

File renamed without changes.

‎package-lock.json

-65
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@
9999
"vite-plugin-mkcert": "^1.17.3",
100100
"vite-plugin-qrcode": "^0.2.3",
101101
"vite-plugin-remove-console": "^2.2.0",
102-
"vite-plugin-static-copy": "^1.0.0",
103102
"vite-plugin-svgr": "^4.2.0",
104103
"vite-plugin-vsharp": "^1.7.3",
105104
"vite-tsconfig-paths": "^4.3.1"

‎src/component/VideoMainControls.tsx ‎src/component/MainControls.tsx

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Text, Checkbox, Grid } from '@mantine/core'
2-
import { useFFmpegVideoStore } from '@/stores'
2+
import { useFFmpegStore, modes } from '@/stores'
33
import { useState } from 'react'
44
import {
55
IconPlayerPlay,
@@ -8,14 +8,16 @@ import {
88
IconTrashX,
99
IconMail,
1010
} from '@tabler/icons-react'
11-
import { VideoSettings } from './VideoSettings'
11+
import { SettingsVideo } from './SettingsVideo'
1212
import { FeedBack } from './FeedBack'
1313
import { useDisclosure } from '@mantine/hooks'
1414
import { isChromium } from '@/utils'
1515

16-
export const VideoMainControls = () => {
17-
const items = useFFmpegVideoStore(state => state.items)
18-
const selectedUUIDs = useFFmpegVideoStore(state => state.selectedUUIDs)
16+
export const MainControls = () => {
17+
const mode = useFFmpegStore(state => state.mode)
18+
const store = modes[mode].store
19+
const items = store(state => state.items)
20+
const selectedUUIDs = store(state => state.selectedUUIDs)
1921
const [autoDownload, setIsAutoDownload] = useState(true)
2022
const isNoSelection = selectedUUIDs.length === 0
2123
const [isSettingsOpened, { open: openSettings, close: closeSettings }] =
@@ -24,7 +26,7 @@ export const VideoMainControls = () => {
2426
useDisclosure(false)
2527
return (
2628
<>
27-
<VideoSettings isOpened={isSettingsOpened} close={closeSettings} />
29+
<SettingsVideo isOpened={isSettingsOpened} close={closeSettings} />
2830
<FeedBack isOpened={isFeedbackOpened} close={closeFeedback} />
2931
<Grid>
3032
<Grid.Col span={6} display="flex" style={{ justifyContent: 'end' }}>
@@ -46,7 +48,7 @@ export const VideoMainControls = () => {
4648
isNoSelection || !items.some(item => item.status !== 'processing')
4749
}
4850
onClick={() => {
49-
useFFmpegVideoStore.getState().convertSelected({ autoDownload })
51+
store.getState().convertSelected({ autoDownload })
5052
}}
5153
>
5254
Convert
@@ -60,7 +62,7 @@ export const VideoMainControls = () => {
6062
isNoSelection || !items.some(item => item.status === 'converted')
6163
}
6264
variant="default"
63-
onClick={() => useFFmpegVideoStore.getState().downloadSelected()}
65+
onClick={() => store.getState().downloadSelected()}
6466
>
6567
<Text>Download</Text>
6668
</Button>
@@ -72,7 +74,7 @@ export const VideoMainControls = () => {
7274
disabled={isNoSelection}
7375
variant="default"
7476
onClick={() => {
75-
useFFmpegVideoStore.getState().removeFiles(selectedUUIDs)
77+
store.getState().removeFiles(selectedUUIDs)
7678
}}
7779
>
7880
Delete

‎src/component/VideoSettings.tsx ‎src/component/SettingsAudio.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useState } from 'react'
1212
import { IconCheck, IconX } from '@tabler/icons-react'
1313
import { useFFmpegVideoStore } from '@/stores'
1414

15-
export const VideoSettings = ({
15+
export const SettingsAudio = ({
1616
isOpened,
1717
close,
1818
}: {

‎src/component/SettingsVideo.tsx

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {
2+
Modal,
3+
Grid,
4+
Stack,
5+
Select,
6+
NumberInput,
7+
Flex,
8+
Button,
9+
} from '@mantine/core'
10+
import { videoExtensions } from '@/constants'
11+
import { useState } from 'react'
12+
import { IconCheck, IconX } from '@tabler/icons-react'
13+
import { useFFmpegVideoStore } from '@/stores'
14+
15+
export const SettingsVideo = ({
16+
isOpened,
17+
close,
18+
}: {
19+
isOpened: boolean
20+
close: () => void
21+
}) => {
22+
const [videoBitrate, setVideoBitrate] = useState<string | number>(0)
23+
const [audioBitrate, setAudioBitrate] = useState<string | number>(0)
24+
const [height, setHeight] = useState<string | number>(0)
25+
const [width, setWidth] = useState<string | number>(0)
26+
const [ext, setExt] = useState<string>(videoExtensions[0]!)
27+
return (
28+
<Modal title="Settings" opened={isOpened} onClose={close} centered>
29+
<Stack>
30+
<Grid>
31+
<Grid.Col span={12}>
32+
<Select
33+
ta="left"
34+
value={ext}
35+
onChange={v => setExt(v || videoExtensions[0]!)}
36+
label="Output"
37+
data={videoExtensions}
38+
defaultValue={videoExtensions[0] || null}
39+
/>
40+
</Grid.Col>
41+
<Grid.Col span={6}>
42+
<NumberInput
43+
ta="left"
44+
decimalScale={0}
45+
hideControls
46+
label="Video Bitrate"
47+
min={0}
48+
description="Set to 0 to keep the original video parameter."
49+
value={videoBitrate}
50+
onChange={setVideoBitrate}
51+
styles={{
52+
label: { fontWeight: 'bold' },
53+
}}
54+
/>
55+
</Grid.Col>
56+
<Grid.Col span={6}>
57+
<NumberInput
58+
ta="left"
59+
decimalScale={0}
60+
hideControls
61+
label="Audio Bitrate"
62+
min={0}
63+
description="Set to 0 to keep the original video parameter."
64+
value={audioBitrate}
65+
onChange={setAudioBitrate}
66+
styles={{
67+
label: { fontWeight: 'bold' },
68+
}}
69+
/>
70+
</Grid.Col>
71+
</Grid>
72+
<Grid>
73+
<Grid.Col span={6}>
74+
<NumberInput
75+
ta="left"
76+
decimalScale={0}
77+
hideControls
78+
label="Width"
79+
min={0}
80+
description="Set to 0 to keep the original video parameter."
81+
value={width}
82+
onChange={setWidth}
83+
styles={{
84+
label: { fontWeight: 'bold' },
85+
}}
86+
/>
87+
</Grid.Col>
88+
<Grid.Col span={6}>
89+
<NumberInput
90+
ta="left"
91+
decimalScale={0}
92+
hideControls
93+
label="height"
94+
min={0}
95+
description="Set to 0 to keep the original video parameter."
96+
value={height}
97+
onChange={setHeight}
98+
styles={{
99+
label: { fontWeight: 'bold' },
100+
}}
101+
/>
102+
</Grid.Col>
103+
</Grid>
104+
<Flex justify="space-evenly">
105+
<Button
106+
leftSection={<IconCheck size={14} />}
107+
variant="default"
108+
onClick={() => {
109+
useFFmpegVideoStore.setState({
110+
settings: {
111+
ext,
112+
videoBitrate,
113+
audioBitrate,
114+
height,
115+
width,
116+
},
117+
})
118+
close()
119+
}}
120+
>
121+
Save
122+
</Button>
123+
<Button
124+
leftSection={<IconX size={14} />}
125+
variant="default"
126+
onClick={close}
127+
>
128+
Cancel
129+
</Button>
130+
</Flex>
131+
</Stack>
132+
</Modal>
133+
)
134+
}

‎src/component/Title.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { Title as Title_, Stack } from '@mantine/core'
22
import { textColor } from '@/styles'
3+
import { useFFmpegStore, modes } from '@/stores'
34

45
export const Title = () => {
6+
const mode = useFFmpegStore(state => state.mode)
7+
58
return (
69
<Stack justify="center" align="center" gap="xs" py="xl">
710
<Title_ order={1}>Video Converter</Title_>
811
<Title_ style={{ color: textColor }} order={3}>
912
Absolutely Free And No Artificial Restrictions!
1013
</Title_>
11-
<Title_ order={5}>Support .flv .mp4 .mkv .webm .wmv and more!</Title_>
14+
<Title_ order={5}>{modes[mode].title}</Title_>
1215
</Stack>
1316
)
1417
}

‎src/component/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ export * from './Dropzone'
44
export * from './FooterSocial'
55
export * from './Title'
66
export * from './VideoList'
7-
export * from './VideoSettings'
8-
export * from './VideoMainControls'
7+
export * from './SettingsVideo'
8+
export * from './MainControls'
99
export * from './DropzoneMini'
1010
export * from './Points'
1111
export * from './FeedBack'

‎src/constants.ts

+113-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
import {
2+
IconAdOff,
3+
IconPlant,
4+
IconFlare,
5+
IconHtml,
6+
IconDevices,
7+
IconFileDelta,
8+
IconSettingsStar,
9+
} from '@tabler/icons-react'
10+
import { useFFmpegAudioStore } from '@/stores/ffmpegAudio'
11+
import { useFFmpegVideoStore } from '@/stores/ffmpegVideo'
12+
113
export const videoFormats = [
214
{ mime: 'video/mp4', ext: '.mp4' },
315
{ mime: 'video/x-flv', ext: '.flv' },
@@ -26,4 +38,104 @@ export const videoFormats = [
2638
export const videoExtensions = videoFormats.map(({ ext }) => ext)
2739
export const videoMimes = videoFormats.map(({ mime }) => mime)
2840

29-
export const videosTypes = [...videoExtensions, ...videoMimes]
41+
export const videoTypes = [...videoExtensions, ...videoMimes]
42+
43+
export const videoPoints = [
44+
{
45+
Icon: IconPlant,
46+
title: 'Free',
47+
text: 'Enjoy unlimited access to powerful video conversion tools without spending a dime, with no hidden costs or subscriptions',
48+
},
49+
{
50+
Icon: IconAdOff,
51+
title: 'Ad-Free Experience',
52+
text: 'Convert your videos without interruptions. Our website is completely free of ads, so you can focus on what matters most—getting your work done efficiently.',
53+
},
54+
{
55+
Icon: IconFlare,
56+
title: 'No Artificial Restrictions',
57+
text: 'We believe in giving you full control. Convert and compress videos without any hidden limits on file size or duration.',
58+
},
59+
{
60+
Icon: IconHtml,
61+
title: 'Web-Based Convenience',
62+
text: ' No downloads, no installations. Access our video converter from any device, anywhere. Simply open your browser and start converting.',
63+
},
64+
{
65+
Icon: IconDevices,
66+
title: 'Client-Side Processing',
67+
text: 'Your privacy is our priority. All conversions are done directly on your device, ensuring that your files never leave your computer.',
68+
},
69+
{
70+
Icon: IconFileDelta,
71+
title: 'Supports Multiple Formats',
72+
text: 'Whether it’s MP4, AVI, MKV, or any other format, our converter handles them all with ease. Convert between dozens of video formats effortlessly.',
73+
},
74+
{
75+
Icon: IconSettingsStar,
76+
title: 'Customizable Settings',
77+
text: 'Tailor your conversions to your specific needs. Adjust resolution, bitrate, and more with easy-to-use settings.',
78+
},
79+
]
80+
81+
export const audioFormats = [
82+
{ mime: 'audio/mpeg', ext: '.mp3' },
83+
{ mime: 'audio/wav', ext: '.wav' },
84+
{ mime: 'audio/flac', ext: '.flac' },
85+
{ mime: 'audio/aac', ext: '.aac' },
86+
{ mime: 'audio/ogg', ext: '.ogg' },
87+
{ mime: 'audio/midi', ext: '.midi' },
88+
{ mime: 'audio/x-m4a', ext: '.m4a' },
89+
{ mime: 'audio/x-wav', ext: '.wav' },
90+
{ mime: 'audio/x-flac', ext: '.flac' },
91+
{ mime: 'audio/ogg', ext: '.oga' },
92+
{ mime: 'audio/opus', ext: '.opus' },
93+
{ mime: 'audio/x-aiff', ext: '.aiff' },
94+
{ mime: 'audio/x-matroska', ext: '.mka' },
95+
{ mime: 'audio/amr', ext: '.amr' },
96+
{ mime: 'audio/aiff', ext: '.aif' },
97+
{ mime: 'audio/x-aac', ext: '.aac' },
98+
] as const
99+
100+
export const audioExtensions = audioFormats.map(({ ext }) => ext)
101+
export const audioMimes = audioFormats.map(({ mime }) => mime)
102+
103+
export const audioTypes = [...videoExtensions, ...videoMimes]
104+
105+
export const audioPoints = [
106+
{
107+
Icon: IconPlant,
108+
title: 'Free',
109+
text: 'Enjoy unlimited access to powerful audio conversion tools without spending a dime, with no hidden costs or subscriptions.',
110+
},
111+
{
112+
Icon: IconAdOff,
113+
title: 'Ad-Free Experience',
114+
text: 'Convert your audio files without interruptions. Our website is completely free of ads, so you can focus on what matters most—getting your work done efficiently.',
115+
},
116+
{
117+
Icon: IconFlare,
118+
title: 'No Artificial Restrictions',
119+
text: 'We believe in giving you full control. Convert and compress audio files without any hidden limits on file size or duration.',
120+
},
121+
{
122+
Icon: IconHtml,
123+
title: 'Web-Based Convenience',
124+
text: 'No downloads, no installations. Access our audio converter from any device, anywhere. Simply open your browser and start converting.',
125+
},
126+
{
127+
Icon: IconDevices,
128+
title: 'Client-Side Processing',
129+
text: 'Your privacy is our priority. All conversions are done directly on your device, ensuring that your files never leave your computer.',
130+
},
131+
{
132+
Icon: IconFileDelta,
133+
title: 'Supports Multiple Formats',
134+
text: 'Whether it’s MP3, WAV, FLAC, or any other format, our converter handles them all with ease. Convert between dozens of audio formats effortlessly.',
135+
},
136+
{
137+
Icon: IconSettingsStar,
138+
title: 'Customizable Settings',
139+
text: 'Tailor your conversions to your specific needs. Adjust bitrate, sample rate, and more with easy-to-use settings.',
140+
},
141+
]

‎src/screen/home.tsx

+45-60
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,32 @@ import {
33
FooterSocial,
44
Title,
55
VideoList,
6-
VideoMainControls,
6+
MainControls,
77
DropzoneMini,
88
Points,
99
} from '@/component'
10-
import { Stack } from '@mantine/core'
11-
import { useFFmpegVideoStore } from '@/stores'
10+
import { Stack, Button } from '@mantine/core'
1211
import {
13-
IconAdOff,
14-
IconPlant,
15-
IconFlare,
16-
IconHtml,
17-
IconDevices,
18-
IconFileDelta,
19-
IconSettingsStar,
20-
} from '@tabler/icons-react'
21-
import { videosTypes } from '@/constants'
12+
useFFmpegVideoStore,
13+
useFFmpegStore,
14+
useFFmpegAudioStore,
15+
modes,
16+
} from '@/stores'
17+
import { IconCheck } from '@tabler/icons-react'
2218

23-
const points = [
24-
{
25-
Icon: IconPlant,
26-
title: 'Free',
27-
text: 'Enjoy unlimited access to powerful video conversion tools without spending a dime, with no hidden costs or subscriptions',
28-
},
29-
{
30-
Icon: IconAdOff,
31-
title: 'Ad-Free Experience',
32-
text: 'Convert your videos without interruptions. Our website is completely free of ads, so you can focus on what matters most—getting your work done efficiently.',
33-
},
34-
{
35-
Icon: IconFlare,
36-
title: 'No Artificial Restrictions',
37-
text: 'We believe in giving you full control. Convert and compress videos without any hidden limits on file size or duration.',
38-
},
39-
{
40-
Icon: IconHtml,
41-
title: 'Web-Based Convenience',
42-
text: ' No downloads, no installations. Access our video converter from any device, anywhere. Simply open your browser and start converting.',
43-
},
44-
{
45-
Icon: IconDevices,
46-
title: 'Client-Side Processing',
47-
text: 'Your privacy is our priority. All conversions are done directly on your device, ensuring that your files never leave your computer.',
48-
},
49-
{
50-
Icon: IconFileDelta,
51-
title: 'Supports Multiple Formats',
52-
text: 'Whether it’s MP4, AVI, MKV, or any other format, our converter handles them all with ease. Convert between dozens of video formats effortlessly.',
53-
},
54-
{
55-
Icon: IconSettingsStar,
56-
title: 'Customizable Settings',
57-
text: 'Tailor your conversions to your specific needs. Adjust resolution, bitrate, and more with easy-to-use settings for a truly personalized experience.',
58-
},
59-
]
19+
const isMoreThan99 = (count: number) =>
20+
count > 0 ? (count > 99 ? `(99+)` : `(${count})`) : ''
6021

6122
export const Home = () => {
62-
const items = useFFmpegVideoStore(state => state.items)
63-
const hasItems = items.length > 0
23+
const mode = useFFmpegStore(state => state.mode)
24+
const store = modes[mode].store
25+
const types = modes[mode].types
26+
const points = modes[mode].points
27+
const audioCount = useFFmpegAudioStore(state => state.items).length
28+
const videoCount = useFFmpegVideoStore(state => state.items).length
29+
const hasItems = videoCount > 0 || audioCount > 0
30+
const isVideo = mode === 'video'
31+
const isAudio = mode === 'audio'
6432

6533
return (
6634
<Stack gap={0} align="center" justify="start" h="100%" px={0}>
@@ -73,21 +41,38 @@ export const Home = () => {
7341
flexGrow: 1,
7442
}}
7543
>
44+
<Button.Group>
45+
<Button
46+
w="8rem"
47+
leftSection={isVideo ? <IconCheck size={14} /> : null}
48+
variant={isVideo ? 'filled' : 'default'}
49+
onClick={() => {
50+
useFFmpegStore.getState().switchMode('video')
51+
}}
52+
>
53+
Video{isMoreThan99(videoCount)}
54+
</Button>
55+
<Button
56+
w="8rem"
57+
leftSection={isAudio ? <IconCheck size={14} /> : null}
58+
variant={isAudio ? 'filled' : 'default'}
59+
onClick={() => {
60+
useFFmpegStore.getState().switchMode('audio')
61+
}}
62+
>
63+
Audio
64+
{isMoreThan99(audioCount)}
65+
</Button>
66+
</Button.Group>
7667
{hasItems ? (
7768
<>
78-
<VideoMainControls />
79-
<DropzoneMini
80-
onDrop={useFFmpegVideoStore.getState().addFiles}
81-
accept={videosTypes}
82-
/>
69+
<MainControls />
70+
<DropzoneMini onDrop={store.getState().addFiles} accept={types} />
8371
<VideoList />
8472
</>
8573
) : (
8674
<>
87-
<Dropzone
88-
onDrop={useFFmpegVideoStore.getState().addFiles}
89-
accept={videosTypes}
90-
/>
75+
<Dropzone onDrop={store.getState().addFiles} accept={types} />
9176
<Points items={points} />
9277
</>
9378
)}

‎src/stores/ffmpeg.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,32 @@ import { toBlobURL } from '@ffmpeg/util'
44
import { isChromium } from '@/utils'
55

66
const initialState = {
7-
packageStatus: 'idle' as const,
7+
packageStatus: 'idle',
88
message: null,
9-
}
9+
mode: 'video',
10+
} as const
11+
1012
export const ffmpeg = new FFmpeg()
1113
export const useFFmpegStore = persistent<{
1214
load: () => void
1315
packageStatus: 'idle' | 'loading' | 'loaded'
1416
message: null | string
17+
mode: 'video' | 'audio'
18+
switchMode: (mode: 'video' | 'audio') => void
1519
}>(
1620
{
17-
name: 'ffmpegVideo',
18-
keysToPersist: [],
21+
name: 'ffmpeg',
22+
keysToPersist: ['mode'],
1923
},
2024
(set, get) => {
2125
return {
2226
...initialState,
2327
reset: () => {
2428
set({ ...initialState, packageStatus: get().packageStatus })
2529
},
30+
switchMode: (mode: 'video' | 'audio') => {
31+
set({ mode })
32+
},
2633
load: async () => {
2734
const { packageStatus: status } = get()
2835
if (status !== 'idle') return

‎src/stores/ffmpegAudio.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ export const useFFmpegAudioStore = persistent<{
4343
removeFiles: (file_uuids: string[]) => void
4444
}>(
4545
{
46-
name: 'ffmpegVideo',
47-
keysToPersist: [],
46+
name: 'ffmpegAudio',
47+
keysToPersist: ['settings'],
4848
},
4949
(set, get) => {
5050
const clearDownload = (uuids: string[]) => {

‎src/stores/ffmpegVideo.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const useFFmpegVideoStore = persistent<{
4444
}>(
4545
{
4646
name: 'ffmpegVideo',
47-
keysToPersist: [],
47+
keysToPersist: ['settings'],
4848
},
4949
(set, get) => {
5050
const clearDownload = (uuids: string[]) => {

‎src/stores/index.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
import { useFFmpegAudioStore } from './ffmpegAudio'
2+
import { useFFmpegVideoStore } from './ffmpegVideo'
3+
import { videoTypes, videoPoints, audioPoints, audioTypes } from '@/constants'
4+
export const modes = {
5+
video: {
6+
store: useFFmpegVideoStore,
7+
types: videoTypes,
8+
points: videoPoints,
9+
title: 'Support .flv .mp4 .mkv .webm .wmv and more!',
10+
},
11+
audio: {
12+
store: useFFmpegAudioStore,
13+
points: audioPoints,
14+
types: audioTypes,
15+
title: 'Support .mp3 .wav .mkv .aac .flac and more!',
16+
},
17+
} as const
18+
119
export * from './ffmpeg'
220
export * from './ffmpegAudio'
321
export * from './ffmpegVideo'

‎vite.config.ts

-25
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import svgr from 'vite-plugin-svgr'
44
import { qrcode } from 'vite-plugin-qrcode'
55
import mkcert from 'vite-plugin-mkcert'
66
import removeConsole from 'vite-plugin-remove-console'
7-
import { viteStaticCopy } from 'vite-plugin-static-copy'
87
import tsconfigPaths from 'vite-tsconfig-paths'
98
import dynamicImport from 'vite-plugin-dynamic-import'
109
import vsharp from 'vite-plugin-vsharp'
@@ -23,30 +22,6 @@ export default defineConfig({
2322
mkcert(),
2423
vsharp(),
2524
removeConsole({ includes: ['log'] }),
26-
viteStaticCopy({
27-
targets: [
28-
{
29-
src: '_redirects',
30-
dest: '',
31-
},
32-
{
33-
src: 'manifest.json',
34-
dest: '',
35-
},
36-
{
37-
src: 'icons',
38-
dest: '',
39-
},
40-
{
41-
src: 'audios',
42-
dest: '',
43-
},
44-
{
45-
src: 'characters',
46-
dest: '',
47-
},
48-
],
49-
}),
5025
],
5126
optimizeDeps: {
5227
exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'],

0 commit comments

Comments
 (0)
Please sign in to comment.