From 381c086eba946bff71f9c58a9aa8280823ce445d Mon Sep 17 00:00:00 2001 From: aleidk Date: Sun, 19 Oct 2025 17:18:08 -0300 Subject: [PATCH] feat: schedule content --- src/config.rs | 2 +- src/ersatz_tv.rs | 83 ++++++++++++++++++++++++++++++++++++++--- src/ersatz_tv/models.rs | 39 +++++++++++++++++++ src/jellyfin.rs | 2 - src/jellyfin/models.rs | 2 +- src/main.rs | 38 +++++++++++++++---- 6 files changed, 150 insertions(+), 16 deletions(-) diff --git a/src/config.rs b/src/config.rs index c430f75..279bfa0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,7 @@ pub struct Args { /// URL host of the ErsatzTv service pub host: String, /// UUID of the build - pub build_id: Uuid, + pub build_id: String, /// The playout build mode pub playout_mode: PlayoutMode, } diff --git a/src/ersatz_tv.rs b/src/ersatz_tv.rs index f95305e..8e4a3a5 100644 --- a/src/ersatz_tv.rs +++ b/src/ersatz_tv.rs @@ -1,27 +1,32 @@ use anyhow::anyhow; use uuid::Uuid; -use crate::ersatz_tv::models::ContextModel; +use crate::ersatz_tv::models::{ContextModel, ScheduleAllContentDto, SearchQueryDto}; pub mod models; pub struct Client { _client: reqwest::Client, host: String, + build_id: String, } impl Client { - pub fn new(host: String) -> anyhow::Result { + pub fn new(host: &String, build_id: &String) -> anyhow::Result { let _client = reqwest::Client::builder().gzip(true).build()?; - Ok(Self { _client, host }) + Ok(Self { + _client, + host: host.clone(), + build_id: build_id.clone(), + }) } - pub async fn get_context(&self, build_id: Uuid) -> anyhow::Result { + pub async fn get_context(&self) -> anyhow::Result { let response = self ._client .get(format!( "{}/api/scripted/playout/build/{}/context", - self.host, build_id + self.host, self.build_id )) .send() .await?; @@ -39,4 +44,72 @@ impl Client { Ok(context) } + + async fn _load_content(&self, content: &SearchQueryDto) -> anyhow::Result<()> { + let response = self + ._client + .post(format!( + "{}/api/scripted/playout/build/{}/add_search", + self.host, self.build_id + )) + .json(content) + .send() + .await?; + + if let Err(err) = &response.error_for_status_ref() { + return Err(anyhow!( + "Request for {} returned with status {}:\n{}", + err.url().unwrap().path(), + err.status().unwrap_or_default(), + response.text().await? + )); + } + + Ok(()) + } + + pub async fn load_content>( + &self, + content: T, + ) -> anyhow::Result<()> { + for item in content { + self._load_content(&item).await?; + } + + Ok(()) + } + + async fn _schedule_content(&self, content: &ScheduleAllContentDto) -> anyhow::Result<()> { + let response = self + ._client + .post(format!( + "{}/api/scripted/playout/build/{}/add_all", + self.host, self.build_id + )) + .json(content) + .send() + .await?; + + if let Err(err) = &response.error_for_status_ref() { + return Err(anyhow!( + "Request for {} returned with status {}:\n{}", + err.url().unwrap().path(), + err.status().unwrap_or_default(), + response.text().await? + )); + } + + Ok(()) + } + + pub async fn schedule_content>( + &self, + content: T, + ) -> anyhow::Result<()> { + for item in content { + self._schedule_content(&item).await?; + } + + Ok(()) + } } diff --git a/src/ersatz_tv/models.rs b/src/ersatz_tv/models.rs index df88649..afdffe5 100644 --- a/src/ersatz_tv/models.rs +++ b/src/ersatz_tv/models.rs @@ -8,3 +8,42 @@ pub struct ContextModel { finish_time: chrono::DateTime, is_done: bool, } + +#[derive(Debug, Serialize, Deserialize)] +pub struct SearchQueryDto { + pub key: String, + pub order: SearchQueryOrder, + pub query: SearchQueryValue, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleAllContentDto { + #[serde(rename = "content")] + pub key: String, + pub custom_title: String, + pub disable_watermarks: bool, + pub filler_kind: FillerKind, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SearchQueryValue { + SongTitle(String), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SearchQueryOrder { + Chronological, + Shuffle, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum FillerKind { + None, + Preroll, + Midroll, + Postroll, +} diff --git a/src/jellyfin.rs b/src/jellyfin.rs index 1487383..9244fd6 100644 --- a/src/jellyfin.rs +++ b/src/jellyfin.rs @@ -62,8 +62,6 @@ impl Client { )); } - println!("{}", response.url()); - let context: GetItemsResponseModel = response.json().await?; Ok(context) diff --git a/src/jellyfin/models.rs b/src/jellyfin/models.rs index 1ec8022..aa78720 100644 --- a/src/jellyfin/models.rs +++ b/src/jellyfin/models.rs @@ -32,7 +32,7 @@ pub struct GetItemsResponseModel { pub struct ItemModel { pub name: String, pub id: String, - pub path: String, + pub path: std::path::PathBuf, pub production_year: Option, pub is_folder: bool, #[serde(rename = "Type")] diff --git a/src/main.rs b/src/main.rs index 3bb34d5..4c3c1d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,44 @@ use clap::Parser; -use tv_scheduler::{config::Args, ersatz_tv, jellyfin}; +use tv_scheduler::{ + config::Args, + ersatz_tv::{ + self, + models::{ + FillerKind, ScheduleAllContentDto, SearchQueryDto, SearchQueryOrder, SearchQueryValue, + }, + }, + jellyfin, +}; #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); - // let tv = ersatz_tv::Client::new(args.host)?; - - // let context = tv.get_context(args.build_id).await?; - - // println!("{:?}", context); let jelly = jellyfin::Client::new("https://media.hoshikusu.xyz")?; let item = jelly.get_random_vgm_album().await?; let songs = jelly.get_songs(item.id).await?; - println!("{:#?}", songs); + + let tv = ersatz_tv::Client::new(&args.host, &args.build_id)?; + + let content_payload = songs.items.iter().map(|song| SearchQueryDto { + key: song.id.clone(), + order: SearchQueryOrder::Chronological, + query: SearchQueryValue::SongTitle(format!(r#"title:"{}" AND library_id:9"#, song.name)), + }); + + tv.load_content(content_payload).await?; + + let schedule_payload = songs.items.iter().map(|song| ScheduleAllContentDto { + key: song.id.clone(), + custom_title: song.name.clone(), + disable_watermarks: false, + filler_kind: FillerKind::None, + }); + + tv.schedule_content(schedule_payload).await?; + + // println!("{:#?}", songs); Ok(()) }