Skip to content

Commit 6e5eb41

Browse files
committed
save
1 parent e829f7b commit 6e5eb41

13 files changed

+5425
-3339
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ RUN cat llvm.txt > /etc/apt/sources.list.d/llvm.list && \
3838
LABEL org.opencontainers.image.source https://github.com/termsurf/task
3939
LABEL org.opencontainers.image.title "Task: Common Actions Interface"
4040
LABEL org.opencontainers.image.description "A wrapper around a lot of tools to make it easier to use them all."
41+
42+
# https://packages.debian.org/search?keywords=llvm

code/browser/video.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { FFmpeg } from '@ffmpeg/ffmpeg'
2+
import { fetchFile, toBlobURL } from '@ffmpeg/util'
3+
4+
export async function bindFFmpeg({
5+
onLog,
6+
onProgress,
7+
corePath,
8+
wasmPath,
9+
}: {
10+
wasmPath: string
11+
corePath: string
12+
onLog?: (message: string) => void
13+
onProgress?: (progress: number) => void
14+
}) {
15+
const ffmpeg = new FFmpeg()
16+
if (onLog) {
17+
ffmpeg.on('log', ({ message }) => {
18+
onLog(message)
19+
})
20+
}
21+
if (onProgress) {
22+
ffmpeg.on('progress', ({ progress }) => {
23+
// message.innerHTML = `${progress * 100} %`
24+
onProgress(progress)
25+
})
26+
}
27+
await ffmpeg.load({
28+
coreURL: corePath,
29+
wasmURL: wasmPath,
30+
})
31+
32+
return ffmpeg
33+
}
34+
35+
export async function writeFFmpegFile(
36+
ffmpeg: FFmpeg,
37+
name: string,
38+
file: File,
39+
) {
40+
await ffmpeg.writeFile(name, await fetchFile(file))
41+
}
42+
43+
// message.innerHTML = 'Start transcoding'
44+
// await ffmpeg.exec(['-i', name, 'output.mp4'])
45+
// message.innerHTML = 'Complete transcoding'
46+
// const data = await ffmpeg.readFile('output.mp4')
47+
// video.src = URL.createObjectURL(
48+
// new Blob([data.buffer], { type: 'video/mp4' }),
49+
// )

code/build/ffmpeg.wasm

23.2 MB
Binary file not shown.

code/node/video.ts

Lines changed: 183 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { FFMPEGCodecAudio, FFMPEGCodecVideo } from '../shared'
1+
import {
2+
FFMPEG_TIME_PATTERN,
3+
FFmpegCodecAudio,
4+
FFmpegCodecSubtitle,
5+
FFmpegCodecVideo,
6+
FFmpegStrictOption,
7+
} from '../shared'
28
import { IOPath } from '../shared/type'
39
import { exec } from './process'
410

@@ -15,67 +21,225 @@ export async function addAudioToVideoWithFFMPEG({
1521
inputVideoPath,
1622
inputAudioPath,
1723
outputPath,
24+
audioCodec = 'aac',
1825
fit,
1926
}: {
2027
inputVideoPath: string
2128
inputAudioPath: string
2229
outputPath: string
2330
fit?: boolean
31+
audioCodec?: FFmpegCodecAudio
2432
}) {
2533
return await exec(
26-
`ffmpeg -y -loglevel warning -hide_banner -nostats -i "${inputVideoPath}" -i "${inputAudioPath}" ${
34+
`ffmpeg -y -loglevel error -hide_banner -nostats -i "${inputVideoPath}" -i "${inputAudioPath}" ${
2735
fit ? '-shortest ' : ''
28-
}-c:v copy -c:a aac "${outputPath}"`,
36+
} -c:v copy -c:a ${audioCodec} "${outputPath}"`,
2937
)
3038
}
3139

