synops/maskinrommet/src/highlight.rs
vegard eb2628c6a1 Auto Highlight Reel: AI-kuratert klipp-pakke fra podcast-transkripsjon
Ny feature: highlight_extract-jobb som analyserer fullstendig
transkripsjon etter innspilling og finner 5-10 klippverdige øyeblikk
(humor, emosjon, sterke meninger, punchlines, narrative høydepunkter).

Komponenter:
- synops-highlight CLI: henter segmenter, kaller AI, oppretter klipp-noder
- maskinrommet/highlight.rs: jobbdispatcher med modellrouting
- Registrert i jobbkø-dispatcher som "highlight_extract"

Hvert klipp blir en content-node med metadata (tidsstempler, score,
foreslått teksting, thumbnail-sitat, hashtags) og derived_from-edge
til episoden. Bruker synops/high-modell via AI Gateway.

Ref: docs/proposals/auto_highlight_reel.md
2026-03-19 21:40:50 +00:00

100 lines
3.2 KiB
Rust

// Highlight-reel dispatcher — delegerer til synops-highlight CLI.
//
// Maskinrommet orkestrerer, CLI-verktøyet gjør jobben:
// henter transkripsjon, kaller AI for analyse, oppretter klipp-noder.
//
// Jobbtype: "highlight_extract"
// Payload: { "media_node_id": "<uuid>", "requested_by": "<uuid>",
// "collection_id": "<uuid>" (valgfri) }
//
// Ref: docs/proposals/auto_highlight_reel.md
// docs/retninger/unix_filosofi.md
use uuid::Uuid;
use crate::ai_admin;
use crate::cli_dispatch;
use crate::jobs::JobRow;
/// Synops-highlight binary path.
fn highlight_bin() -> String {
std::env::var("SYNOPS_HIGHLIGHT_BIN")
.unwrap_or_else(|_| "synops-highlight".to_string())
}
/// Handler for highlight_extract-jobber.
///
/// Spawner synops-highlight med --write for å gjøre alt arbeidet:
/// transkripsjonshenting, AI-analyse, klipp-node-opprettelse.
///
/// Payload forventer:
/// - media_node_id: UUID — episodenoden med transkripsjon
/// - requested_by: UUID — brukeren som utløste highlight-analysen
/// - collection_id: UUID (valgfri) — podcast-samling for belongs_to-edge
pub async fn handle_highlight_extract(
job: &JobRow,
db: &sqlx::PgPool,
) -> Result<serde_json::Value, String> {
let media_node_id: Uuid = job
.payload
.get("media_node_id")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.ok_or("Mangler gyldig media_node_id i payload")?;
let requested_by: Uuid = job
.payload
.get("requested_by")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.ok_or("Mangler gyldig requested_by i payload")?;
let collection_id: Option<Uuid> = job
.payload
.get("collection_id")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok());
// Bygg kommando
let bin = highlight_bin();
let mut cmd = tokio::process::Command::new(&bin);
cmd.arg("--media-node-id")
.arg(media_node_id.to_string())
.arg("--requested-by")
.arg(requested_by.to_string())
.arg("--write");
if let Some(coll_id) = collection_id {
cmd.arg("--collection-id").arg(coll_id.to_string());
}
// Sett miljøvariabler CLI-verktøyet trenger
cli_dispatch::set_database_url(&mut cmd)?;
cli_dispatch::forward_env(&mut cmd, "AI_GATEWAY_URL");
cli_dispatch::forward_env(&mut cmd, "LITELLM_MASTER_KEY");
// Modellalias fra ai_job_routing — admin kan endre uten redeploy
let model_alias = ai_admin::resolve_routing_or_default(db, "highlight").await;
cmd.env("AI_HIGHLIGHT_MODEL", &model_alias);
tracing::info!(
media_node_id = %media_node_id,
requested_by = %requested_by,
collection_id = ?collection_id,
bin = %bin,
"Starter synops-highlight"
);
let result = cli_dispatch::run_cli_tool(&bin, &mut cmd).await?;
tracing::info!(
media_node_id = %media_node_id,
clips_created = result["clips_created"].as_u64().unwrap_or(0),
highlights_found = result["highlights_found"].as_u64().unwrap_or(0),
status = result["status"].as_str().unwrap_or("unknown"),
"synops-highlight fullført"
);
Ok(result)
}