Compare commits

..
3 Commits
Author SHA1 Message Date
ayrisdevandClaude Sonnet 5 b5c761afbf fix: frontend production'da next dev yerine build+start çalışsın
Dockerfile'ın CMD'si next dev kullanıyordu — Coolify'de canlı sistemde
dev-mode overlay/bildirimleri (Next.js sürüm uyarısı, hata detayları) ve
optimize edilmemiş, JIT-compile edilen bir build çalışıyordu. Artık image
build sırasında next build çalışıyor, CMD sadece next start.

DATABASE_URL build adımında gerçek bir DB'ye ihtiyaç duymuyor (tüm
sayfalar force-dynamic, build sırasında sorgu atılmıyor) ama Prisma
client import'ta connection string formatını doğruluyor — yer tutucu bir
değer verildi, Coolify'nin gerçek DATABASE_URL'i runtime'da onun üzerine
yazıyor zaten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 22:59:53 +03:00
ayrisdevandClaude Sonnet 5 dea207996e fix: kanal detay sayfasında iç içe girinti (segment/aday klip) netleştirildi
3 seviyeli border-left girinti (oturum > segment > aday klip) yerine
gerçek kutu (soft-card) blokları — her segment kendi arka planıyla
ayrışıyor, adaylar onun içinde daha da net bir alt kutuda. Segment
başlığı artık ham dosya yolu değil ("15sn segment · 02.09.2026 20:14"
gibi) insan-okur bir etiket — dosya yolu küçük/soluk referans olarak
altta kaldı. "Canlı Durum" kartındaki karışık bölümler de (hata/canlı
önizleme/log/segment ayarı/manuel başlat) Separator ile ayrıştırıldı.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 22:58:06 +03:00
ayrisdevandClaude Sonnet 5 58ef40edf8 feat: segment süresi varsayılanını 15 dk -> 5 dk düşür + dashboard'u kart-grid'e çevir
Global SEGMENT_TIME_SEC varsayılanı 900->300 (docker-compose, .env.example,
env.ts, signal_detection.py fallback'i) — kanal bazlı override zaten
panelden ayarlanabiliyordu, bu sadece varsayılanı değiştiriyor.

Dashboard'daki kanal listesi tablo yerine kart-grid: her kanal kendi
kartında, durum rozetleri üstte belirgin, aksiyonlar altta — önceki
tablo düzeni (özellikle aksiyon sütunundaki sıkışık butonlar) okunması
zor bulunuyordu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 22:55:12 +03:00
7 changed files with 195 additions and 180 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID= TELEGRAM_CHAT_ID=
POLL_INTERVAL_MS=60000 POLL_INTERVAL_MS=60000
SEGMENT_TIME_SEC=900 SEGMENT_TIME_SEC=300
API_DAEMON_PORT=4001 API_DAEMON_PORT=4001
# Signs the panel's login session cookies (frontend). Generate with: # Signs the panel's login session cookies (frontend). Generate with:
+1 -1
View File
@@ -4,7 +4,7 @@ export const env = {
redisUrl: process.env.REDIS_URL ?? "redis://localhost:6379", redisUrl: process.env.REDIS_URL ?? "redis://localhost:6379",
sharedMediaRoot: process.env.SHARED_MEDIA_ROOT ?? "./shared-media", sharedMediaRoot: process.env.SHARED_MEDIA_ROOT ?? "./shared-media",
pollIntervalMs: Number(process.env.POLL_INTERVAL_MS ?? 60_000), pollIntervalMs: Number(process.env.POLL_INTERVAL_MS ?? 60_000),
segmentTimeSec: Number(process.env.SEGMENT_TIME_SEC ?? 900), segmentTimeSec: Number(process.env.SEGMENT_TIME_SEC ?? 300),
port: Number(process.env.API_DAEMON_PORT ?? 4001), port: Number(process.env.API_DAEMON_PORT ?? 4001),
forceLiveUrl: process.env.FORCE_LIVE_URL ?? "", forceLiveUrl: process.env.FORCE_LIVE_URL ?? "",
ytdlpPotProviderUrl: process.env.YTDLP_POT_PROVIDER_URL ?? "", ytdlpPotProviderUrl: process.env.YTDLP_POT_PROVIDER_URL ?? "",
+9 -1
View File
@@ -7,4 +7,12 @@ COPY . .
RUN pnpm install RUN pnpm install
RUN pnpm --filter @streamclipper/db exec prisma generate RUN pnpm --filter @streamclipper/db exec prisma generate
CMD ["pnpm", "--filter", "@streamclipper/frontend", "dev"] # DATABASE_URL isn't needed for real at build time (all pages are
# force-dynamic — nothing queries the DB during `next build`), but
# Prisma's client validates the connection string format at import, so a
# well-formed placeholder keeps the build step from failing. Coolify's
# actual DATABASE_URL is supplied as a runtime env var and overrides this.
ENV DATABASE_URL="postgresql://build:build@localhost:5432/build"
RUN pnpm --filter @streamclipper/frontend build
CMD ["pnpm", "--filter", "@streamclipper/frontend", "start"]
+117 -105
View File
@@ -21,6 +21,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Badge, type badgeVariants } from "@/components/ui/badge"; import { Badge, type badgeVariants } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import type { VariantProps } from "class-variance-authority"; import type { VariantProps } from "class-variance-authority";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -111,40 +112,12 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
)} )}
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-3"> <CardContent className="flex flex-col gap-4">
<p className="font-mono-num text-xs text-muted-foreground"> <p className="font-mono-num text-xs text-muted-foreground">
Aktif: {channel.isActive ? "evet" : "hayır"} · Son kontrol:{" "} Aktif: {channel.isActive ? "evet" : "hayır"} · Son kontrol:{" "}
{channel.lastCheckedAt ? formatTr(channel.lastCheckedAt) : "—"} {channel.lastCheckedAt ? formatTr(channel.lastCheckedAt) : "—"}
</p> </p>
<form
action={updateChannelSegmentTime.bind(null, channel.id)}
className="flex flex-wrap items-end gap-2"
>
<div className="flex flex-col gap-1.5">
<Label htmlFor="segmentTimeMin" className="text-xs text-muted-foreground">
Bu kanal için segment süresi (dakika)
</Label>
<Input
id="segmentTimeMin"
name="segmentTimeMin"
type="number"
min={1}
max={60}
step={0.5}
placeholder="varsayılan (15)"
defaultValue={channel.segmentTimeSec ? channel.segmentTimeSec / 60 : ""}
className="w-40"
/>
</div>
<Button type="submit" variant="outline" size="sm">
Kaydet
</Button>
<span className="text-xs text-muted-foreground">
Boş bırakılırsa global varsayılan kullanılır. Sadece bundan sonra başlayacak kayıtları etkiler.
</span>
</form>
{status === undefined && ( {status === undefined && (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
api-daemon&apos;dan canlı durum alınamadı (servis erişilemez olabilir). api-daemon&apos;dan canlı durum alınamadı (servis erişilemez olabilir).
@@ -152,7 +125,7 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
)} )}
{status?.lastPollError && ( {status?.lastPollError && (
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5 rounded-lg border border-err/30 bg-err/5 p-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge variant="err" className="w-fit"> <Badge variant="err" className="w-fit">
son poll hatası son poll hatası
@@ -161,32 +134,64 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
{formatTr(status.lastPollError.at)} {formatTr(status.lastPollError.at)}
</span> </span>
</div> </div>
<pre className="font-mono-num whitespace-pre-wrap rounded-md border border-border bg-background/60 p-3 text-[0.7rem] text-muted-foreground"> <pre className="font-mono-num whitespace-pre-wrap text-[0.7rem] text-muted-foreground">
{status.lastPollError.message} {status.lastPollError.message}
</pre> </pre>
</div> </div>
)} )}
{status?.recording && activeSession?.liveVideoId && activeSession.liveVideoId !== "forced" && ( {status?.recording && (
<iframe <div className="flex flex-col gap-3">
src={`https://www.youtube.com/embed/${activeSession.liveVideoId}`} {activeSession?.liveVideoId && activeSession.liveVideoId !== "forced" && (
title="Canlı yayın önizleme" <iframe
className="aspect-video w-full max-w-md rounded-md border border-border" src={`https://www.youtube.com/embed/${activeSession.liveVideoId}`}
allow="autoplay; encrypted-media" title="Canlı yayın önizleme"
/> className="aspect-video w-full max-w-md rounded-md border border-border"
allow="autoplay; encrypted-media"
/>
)}
{activeSession && <LiveLogViewer sessionId={activeSession.id} />}
</div>
)} )}
{status?.recording && activeSession && <LiveLogViewer sessionId={activeSession.id} />} <Separator />
<details className="mt-1 text-sm"> <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<summary className="cursor-pointer font-mono-num text-xs text-muted-foreground"> <form
Manuel URL ile kayıt başlat action={updateChannelSegmentTime.bind(null, channel.id)}
</summary> className="flex flex-wrap items-end gap-2"
<form action={forceStartCapture.bind(null, channel.id)} className="mt-2 flex gap-2"> >
<Input name="url" placeholder="https://www.youtube.com/watch?v=..." required className="flex-1" /> <div className="flex flex-col gap-1.5">
<Button type="submit">Başlat</Button> <Label htmlFor="segmentTimeMin" className="text-xs text-muted-foreground">
Bu kanal için segment süresi (dakika)
</Label>
<Input
id="segmentTimeMin"
name="segmentTimeMin"
type="number"
min={1}
max={60}
step={0.5}
placeholder="varsayılan"
defaultValue={channel.segmentTimeSec ? channel.segmentTimeSec / 60 : ""}
className="w-32"
/>
</div>
<Button type="submit" variant="outline" size="sm">
Kaydet
</Button>
</form> </form>
</details>
<details className="text-sm">
<summary className="cursor-pointer font-mono-num text-xs text-muted-foreground">
Manuel URL ile kayıt başlat
</summary>
<form action={forceStartCapture.bind(null, channel.id)} className="mt-2 flex gap-2">
<Input name="url" placeholder="https://www.youtube.com/watch?v=..." required className="w-64" />
<Button type="submit">Başlat</Button>
</form>
</details>
</div>
</CardContent> </CardContent>
</Card> </Card>
@@ -213,11 +218,14 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
) : ( ) : (
<CardContent className="flex flex-col gap-4"> <CardContent className="flex flex-col gap-4">
{session.segments.map((segment) => ( {session.segments.map((segment) => (
<div key={segment.id} className="border-l-2 border-border pl-4"> <div key={segment.id} className="rounded-lg border border-border bg-muted/20 p-4">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<span className="font-mono-num text-xs text-muted-foreground">{segment.filePath}</span> <span className="text-sm font-medium">
{segment.duration}sn segment · {formatTr(segment.startedAt)}
</span>
<Badge variant={STATUS_BADGE[segment.status] ?? "muted"}>{segment.status}</Badge> <Badge variant={STATUS_BADGE[segment.status] ?? "muted"}>{segment.status}</Badge>
</div> </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"> <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" /> <source src={`/api/segments/${segment.id}/video`} type="video/mp4" />
@@ -236,73 +244,77 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
</div> </div>
{segment.candidates.length === 0 && segment.status !== "PENDING" && ( {segment.candidates.length === 0 && segment.status !== "PENDING" && (
<p className="mt-2 text-xs text-muted-foreground"> <p className="mt-3 text-xs text-muted-foreground">
Bu segmentte aday klip bulunamadı (sinyal analizi ilginç bir an tespit etmedi). Bu segmentte aday klip bulunamadı (sinyal analizi ilginç bir an tespit etmedi).
</p> </p>
)} )}
{segment.candidates.map((c) => ( {segment.candidates.length > 0 && (
<div key={c.id} className="mt-3 border-l-2 border-border pl-4"> <div className="mt-3 flex flex-col gap-2">
<div className="flex items-center justify-between gap-2"> {segment.candidates.map((c) => (
<span className="font-mono-num text-xs text-muted-foreground"> <div key={c.id} className="rounded-md border border-border bg-background/60 p-3">
{c.startSec}s {c.endSec}s <div className="flex items-center justify-between gap-2">
{c.audioPeakScore != null && ` · peak ${c.audioPeakScore.toFixed(1)}dB`} <span className="font-mono-num text-xs text-muted-foreground">
</span> {c.startSec}s {c.endSec}s
<Badge variant={STATUS_BADGE[c.status] ?? "muted"}>{c.status}</Badge> {c.audioPeakScore != null && ` · peak ${c.audioPeakScore.toFixed(1)}dB`}
</div> </span>
{transcriptPreview(c.transcriptJson) && ( <Badge variant={STATUS_BADGE[c.status] ?? "muted"}>{c.status}</Badge>
<p className="mt-1.5 text-sm leading-relaxed">{transcriptPreview(c.transcriptJson)}</p> </div>
)} {transcriptPreview(c.transcriptJson) && (
<p className="mt-1.5 text-sm leading-relaxed">{transcriptPreview(c.transcriptJson)}</p>
)}
{c.short && ( {c.short && (
<div className="mt-2 flex flex-col items-start gap-2"> <div className="mt-2 flex flex-col items-start gap-2">
<Badge variant={STATUS_BADGE[c.short.status] ?? "muted"}>9:16: {c.short.status}</Badge> <Badge variant={STATUS_BADGE[c.short.status] ?? "muted"}>9:16: {c.short.status}</Badge>
{c.short.status === "READY" && ( {c.short.status === "READY" && (
<> <>
<video controls preload="metadata" className="w-[220px] rounded-md border border-border"> <video controls preload="metadata" className="w-[220px] rounded-md border border-border">
<source src={`/api/shorts/${c.short.id}/video`} type="video/mp4" /> <source src={`/api/shorts/${c.short.id}/video`} type="video/mp4" />
</video> </video>
<Button asChild variant="outline" size="sm"> <Button asChild variant="outline" size="sm">
<a href={`/api/shorts/${c.short.id}/video?download=1`}>İndir</a> <a href={`/api/shorts/${c.short.id}/video?download=1`}>İndir</a>
</Button> </Button>
</> </>
)}
{c.short.status === "FAILED" && (
<>
{c.short.errorMessage && (
<p className="font-mono-num text-xs text-muted-foreground">{c.short.errorMessage}</p>
)} )}
<form action={retryRender.bind(null, c.id)}> {c.short.status === "FAILED" && (
<Button type="submit" size="sm"> <>
Tekrar Dene {c.short.errorMessage && (
</Button> <p className="font-mono-num text-xs text-muted-foreground">{c.short.errorMessage}</p>
</form> )}
</> <form action={retryRender.bind(null, c.id)}>
<Button type="submit" size="sm">
Tekrar Dene
</Button>
</form>
</>
)}
{c.short.status === "RENDERING" && (
<form action={cancelRender.bind(null, c.id)}>
<ConfirmButton
confirmText="Render'ı başarısız say ve kilidi aç? (Çalışan process'i öldürmez, sadece durumu sıfırlar)"
label="Takıldıysa İptal Et"
variant="outline"
/>
</form>
)}
</div>
)} )}
{c.short.status === "RENDERING" && ( {!c.short && c.status === "TRANSCRIBED" && (
<form action={cancelRender.bind(null, c.id)}> <form action={retryRender.bind(null, c.id)} className="mt-2">
<ConfirmButton <Button type="submit" size="sm">
confirmText="Render'ı başarısız say ve kilidi aç? (Çalışan process'i öldürmez, sadece durumu sıfırlar)" 9:16 Render Et
label="Takıldıysa İptal Et" </Button>
variant="outline"
/>
</form> </form>
)} )}
</div>
)}
{!c.short && c.status === "TRANSCRIBED" && (
<form action={retryRender.bind(null, c.id)} className="mt-2">
<Button type="submit" size="sm">
9:16 Render Et
</Button>
</form>
)}
<form action={deleteCandidateSegment.bind(null, c.id)} className="mt-2"> <form action={deleteCandidateSegment.bind(null, c.id)} className="mt-2">
<ConfirmButton confirmText="Bu aday klibi silmek istediğine emin misin?" label="Sil" /> <ConfirmButton confirmText="Bu aday klibi silmek istediğine emin misin?" label="Sil" />
</form> </form>
</div>
))}
</div> </div>
))} )}
</div> </div>
))} ))}
</CardContent> </CardContent>
+64 -69
View File
@@ -6,8 +6,8 @@ import { formatTr } from "../lib/formatDate";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { cn } from "@/lib/utils";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -47,9 +47,9 @@ export default async function DashboardPage() {
{channels.length === 0 ? ( {channels.length === 0 ? (
<p className="text-sm text-muted-foreground">Henüz kanal eklenmedi.</p> <p className="text-sm text-muted-foreground">Henüz kanal eklenmedi.</p>
) : ( ) : (
<Card> <div className="flex flex-col gap-3">
<CardHeader className="flex-row items-center justify-between"> <div className="flex items-center justify-between">
<CardTitle className="text-sm font-medium text-muted-foreground">{channels.length} kanal</CardTitle> <span className="text-sm font-medium text-muted-foreground">{channels.length} kanal</span>
<div className="flex gap-2"> <div className="flex gap-2">
<form action={toggleAllChannels.bind(null, true)}> <form action={toggleAllChannels.bind(null, true)}>
<Button type="submit" variant="outline" size="sm"> <Button type="submit" variant="outline" size="sm">
@@ -62,70 +62,65 @@ export default async function DashboardPage() {
</Button> </Button>
</form> </form>
</div> </div>
</CardHeader> </div>
<CardContent>
<Table> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<TableHeader> {channels.map((c) => {
<TableRow> const recording = c.sessions.length > 0;
<TableHead>Kanal</TableHead> const status = statuses.get(c.id);
<TableHead>Durum</TableHead> return (
<TableHead>Kayıt</TableHead> <Card
<TableHead>Son Kontrol</TableHead> key={c.id}
<TableHead className="text-right">Aksiyon</TableHead> className={cn("gap-4 py-5", recording && "border-live/40")}
</TableRow> >
</TableHeader> <CardHeader className="gap-1 px-5">
<TableBody> <div className="flex items-start justify-between gap-2">
{channels.map((c) => { <Link href={`/channels/${c.id}`} className="font-semibold leading-tight hover:text-primary hover:underline">
const recording = c.sessions.length > 0; {c.name}
const status = statuses.get(c.id); </Link>
return ( <Badge variant={c.isActive ? "ok" : "muted"} className="shrink-0">
<TableRow key={c.id}> {c.isActive ? "Aktif" : "Pasif"}
<TableCell> </Badge>
<Link href={`/channels/${c.id}`} className="font-medium hover:text-primary hover:underline"> </div>
{c.name} <span className="font-mono-num text-xs text-muted-foreground">{c.youtubeHandle}</span>
</Link>{" "} </CardHeader>
<span className="font-mono-num text-xs text-muted-foreground">{c.youtubeHandle}</span>
</TableCell> <CardContent className="flex flex-col gap-2 px-5">
<TableCell> <div className="flex flex-wrap items-center gap-1.5">
<Badge variant={c.isActive ? "ok" : "muted"}>{c.isActive ? "Aktif" : "Pasif"}</Badge> <Badge variant={recording ? "live" : "muted"}>
</TableCell> {recording ? "🔴 Kayıtta" : "Kayıtta değil"}
<TableCell> </Badge>
<div className="flex items-center gap-1.5"> {status?.lastPollError && (
<Badge variant={recording ? "live" : "muted"}>{recording ? "🔴 Kayıtta" : "—"}</Badge> <Badge
{status?.lastPollError && ( variant="err"
<Badge title={`${formatTr(status.lastPollError.at)}${status.lastPollError.message}`}
variant="err" >
title={`${formatTr(status.lastPollError.at)}${status.lastPollError.message}`} hata
> </Badge>
hata )}
</Badge> </div>
)} <span className="font-mono-num text-xs text-muted-foreground">
</div> Son kontrol: {c.lastCheckedAt ? formatTr(c.lastCheckedAt) : "—"}
</TableCell> </span>
<TableCell className="font-mono-num text-xs text-muted-foreground"> </CardContent>
{c.lastCheckedAt ? formatTr(c.lastCheckedAt) : "—"}
</TableCell> <CardFooter className="gap-2 px-5">
<TableCell> <form action={toggleChannelActive.bind(null, c.id, !c.isActive)}>
<div className="flex justify-end gap-1.5"> <Button type="submit" variant="outline" size="sm">
<form action={toggleChannelActive.bind(null, c.id, !c.isActive)}> {c.isActive ? "Duraklat" : "Devam Ettir"}
<Button type="submit" variant="outline" size="sm"> </Button>
{c.isActive ? "Duraklat" : "Devam Ettir"} </form>
</Button> <form action={checkChannelNow.bind(null, c.id)}>
</form> <Button type="submit" variant="ghost" size="sm">
<form action={checkChannelNow.bind(null, c.id)}> Şimdi Kontrol Et
<Button type="submit" variant="ghost" size="sm"> </Button>
Şimdi Kontrol Et </form>
</Button> </CardFooter>
</form> </Card>
</div> );
</TableCell> })}
</TableRow> </div>
); </div>
})}
</TableBody>
</Table>
</CardContent>
</Card>
)} )}
</div> </div>
); );
+1 -1
View File
@@ -17,7 +17,7 @@ WINDOW_SEC = 1.0
PEAK_STD_MULTIPLIER = 1.5 PEAK_STD_MULTIPLIER = 1.5
CANDIDATE_PAD_SEC = 45 CANDIDATE_PAD_SEC = 45
CHAT_SPIKE_MULTIPLIER = 3 CHAT_SPIKE_MULTIPLIER = 3
SEGMENT_TIME_SEC = int(os.environ.get("SEGMENT_TIME_SEC", "900")) SEGMENT_TIME_SEC = int(os.environ.get("SEGMENT_TIME_SEC", "300"))
stt_scoring_queue = Queue(QUEUE_NAMES["STT_SCORING"], {"connection": REDIS_URL}) stt_scoring_queue = Queue(QUEUE_NAMES["STT_SCORING"], {"connection": REDIS_URL})
+2 -2
View File
@@ -33,7 +33,7 @@ services:
DATABASE_URL: postgresql://streamclipper:streamclipper@sc_postgres:5432/streamclipper DATABASE_URL: postgresql://streamclipper:streamclipper@sc_postgres:5432/streamclipper
SHARED_MEDIA_ROOT: /shared-media SHARED_MEDIA_ROOT: /shared-media
POLL_INTERVAL_MS: ${POLL_INTERVAL_MS:-60000} POLL_INTERVAL_MS: ${POLL_INTERVAL_MS:-60000}
SEGMENT_TIME_SEC: ${SEGMENT_TIME_SEC:-900} SEGMENT_TIME_SEC: ${SEGMENT_TIME_SEC:-300}
API_DAEMON_PORT: ${API_DAEMON_PORT:-4001} API_DAEMON_PORT: ${API_DAEMON_PORT:-4001}
FORCE_LIVE_URL: ${FORCE_LIVE_URL:-} FORCE_LIVE_URL: ${FORCE_LIVE_URL:-}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
@@ -61,7 +61,7 @@ services:
REDIS_URL: redis://sc_redis:6379 REDIS_URL: redis://sc_redis:6379
DATABASE_URL: postgresql://streamclipper:streamclipper@sc_postgres:5432/streamclipper DATABASE_URL: postgresql://streamclipper:streamclipper@sc_postgres:5432/streamclipper
SHARED_MEDIA_ROOT: /shared-media SHARED_MEDIA_ROOT: /shared-media
SEGMENT_TIME_SEC: ${SEGMENT_TIME_SEC:-900} SEGMENT_TIME_SEC: ${SEGMENT_TIME_SEC:-300}
OPENAI_API_KEY: ${OPENAI_API_KEY:-} OPENAI_API_KEY: ${OPENAI_API_KEY:-}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-} TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}