feat: analiz zincirini atlayıp ham segmenti doğrudan Telegram'a gönderme modu
Yeni "Ham Kayıt Modu" ayarı (send_raw_segments_to_telegram, varsayılan
kapalı): açıksa her ham segment tamamlanır tamamlanmaz sinyal analizi/
STT/9:16 render zincirine hiç girmeden doğrudan Telegram'a gönderiliyor
ve sunucudan siliniyor. Kullanıcı transkript/9:16 istemediğinde ("sen
bana direk videoyu gönder") tüm analiz pipeline'ını atlayıp sadece
kayıt+gönder+sil yapan basit bir mod.
- RawSegmentStatus'a SENT_TO_TELEGRAM eklendi, RawSegment.filePath
nullable yapıldı (aynı ShortVideo.filePath deseni — gönderim sonrası
null, dosya diskten silinmiş demek).
- api-daemon/src/telegram.ts'e sendTelegramVideo eklendi (native fetch+
FormData+Blob ile multipart upload, ~50MB bot-limiti önceden kontrol
ediliyor) — worker/telegram.py'deki aynı fonksiyonun Node karşılığı.
- streamIngest.ts: segment tamamlanınca, mod açıksa signal-detection
kuyruğuna hiç eklemeden doğrudan gönderiyor (kuyruğa eklemek, aşağıda
dosyayı silmenin sinyal analizini bozmasına sebep olurdu).
- Panel: segment listelerinde filePath=null durumunda video/indir yerine
"Telegram'a gönderildi" notu (segments, channels/[id]).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,7 @@ export async function deleteRawSegment(segmentId: string) {
|
||||
await deleteShortsForCandidates(segment.candidates.map((c) => c.id));
|
||||
await prisma.candidateSegment.deleteMany({ where: { rawSegmentId: segmentId } });
|
||||
await prisma.rawSegment.delete({ where: { id: segmentId } });
|
||||
await unlink(segment.filePath).catch(() => {});
|
||||
if (segment.filePath) await unlink(segment.filePath).catch(() => {});
|
||||
|
||||
revalidatePath("/segments");
|
||||
revalidatePath("/", "layout");
|
||||
@@ -61,7 +61,7 @@ export async function deleteChannel(channelId: string) {
|
||||
});
|
||||
const rawSegmentIds = sessions.flatMap((s) => s.segments.map((seg) => seg.id));
|
||||
const candidateIds = sessions.flatMap((s) => s.segments.flatMap((seg) => seg.candidates.map((c) => c.id)));
|
||||
const filePaths = sessions.flatMap((s) => s.segments.map((seg) => seg.filePath));
|
||||
const filePaths = sessions.flatMap((s) => s.segments.map((seg) => seg.filePath)).filter((p): p is string => Boolean(p));
|
||||
|
||||
await deleteShortsForCandidates(candidateIds);
|
||||
await prisma.candidateSegment.deleteMany({ where: { rawSegmentId: { in: rawSegmentIds } } });
|
||||
@@ -175,6 +175,16 @@ export async function updateDeleteAfterTelegramSend(formData: FormData) {
|
||||
revalidatePath("/settings");
|
||||
}
|
||||
|
||||
export async function updateSendRawSegmentsToTelegram(formData: FormData) {
|
||||
const enabled = formData.get("sendRawSegmentsToTelegram") === "true";
|
||||
await prisma.appSetting.upsert({
|
||||
where: { key: "send_raw_segments_to_telegram" },
|
||||
create: { key: "send_raw_segments_to_telegram", value: String(enabled) },
|
||||
update: { value: String(enabled) },
|
||||
});
|
||||
revalidatePath("/settings");
|
||||
}
|
||||
|
||||
export async function updateNotificationPrefs(formData: FormData) {
|
||||
const streamStart = formData.get("notifyStreamStart") === "true";
|
||||
const renderDone = formData.get("notifyRenderDone") === "true";
|
||||
|
||||
@@ -6,7 +6,7 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
const { id } = await params;
|
||||
|
||||
const segment = await prisma.rawSegment.findUnique({ where: { id } });
|
||||
if (!segment) return new Response("Not found", { status: 404 });
|
||||
if (!segment?.filePath) return new Response("Not found", { status: 404 });
|
||||
|
||||
return streamVideoFile(req, segment.filePath, `${id}.mp4`);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ const STATUS_BADGE: Record<string, VariantProps<typeof badgeVariants>["variant"]
|
||||
PENDING: "muted",
|
||||
PROCESSED: "ok",
|
||||
DISCARDED: "err",
|
||||
SENT_TO_TELEGRAM: "ok",
|
||||
PENDING_STT: "warn",
|
||||
TRANSCRIBED: "ok",
|
||||
RENDERING: "warn",
|
||||
@@ -225,30 +226,43 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
|
||||
</span>
|
||||
<Badge variant={STATUS_BADGE[segment.status] ?? "muted"}>{segment.status}</Badge>
|
||||
</div>
|
||||
<span className="font-mono-num text-[0.7rem] text-muted-foreground">{segment.filePath}</span>
|
||||
|
||||
<video controls preload="metadata" className="mt-2 w-full max-w-md rounded-md border border-border">
|
||||
<source src={`/api/segments/${segment.id}/video`} type="video/mp4" />
|
||||
</video>
|
||||
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<a href={`/api/segments/${segment.id}/video?download=1`}>İndir</a>
|
||||
</Button>
|
||||
<form action={deleteRawSegment.bind(null, segment.id)}>
|
||||
<ConfirmButton
|
||||
confirmText="Bu segmenti ve içindeki tüm aday klipleri silmek istediğine emin misin? Geri alınamaz."
|
||||
label="Sil"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{segment.candidates.length === 0 && segment.status !== "PENDING" && (
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
Bu segmentte aday klip bulunamadı (sinyal analizi ilginç bir an tespit etmedi).
|
||||
{segment.filePath ? (
|
||||
<>
|
||||
<span className="font-mono-num text-[0.7rem] text-muted-foreground">{segment.filePath}</span>
|
||||
<video controls preload="metadata" className="mt-2 w-full max-w-md rounded-md border border-border">
|
||||
<source src={`/api/segments/${segment.id}/video`} type="video/mp4" />
|
||||
</video>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<a href={`/api/segments/${segment.id}/video?download=1`}>İndir</a>
|
||||
</Button>
|
||||
<form action={deleteRawSegment.bind(null, segment.id)}>
|
||||
<ConfirmButton
|
||||
confirmText="Bu segmenti ve içindeki tüm aday klipleri silmek istediğine emin misin? Geri alınamaz."
|
||||
label="Sil"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
✅ Telegram'a gönderildi, sunucuda saklanmıyor.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{segment.status === "SENT_TO_TELEGRAM" ? (
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
Analiz edilmeden (transkript/9:16 atlanarak) doğrudan gönderildi.
|
||||
</p>
|
||||
) : (
|
||||
segment.candidates.length === 0 &&
|
||||
segment.status !== "PENDING" && (
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
Bu segmentte aday klip bulunamadı (sinyal analizi ilginç bir an tespit etmedi).
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
|
||||
{segment.candidates.length > 0 && (
|
||||
<div className="mt-3 flex flex-col gap-2">
|
||||
{segment.candidates.map((c) => (
|
||||
|
||||
@@ -13,6 +13,7 @@ const STATUS_BADGE: Record<string, VariantProps<typeof badgeVariants>["variant"]
|
||||
PENDING: "muted",
|
||||
PROCESSED: "ok",
|
||||
DISCARDED: "err",
|
||||
SENT_TO_TELEGRAM: "ok",
|
||||
PENDING_STT: "warn",
|
||||
TRANSCRIBED: "ok",
|
||||
RENDERING: "warn",
|
||||
@@ -53,23 +54,32 @@ export default async function SegmentsPage() {
|
||||
<Badge variant={STATUS_BADGE[segment.status] ?? "muted"}>{segment.status}</Badge>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-3">
|
||||
<video controls preload="metadata" className="w-full max-w-md rounded-md border border-border">
|
||||
<source src={`/api/segments/${segment.id}/video`} type="video/mp4" />
|
||||
</video>
|
||||
{segment.filePath ? (
|
||||
<>
|
||||
<video controls preload="metadata" className="w-full max-w-md rounded-md border border-border">
|
||||
<source src={`/api/segments/${segment.id}/video`} type="video/mp4" />
|
||||
</video>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<a href={`/api/segments/${segment.id}/video?download=1`}>İndir</a>
|
||||
</Button>
|
||||
<form action={deleteRawSegment.bind(null, segment.id)}>
|
||||
<ConfirmButton
|
||||
confirmText="Bu segmenti ve içindeki tüm aday klipleri silmek istediğine emin misin? Geri alınamaz."
|
||||
label="Sil"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">✅ Telegram'a gönderildi, sunucuda saklanmıyor.</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<a href={`/api/segments/${segment.id}/video?download=1`}>İndir</a>
|
||||
</Button>
|
||||
<form action={deleteRawSegment.bind(null, segment.id)}>
|
||||
<ConfirmButton
|
||||
confirmText="Bu segmenti ve içindeki tüm aday klipleri silmek istediğine emin misin? Geri alınamaz."
|
||||
label="Sil"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{segment.candidates.length === 0 ? (
|
||||
{segment.status === "SENT_TO_TELEGRAM" ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Bu segment analiz edilmeden (transkript/9:16 atlanarak) doğrudan Telegram'a gönderildi.
|
||||
</p>
|
||||
) : segment.candidates.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">Bu segmentte aday klip bulunamadı.</p>
|
||||
) : (
|
||||
segment.candidates.map((c) => (
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
updateSttEnabled,
|
||||
updateAutoRenderEnabled,
|
||||
updateDeleteAfterTelegramSend,
|
||||
updateSendRawSegmentsToTelegram,
|
||||
addCookieProfile,
|
||||
updateCookieProfile,
|
||||
deleteCookieProfile,
|
||||
@@ -36,6 +37,7 @@ export default async function SettingsPage() {
|
||||
sttSetting,
|
||||
autoRenderSetting,
|
||||
deleteAfterSendSetting,
|
||||
sendRawSetting,
|
||||
cookieProfiles,
|
||||
] = await Promise.all([
|
||||
prisma.appSetting.findUnique({ where: { key: "ytdlp_cookies" } }),
|
||||
@@ -47,6 +49,7 @@ export default async function SettingsPage() {
|
||||
prisma.appSetting.findUnique({ where: { key: "stt_enabled" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "auto_render_enabled" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "delete_after_telegram_send" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "send_raw_segments_to_telegram" } }),
|
||||
prisma.cookieProfile.findMany({ orderBy: { createdAt: "asc" } }),
|
||||
]);
|
||||
|
||||
@@ -60,6 +63,7 @@ export default async function SettingsPage() {
|
||||
const sttEnabled = sttSetting?.value !== "false";
|
||||
const autoRenderEnabled = autoRenderSetting?.value !== "false";
|
||||
const deleteAfterTelegramSend = deleteAfterSendSetting?.value === "true";
|
||||
const sendRawSegmentsToTelegram = sendRawSetting?.value === "true";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
@@ -259,8 +263,33 @@ export default async function SettingsPage() {
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Depolama</CardTitle>
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Ham Kayıt Modu</CardTitle>
|
||||
<CardDescription>
|
||||
Açarsan her ham segment (analiz/transkript/9:16 zincirine hiç girmeden) tamamlanır tamamlanmaz doğrudan
|
||||
Telegram'a gönderilip sunucudan silinir — transkript ve 9:16 render tamamen atlanır. Bunu açtıysan
|
||||
STT ve 9:16 Render ayarlarının kapalı olması mantıklı (aksi halde ikisi de boşa kalır, çünkü bu mod
|
||||
segmenti analiz kuyruğuna hiç sokmuyor). ~50MB'ı geçen segmentler gönderilemez, sunucuda kalır —
|
||||
gerekirse yukarıdaki "Sistem Parametreleri"nden segment süresini kısalt.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<form action={updateSendRawSegmentsToTelegram}>
|
||||
<CardContent>
|
||||
<Label className="flex items-center justify-between gap-4 text-sm font-normal">
|
||||
Ham segmenti doğrudan Telegram'a gönder (analiz atlanır)
|
||||
<SettingSwitch name="sendRawSegmentsToTelegram" defaultChecked={sendRawSegmentsToTelegram} />
|
||||
</Label>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button type="submit">Kaydet</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Depolama (9:16 klipler)</CardTitle>
|
||||
<CardDescription>
|
||||
Yukarıdaki "Ham Kayıt Modu"ndan farklı — bu, STT+9:16 render zincirinden geçmiş klipler için.
|
||||
Açarsan her render biten video Telegram'a gönderilip <strong>sunucudan kalıcı olarak
|
||||
silinir</strong> — sadece Telegram'daki kopyada kalır (geri alınamaz). Gönderim başarısız olursa
|
||||
dosya sunucuda kalır. Kanal sayısı arttıkça disk doluluğunu önlemek için.
|
||||
|
||||
Reference in New Issue
Block a user