Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions apps/web/app/api/subtitle/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {NextRequest, NextResponse} from 'next/server';
import {createClient} from '@/lib/supabase/server';
import {convertJsonToSrt} from "@/utils/convertJsonToSrt";

export async function GET(req: NextRequest, context: { params: Promise<{ id: string }> }) {
const supabase = await createClient();

const params = await context.params;
const subtitleId = params.id;
try{
const{data: {user}} = await supabase.auth.getUser();
if(!user){
return NextResponse.json({error: 'Unauthorized'}, {status:401});
}



const {data: subtitleData, error: subtitleError} = await supabase
.from('subtitle_jobs')
.select("*")
.eq("id", subtitleId)
.eq("user_id", user.id)
.single();


if (subtitleError) {
return NextResponse.json({ message: 'Error fetching subtitle' }, { status: 500 });
}
// console.log(subtitleData);

return NextResponse.json(subtitleData);


}catch(error: unknown) {
console.error('Error fetching subtitles:', error);
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
}

}



export async function DELETE(
request: Request,
context: { params: Promise<{ id: string }> }
) {
const supabase = await createClient();
const params = await context.params;
const subtitleId = params.id;
console.log(subtitleId);

try {
const { data: { user }, error: userError } = await supabase.auth.getUser();
if (userError || !user) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}

const { data: subtitle, error: subtitleError } = await supabase
.from('subtitle_jobs')
.delete()
.eq('id', subtitleId)
.eq('user_id', user.id)
.select('video_url')
.single();

console.log(subtitle)
if (subtitleError && subtitleError.code !== 'PGRST116' || !subtitle) {

console.error('Subtitle lookup error:', subtitleError);
NextResponse.json({ message: 'Subtitle lookup error' }, { status: 404 });
}

if (subtitle?.video_url) {
const bucketName = 'video_subtitles';
const filePath = subtitle.video_url.substring(
subtitle.video_url.indexOf(bucketName) + bucketName.length + 1
);
if (filePath) {
await supabase.storage.from(bucketName).remove([filePath]);
}
}

return NextResponse.json({ message: 'Script deleted successfully' });
} catch (error) {
console.error('Error deleting script:', error);
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
}
}

export async function PATCH(request: Request, context: { params: Promise<{ id: string }> }) {
const supabase = await createClient();
const params = await context.params;
const subtitle_id = params.id;
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const body = await request.json();
const { subtitle_json} = body;
console.log(subtitle_id);
console.log(subtitle_json.type);
if (!Array.isArray(subtitle_json)) {
return NextResponse.json({ error: 'Invalid subtitle format' }, { status: 400 });
}

const srtContent = convertJsonToSrt(subtitle_json);

const { error: updateError } = await supabase
.from("subtitle_jobs")
.update({
subtitles_json:JSON.stringify(subtitle_json)
})
.eq("id", subtitle_id)
.eq("user_id", user.id)
.single();

if (updateError) {
console.error(updateError);
return NextResponse.json({ error: 'Failed to update subtitles' }, { status: 500 });
}

return NextResponse.json({
success: true,
message: 'Subtitles updated successfully',
subtitles: subtitle_json,
srt: srtContent
});
} catch (error) {
console.error("Error in PATCH:", error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
89 changes: 89 additions & 0 deletions apps/web/app/api/subtitle/burnSubtitle/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { configureFFmpeg } from "@/utils/ffmpegConfig";
import { fetchVideoAsBuffer } from "@/utils/videoUrlTools";
import { convertJsonToSrt } from "@/utils/convertJsonToSrt";

export const runtime = "nodejs";

export async function POST(request: Request) {
try {
const { videoUrl, subtitles } = await request.json();

if (!videoUrl || !subtitles || !Array.isArray(subtitles)) {
return NextResponse.json(
{ error: "Missing videoUrl or subtitles" },
{ status: 400 }
);
}

console.log("Starting burnSubtitle process...");
console.log(" Video URL:", videoUrl);
console.log(" Subtitles received:", subtitles.length);

const videoBuffer = await fetchVideoAsBuffer(videoUrl);

const tmpDir = path.join(os.tmpdir(), "video_processing");
await fs.mkdir(tmpDir, { recursive: true });

const videoPath = path.join(tmpDir, `input_${Date.now()}.mp4`);
const srtPath = path.join(tmpDir, `subs_${Date.now()}.srt`);
const outputPath = path.join(tmpDir, `output_${Date.now()}.mp4`);

await fs.writeFile(videoPath, videoBuffer);
console.log("πŸ’Ύ Saved video to:", videoPath);

const srtContent = convertJsonToSrt(subtitles);
await fs.writeFile(srtPath, srtContent, "utf-8");

const ffmpeg = configureFFmpeg();

const safeSubtitlePath = srtPath
.replace(/\\/g, "/")
.replace(/:/g, "\\:");


await new Promise<void>((resolve, reject) => {
ffmpeg(videoPath)
.outputOptions(["-c:v", "libx264", "-c:a", "copy"])
.videoFilter(`subtitles='${safeSubtitlePath}'`)
.on("start", (cmd) => console.log(" FFmpeg command:", cmd))
.on("progress", (p) => console.log(`⏳ FFmpeg progress: ${p.percent?.toFixed(2)}%`))
.on("end", () => {
console.log(" FFmpeg completed successfully");
resolve();
})
.on("error", (err) => {
console.error("Burn subtitle error:", err);
reject(err);
})
.save(outputPath);
});

const outputBuffer = await fs.readFile(outputPath);
console.log(" Output video size:", outputBuffer.length);

await Promise.allSettled([
fs.unlink(videoPath),
fs.unlink(srtPath),
fs.unlink(outputPath),
]);

return new NextResponse(outputBuffer, {
status: 200,
headers: {
"Content-Type": "video/mp4",
"Content-Disposition": "attachment; filename=video_with_subtitles.mp4",
},
});

} catch (error) {
console.error("BurnSubtitle route error:", error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "Unknown error" },
{ status: 500 }
);
}
}
Loading
Loading