feat: schedule content

This commit is contained in:
Alexander Navarro 2025-10-19 17:18:08 -03:00
parent 12ad788452
commit 381c086eba
6 changed files with 150 additions and 16 deletions

View file

@ -7,7 +7,7 @@ pub struct Args {
/// URL host of the ErsatzTv service /// URL host of the ErsatzTv service
pub host: String, pub host: String,
/// UUID of the build /// UUID of the build
pub build_id: Uuid, pub build_id: String,
/// The playout build mode /// The playout build mode
pub playout_mode: PlayoutMode, pub playout_mode: PlayoutMode,
} }

View file

@ -1,27 +1,32 @@
use anyhow::anyhow; use anyhow::anyhow;
use uuid::Uuid; use uuid::Uuid;
use crate::ersatz_tv::models::ContextModel; use crate::ersatz_tv::models::{ContextModel, ScheduleAllContentDto, SearchQueryDto};
pub mod models; pub mod models;
pub struct Client { pub struct Client {
_client: reqwest::Client, _client: reqwest::Client,
host: String, host: String,
build_id: String,
} }
impl Client { impl Client {
pub fn new(host: String) -> anyhow::Result<Self> { pub fn new(host: &String, build_id: &String) -> anyhow::Result<Self> {
let _client = reqwest::Client::builder().gzip(true).build()?; 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<ContextModel> { pub async fn get_context(&self) -> anyhow::Result<ContextModel> {
let response = self let response = self
._client ._client
.get(format!( .get(format!(
"{}/api/scripted/playout/build/{}/context", "{}/api/scripted/playout/build/{}/context",
self.host, build_id self.host, self.build_id
)) ))
.send() .send()
.await?; .await?;
@ -39,4 +44,72 @@ impl Client {
Ok(context) 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<T: IntoIterator<Item = SearchQueryDto>>(
&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<T: IntoIterator<Item = ScheduleAllContentDto>>(
&self,
content: T,
) -> anyhow::Result<()> {
for item in content {
self._schedule_content(&item).await?;
}
Ok(())
}
} }

View file

@ -8,3 +8,42 @@ pub struct ContextModel {
finish_time: chrono::DateTime<chrono::Local>, finish_time: chrono::DateTime<chrono::Local>,
is_done: bool, 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,
}

View file

@ -62,8 +62,6 @@ impl Client {
)); ));
} }
println!("{}", response.url());
let context: GetItemsResponseModel = response.json().await?; let context: GetItemsResponseModel = response.json().await?;
Ok(context) Ok(context)

View file

@ -32,7 +32,7 @@ pub struct GetItemsResponseModel {
pub struct ItemModel { pub struct ItemModel {
pub name: String, pub name: String,
pub id: String, pub id: String,
pub path: String, pub path: std::path::PathBuf,
pub production_year: Option<i64>, pub production_year: Option<i64>,
pub is_folder: bool, pub is_folder: bool,
#[serde(rename = "Type")] #[serde(rename = "Type")]

View file

@ -1,20 +1,44 @@
use clap::Parser; 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] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args = Args::parse(); 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 jelly = jellyfin::Client::new("https://media.hoshikusu.xyz")?;
let item = jelly.get_random_vgm_album().await?; let item = jelly.get_random_vgm_album().await?;
let songs = jelly.get_songs(item.id).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(()) Ok(())
} }