StreamClipper AI Faz 1: ingestion + sinyal analizi + STT + altyapı iskeleti

yt-dlp headless capture (15dk segmentleme), ses peak + chat velocity sinyal
tespiti, OpenAI Whisper STT (kelime zaman damgalı), BullMQ/Redis/Postgres
altyapısı ve kanal durumu + transkript kütüphanesi gösteren Next.js panel.
LLM virality skorlama, render/crop/altyazı ve multi-platform dağıtım bu
fazın kapsamı dışında.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 14:36:50 +03:00
co-authored by Claude Sonnet 5
commit eccc74166a
44 changed files with 3897 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@streamclipper/db",
"private": true,
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"generate": "prisma generate",
"migrate": "prisma migrate dev",
"seed": "tsx prisma/seed.ts"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@prisma/client": "^6.3.0"
},
"devDependencies": {
"@types/node": "^22.10.0",
"prisma": "^6.3.0",
"tsx": "^4.19.2",
"typescript": "^5.7.0"
}
}
+78
View File
@@ -0,0 +1,78 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum RawSegmentStatus {
PENDING
PROCESSED
DISCARDED
}
enum CandidateSegmentStatus {
PENDING_STT
TRANSCRIBED
FAILED
}
model Channel {
id String @id @default(cuid())
name String
youtubeHandle String @map("youtube_handle")
channelId String @unique @map("channel_id")
isActive Boolean @default(true) @map("is_active")
lastCheckedAt DateTime? @map("last_checked_at")
createdAt DateTime @default(now()) @map("created_at")
sessions StreamSession[]
@@map("channels")
}
model StreamSession {
id String @id @default(cuid())
channelId String @map("channel_id")
channel Channel @relation(fields: [channelId], references: [id])
liveVideoId String? @map("live_video_id")
startedAt DateTime @default(now()) @map("started_at")
endedAt DateTime? @map("ended_at")
totalSegments Int @default(0) @map("total_segments")
segments RawSegment[]
@@map("stream_sessions")
}
model RawSegment {
id String @id @default(cuid())
sessionId String @map("session_id")
session StreamSession @relation(fields: [sessionId], references: [id])
filePath String @map("file_path")
duration Int
startedAt DateTime @map("started_at")
status RawSegmentStatus @default(PENDING)
createdAt DateTime @default(now()) @map("created_at")
candidates CandidateSegment[]
@@map("raw_segments")
}
model CandidateSegment {
id String @id @default(cuid())
rawSegmentId String @map("raw_segment_id")
rawSegment RawSegment @relation(fields: [rawSegmentId], references: [id])
startSec Int @map("start_sec")
endSec Int @map("end_sec")
audioPeakScore Float? @map("audio_peak_score")
chatVelocityScore Float? @map("chat_velocity_score")
transcriptJson Json? @map("transcript_json")
status CandidateSegmentStatus @default(PENDING_STT)
createdAt DateTime @default(now()) @map("created_at")
@@map("candidate_segments")
}
+23
View File
@@ -0,0 +1,23 @@
import { prisma } from "../src";
async function main() {
const channelId = process.env.SEED_CHANNEL_ID ?? "UCXXXXXXXXXXXXXXXXXXXXXX";
const channel = await prisma.channel.upsert({
where: { channelId },
update: {},
create: {
name: process.env.SEED_CHANNEL_NAME ?? "Test Kanal",
youtubeHandle: process.env.SEED_CHANNEL_HANDLE ?? "@test",
channelId,
isActive: true,
},
});
console.log("Seeded channel:", channel);
}
main()
.catch((err) => {
console.error(err);
process.exitCode = 1;
})
.finally(() => process.exit());
+14
View File
@@ -0,0 +1,14 @@
import { PrismaClient } from "@prisma/client";
declare global {
// eslint-disable-next-line no-var
var __prisma: PrismaClient | undefined;
}
export const prisma = global.__prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") {
global.__prisma = prisma;
}
export * from "@prisma/client";
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}