32-
export async function convertVideoToAudioWithFFMPEG({
40+
export async function convertVideoToAudioWithFFmpeg({
3341
inputPath,
3442
outputPath,
3543
}: IOPath) {
3644
// output.mp3
3745
return await exec(`ffmpeg -i "${inputPath}" -vn ${outputPath}`)
3846
}
3947

40-
export async function compressMP4WithFFMPEG({
48+
export async function compressMP4WithFFmpeg({
4149
inputPath,
4250
outputPath,
4351
audioCodec = 'aac',
4452
videoCodec = 'h264',
4553
}: IOPath & {
46-
audioCodec?: FFMPEGCodecAudio
47-
videoCodec?: FFMPEGCodecVideo
54+
audioCodec?: FFmpegCodecAudio
55+
videoCodec?: FFmpegCodecVideo
4856
}) {
49-
return await exec(
50-
`ffmpeg -y -loglevel warning -hide_banner -nostats -i "${inputPath}" -strict -2 -vcodec ${videoCodec} -acodec ${audioCodec} "${outputPath}"`,
51-
)
57+
return await convertVideoWithFFmpeg({
58+
inputPath,
59+
outputPath,
60+
audioCodec,
61+
videoCodec,
62+
})
63+
}
64+
65+
// ffmpeg -filters
66+
// https://ffmpeg.org/ffmpeg-filters.html
67+
// https://opensource.com/article/17/6/ffmpeg-convert-media-file-formats
68+
// -ss 00:01:00 (HOURS:MM:SS.MILLISECONDS)
69+
// You can also use the -an and -sn flags in the same manner to strip out audio and subtitle streams.
70+
// https://img.ly/blog/ultimate-guide-to-ffmpeg/#example-material
71+
// https://ottverse.com/change-resolution-resize-scale-video-using-ffmpeg/
72+
// https://creatomate.com/blog/how-to-change-the-resolution-of-a-video-using-ffmpeg
73+
// https://creatomate.com/blog/use-nodejs-to-generate-instagram-youtube-or-tiktok-videos
74+
// -q:a
75+
// wav to mp3
76+
// ffmpeg -i audio.wav -acodec libmp3lame audio.mp3
77+
// ogg to mp3
78+
// ffmpeg -i audio.ogg -acodec libmp3lame audio.mp3
79+
// ac3 to mp3
80+
// ffmpeg -i audio.ac3 -acodec libmp3lame audio.mp3
81+
// aac to mp3
82+
// ffmpeg -i audio.aac -acodec libmp3lame audio.mp3
83+
export async function convertVideoWithFFmpeg({
84+
inputPath,
85+
outputPath,
86+
audioCodec,
87+
videoCodec,
88+
audioBitRate,
89+
videoBitRate,
90+
frameRate,
91+
startTime,
92+
endTime,
93+
strict = 'strict',
94+
scaleWidth,
95+
scaleHeight,
96+
audioChannels,
97+
audioSamplingFrequency,
98+
subtitleCodec,
99+
duration,
100+
rotate,
101+
}: IOPath & {
102+
audioCodec?: FFmpegCodecAudio
103+
videoCodec?: FFmpegCodecVideo
104+
audioBitRate?: number
105+
videoBitRate?: number
106+
frameRate?: number
107+
startTime?: string
108+
endTime?: string
109+
strict?: FFmpegStrictOption
110+
scaleWidth?: number
111+
scaleHeight?: number
112+
audioChannels?: number
113+
audioSamplingFrequency?: number
114+
subtitleCodec?: FFmpegCodecSubtitle
115+
duration?: string
116+
rotate?: number
117+
}) {
118+
if (startTime && !startTime.match(FFMPEG_TIME_PATTERN)) {
119+
throw new Error('Start time value is incorrectly formatted.')
120+
}
121+
if (endTime && !endTime.match(FFMPEG_TIME_PATTERN)) {
122+
throw new Error('End time value is incorrectly formatted.')
123+
}
124+
if (duration && !duration.match(FFMPEG_TIME_PATTERN)) {
125+
throw new Error('Duration value is incorrectly formatted.')
126+
}
127+
128+
const command = ['ffmpeg -y -loglevel error -hide_banner -nostats']
129+
command.push(`-i "${inputPath}"`)
130+
131+
if (startTime) {
132+
command.push(`-ss ${startTime}`)
133+
}
134+
135+
if (endTime) {
136+
command.push(`-to ${endTime}`)
137+
}
138+
139+
if (duration) {
140+
command.push(`-t ${duration}`)
141+
}
142+
143+
if (strict) {
144+
command.push(`-strict ${strict}`)
145+
}
146+
147+
if (audioChannels) {
148+
command.push(`-ac ${audioChannels}`)
149+
}
150+
151+
if (audioSamplingFrequency) {
152+
command.push(`-ar ${audioSamplingFrequency}`)
153+
}
154+
155+
if (scaleWidth) {
156+
if (scaleHeight) {
157+
command.push(`-filter:v "scale=${scaleWidth}:${scaleHeight}"`)
158+
} else {
159+
command.push(`-filter:v "scale=${scaleWidth}:-1"`)
160+
}
161+
} else if (scaleHeight) {
162+
command.push(`-filter:v "scale=-1:${scaleHeight}"`)
163+
} else if (rotate) {
164+
command.push(`-filter:v "rotate=${rotate}`)
165+
}
166+
167+
if (audioBitRate) {
168+
command.push(`-b:a ${audioBitRate}`)
169+
}
170+
171+
if (videoBitRate) {
172+
command.push(`-b:v ${videoBitRate}`)
173+
}
174+
175+
if (frameRate) {
176+
command.push(`-r ${frameRate}`)
177+
}
178+
179+
if (videoCodec) {
180+
command.push(`-c:v ${videoCodec}`)
181+
}
182+
183+
if (audioCodec) {
184+
command.push(`-c:a ${audioCodec}`)
185+
}
186+
187+
if (subtitleCodec) {
188+
command.push(`-s:a ${subtitleCodec}`)
189+
}
190+
191+
command.push(`"${outputPath}"`)
192+
193+
return await exec(command.join(' '))
52194
}
53195

54-
export type ConvertMP4ToGifWithFFMPEG = IOPath & {
196+
export async function compressVideoWithFFmpeg() {}
197+
198+
export type ConvertMP4ToGifWithFFmpeg = IOPath & {
55199
fps?: number
56200
width: number
57-
startTimeInSeconds?: number
58-
duration?: number
201+
startTime?: string
202+
endTime?: string
203+
duration?: string
59204
}
60205

61-
export async function convertMP4ToGifWithFFMPEG({
206+
export async function convertMP4ToGifWithFFmpeg({
62207
inputPath,
63208
outputPath,
64209
fps = 10,
65210
width,
66-
startTimeInSeconds,
211+
startTime,
212+
endTime,
67213
duration,
68-
}: ConvertMP4ToGifWithFFMPEG) {
214+
}: ConvertMP4ToGifWithFFmpeg) {
69215
fps = fps || 10
216+
70217
let cmd = [
71-
`ffmpeg -y -loglevel warning -hide_banner -nostats -i "${inputPath}"`,
218+
`ffmpeg -y -loglevel error -hide_banner -nostats -i "${inputPath}"`,
72219
]
73-
if (startTimeInSeconds) {
74-
cmd.push(`-ss ${startTimeInSeconds}`)
220+
221+
if (startTime && !startTime.match(FFMPEG_TIME_PATTERN)) {
222+
throw new Error('Start time value is incorrectly formatted.')
223+
}
224+
if (endTime && !endTime.match(FFMPEG_TIME_PATTERN)) {
225+
throw new Error('End time value is incorrectly formatted.')
226+
}
227+
if (duration && !duration.match(FFMPEG_TIME_PATTERN)) {
228+
throw new Error('Duration value is incorrectly formatted.')
75229
}
230+
231+
if (startTime) {
232+
cmd.push(`-ss ${startTime}`)
233+
}
234+
235+
if (endTime) {
236+
cmd.push(`-to ${endTime}`)
237+
}
238+
76239
if (duration) {
77240
cmd.push(`-t ${duration}`)
78241
}
242+
79243
cmd.push(
80244
`-vf "fps=${fps},scale=${width}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 "${outputPath}"`,
81245
)

0 commit comments

Comments
 (0)