admin管理员组文章数量:1391925
I'm trying to burn subtitles .ass file into an .mp4 video.
When I try to do so the process finishes and renders the same video without subtitles.
I think the problem is with the font, at least that's what ChatGPT told me, but I'm not sure what to do. Please take a look the code and tell me, if I did it correctly!
The code:
import { FFmpeg } from "@ffmpeg/ffmpeg"
import { fetchFile } from "@ffmpeg/util"
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"
export async function burnSubtitles(inputVideo: string, inputSubtitles: string, session: any, videoFileName: string) {
const ffmpeg = new FFmpeg()
const supabase = createClientComponentClient()
try {
console.log("Loading FFmpeg...")
await ffmpeg.load({
coreURL: "/@ffmpeg/[email protected]/dist/umd/ffmpeg-core.js",
log: true,
})
console.log("FFmpeg Loaded!")
// Fetch video and subtitle files
console.log("Fetching video and subtitle files...")
const videoFile = await fetchFile(inputVideo)
const subtitleFile = await fetchFile(inputSubtitles)
console.log("Writing files to FFmpeg virtual filesystem...")
await ffmpeg.writeFile("input.mp4", videoFile)
await ffmpeg.writeFile("subtitles.ass", subtitleFile)
console.log("Running FFmpeg command...")
await ffmpeg.exec([
"-i",
"input.mp4",
"-vf",
"subtitles=subtitles.ass",
"-c:v",
"libx264",
"-preset",
"fast",
"-crf",
"23",
"-c:a",
"aac",
"-b:a",
"128k",
"output.mp4",
])
console.log("FFmpeg processing completed!")
// Get the output file
console.log("Reading output file...")
const outputData = await ffmpeg.readFile("output.mp4")
// Generate file paths
const processedFile = videoFileName ? `${videoFileName.split(".")[0]}.mp4` : `${Date.now()}.mp4`
const processedFilePath = `processed/${session.user.id}/${processedFile}`
console.log("Uploading processed video to Supabase...")
// Upload the processed video to Supabase
const { error } = await supabase.storage
.from("processed")
.upload(processedFilePath, new Blob([outputData.buffer], { type: "video/mp4" }), { upsert: true })
if (error) {
throw new Error(`Failed to upload processed video: ${error.message}`)
}
// Get the public URL of the uploaded video
const {
data: { publicUrl },
} = supabase.storage.from("processed").getPublicUrl(processedFilePath)
console.log("Subtitled video uploaded successfully:", publicUrl)
return {
success: true,
videoUrl: publicUrl,
}
} catch (error) {
console.error("Error in FFmpeg processing:", error)
return {
success: false,
error: "Processing failed",
}
} finally {
ffmpeg.terminate()
}
}
The ass file:
[Script Info]
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,16,&HFFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,1,2,2,2,30,30,360,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,00:00:00.00,00:00:01.11,Default,,0,0,360,,{\fad(300,0)\t(0,350,\fscx80\fscy80)\t(350,600,\fscx100\fscy100)\shad2}THIS IS A TEST,
Dialogue: 0,00:00:01.11,00:00:02.22,Default,,0,0,360,,{\fad(300,0)\t(0,350,\fscx80\fscy80)\t(350,600,\fscx100\fscy100)\shad2}SUBTITLE
When I try to do so the process finishes, and renders the same video without subtitles.
I'm expecting the rendered video to have the subtitles.
I'm trying to burn subtitles .ass file into an .mp4 video.
When I try to do so the process finishes and renders the same video without subtitles.
I think the problem is with the font, at least that's what ChatGPT told me, but I'm not sure what to do. Please take a look the code and tell me, if I did it correctly!
The code:
import { FFmpeg } from "@ffmpeg/ffmpeg"
import { fetchFile } from "@ffmpeg/util"
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"
export async function burnSubtitles(inputVideo: string, inputSubtitles: string, session: any, videoFileName: string) {
const ffmpeg = new FFmpeg()
const supabase = createClientComponentClient()
try {
console.log("Loading FFmpeg...")
await ffmpeg.load({
coreURL: "https://cdn.jsdelivr/npm/@ffmpeg/[email protected]/dist/umd/ffmpeg-core.js",
log: true,
})
console.log("FFmpeg Loaded!")
// Fetch video and subtitle files
console.log("Fetching video and subtitle files...")
const videoFile = await fetchFile(inputVideo)
const subtitleFile = await fetchFile(inputSubtitles)
console.log("Writing files to FFmpeg virtual filesystem...")
await ffmpeg.writeFile("input.mp4", videoFile)
await ffmpeg.writeFile("subtitles.ass", subtitleFile)
console.log("Running FFmpeg command...")
await ffmpeg.exec([
"-i",
"input.mp4",
"-vf",
"subtitles=subtitles.ass",
"-c:v",
"libx264",
"-preset",
"fast",
"-crf",
"23",
"-c:a",
"aac",
"-b:a",
"128k",
"output.mp4",
])
console.log("FFmpeg processing completed!")
// Get the output file
console.log("Reading output file...")
const outputData = await ffmpeg.readFile("output.mp4")
// Generate file paths
const processedFile = videoFileName ? `${videoFileName.split(".")[0]}.mp4` : `${Date.now()}.mp4`
const processedFilePath = `processed/${session.user.id}/${processedFile}`
console.log("Uploading processed video to Supabase...")
// Upload the processed video to Supabase
const { error } = await supabase.storage
.from("processed")
.upload(processedFilePath, new Blob([outputData.buffer], { type: "video/mp4" }), { upsert: true })
if (error) {
throw new Error(`Failed to upload processed video: ${error.message}`)
}
// Get the public URL of the uploaded video
const {
data: { publicUrl },
} = supabase.storage.from("processed").getPublicUrl(processedFilePath)
console.log("Subtitled video uploaded successfully:", publicUrl)
return {
success: true,
videoUrl: publicUrl,
}
} catch (error) {
console.error("Error in FFmpeg processing:", error)
return {
success: false,
error: "Processing failed",
}
} finally {
ffmpeg.terminate()
}
}
The ass file:
[Script Info]
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,16,&HFFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,1,2,2,2,30,30,360,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,00:00:00.00,00:00:01.11,Default,,0,0,360,,{\fad(300,0)\t(0,350,\fscx80\fscy80)\t(350,600,\fscx100\fscy100)\shad2}THIS IS A TEST,
Dialogue: 0,00:00:01.11,00:00:02.22,Default,,0,0,360,,{\fad(300,0)\t(0,350,\fscx80\fscy80)\t(350,600,\fscx100\fscy100)\shad2}SUBTITLE
When I try to do so the process finishes, and renders the same video without subtitles.
I'm expecting the rendered video to have the subtitles.
Share Improve this question asked Mar 14 at 5:54 Diyan TodorovDiyan Todorov 11 Answer
Reset to default -1import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"
import { cookies } from "next/headers"
interface WhisperSegment {
start: number
end: number
text: string
}
interface WhisperResponse {
segments?: WhisperSegment[]
}
function formatTimeASS(seconds: number): string {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = Math.floor(seconds % 60)
const centiseconds = Math.floor((seconds % 1) * 100)
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}.${centiseconds.toString().padStart(2, "0")}`
}
function splitTextIntoChunks(text: string): string[] {
const words = text.split(" ")
const chunks = []
let chunk = ""
for (const word of words) {
chunk += word + " "
if (/[.!?:]$/.test(word) || chunk.split(" ").length >= 4) {
chunks.push(chunk.trim().toUpperCase())
chunk = ""
}
}
if (chunk) chunks.push(chunk.trim().toUpperCase())
return chunks
}
function createAssContent(segments: WhisperSegment[]): string {
const header = `[Script Info]
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Montserrat Black,48,&HFFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,1,2,2,2,30,30,360,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`
let dialogues = ""
for (const segment of segments) {
dialogues += `Dialogue: 0,${formatTimeASS(segment.start)},${formatTimeASS(segment.end)},Default,,0,0,360,,{\\fad(300,0)\\t(0,350,\\fscx80\\fscy80)\\t(350,600,\\fscx100\\fscy100)\\shad2}${segment.text.toUpperCase()}\n`
}
return header + dialogues.trim()
}
async function checkTextWithGPT4(segments: WhisperSegment[]): Promise<WhisperSegment[]> {
const correctedSegments = [...segments]
try {
for (let i = 0; i < segments.length; i++) {
const segment = segments[i]
const response = await fetch("https://api.openai/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [
{
role: "system",
content:
"You are a conservative spelling and grammar checker. ONLY fix clear spelling errors and obvious grammar mistakes. DO NOT change any words that are correctly spelled, even if they seem unusual. DO NOT alter the meaning, style, or vocabulary choices. DO NOT add or remove content. If the text is already correct, return it unchanged.",
},
{
role: "user",
content: segment.text,
},
],
temperature: 0.1,
}),
})
if (response.ok) {
const data = await response.json()
correctedSegments[i] = {
...segment,
text: data.choices[0].message.content.trim() || segment.text,
}
}
}
return correctedSegments
} catch (error) {
return segments
}
}
export async function POST(req: Request) {
try {
const supabase = createRouteHandlerClient({ cookies })
const {
data: { session },
} = await supabase.auth.getSession()
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
const formData = await req.formData()
const audioUrl = formData.get("audioUrl") as string
const videoFileName = formData.get("videoFileName") as string
if (!audioUrl) {
return NextResponse.json({ error: "Audio URL is required" }, { status: 400 })
}
const audioResponse = await fetch(audioUrl)
const audioBlob = await audioResponse.blob()
const openAIFormData = new FormData()
openAIFormData.append("file", audioBlob, "audio.mp3")
openAIFormData.append("model", "whisper-1")
openAIFormData.append("language", "bg")
openAIFormData.append("response_format", "verbose_json")
const openAIResponse = await fetch("https://api.openai/v1/audio/transcriptions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: openAIFormData,
})
if (!openAIResponse.ok) {
throw new Error("Failed to transcribe audio")
}
const jsonContent: WhisperResponse = await openAIResponse.json()
if (!jsonContent.segments || jsonContent.segments.length === 0) {
return NextResponse.json({ error: "No transcription segments found" }, { status: 400 })
}
const correctedSegments = await checkTextWithGPT4(jsonContent.segments)
const assContent = createAssContent(correctedSegments)
const assFileName = videoFileName ? `${videoFileName.split(".")[0]}.ass` : `${Date.now()}.ass`
const subtitlePath = `subtitles/${session.user.id}/${assFileName}`
await supabase.storage
.from("extracts")
.upload(subtitlePath, new Blob([assContent], { type: "text/plain" }), { upsert: true })
const {
data: { publicUrl },
} = supabase.storage.from("extracts").getPublicUrl(subtitlePath)
return NextResponse.json({
success: true,
subtitleUrl: publicUrl,
})
} catch (error) {
return NextResponse.json({ error: "Processing failed" }, { status: 500 })
}
}"```
Thats how i fixed it.
本文标签: nodejsBurn ass into mp4 using FFMPEG WASMStack Overflow
版权声明:本文标题:node.js - Burn .ass into .mp4 using FFMPEG WASM - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744672215a2618891.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论