feat: add config file loading
This commit is contained in:
parent
381c086eba
commit
67be4dc233
7 changed files with 369 additions and 32 deletions
|
|
@ -1,5 +1,9 @@
|
|||
use clap::{Parser, ValueEnum};
|
||||
use uuid::Uuid;
|
||||
use figment::{
|
||||
Figment,
|
||||
providers::{Env, Format, Toml},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about)]
|
||||
|
|
@ -17,3 +21,48 @@ pub enum PlayoutMode {
|
|||
Continue,
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub struct ScheduleConfig {
|
||||
pub name: String,
|
||||
pub blocks: Vec<ScheduleBlock>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub struct ScheduleBlock {
|
||||
pub schedule_type: ScheduleType,
|
||||
pub content_type: ContentType,
|
||||
pub custom_title: Option<String>,
|
||||
pub pre_filler: Option<Box<ScheduleBlock>>,
|
||||
pub post_filler: Option<Box<ScheduleBlock>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ScheduleType {
|
||||
All,
|
||||
StartAt { time: String, until_tomorrow: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ContentType {
|
||||
/// Smart Collection of ErsatzTv
|
||||
SmartCollection { name: String },
|
||||
/// Random album picked from a container Item from Jellyfin
|
||||
RandomAlbum {
|
||||
container_id: String,
|
||||
album_name_as_title: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ScheduleConfig {
|
||||
pub fn new(path: Option<String>) -> anyhow::Result<Self> {
|
||||
let config = Figment::new()
|
||||
.merge(Toml::file(path.unwrap_or(String::from("config.toml"))))
|
||||
.merge(Env::prefixed("TV_SDR_"))
|
||||
.extract()?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
use anyhow::anyhow;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ersatz_tv::models::{ContextModel, ScheduleAllContentDto, SearchQueryDto};
|
||||
use crate::ersatz_tv::models::{ContextModel, Dto, ScheduleAllContentDto};
|
||||
|
||||
pub mod models;
|
||||
|
||||
pub struct Client {
|
||||
_client: reqwest::Client,
|
||||
host: String,
|
||||
base_endpoint: String,
|
||||
build_id: String,
|
||||
}
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ impl Client {
|
|||
Ok(Self {
|
||||
_client,
|
||||
host: host.clone(),
|
||||
base_endpoint: format!("{}/api/scripted/playout/build/{}", host, build_id),
|
||||
build_id: build_id.clone(),
|
||||
})
|
||||
}
|
||||
|
|
@ -45,14 +47,11 @@ impl Client {
|
|||
Ok(context)
|
||||
}
|
||||
|
||||
async fn _load_content(&self, content: &SearchQueryDto) -> anyhow::Result<()> {
|
||||
pub async fn post<T: Dto>(&self, endpoint: &str, payload: &T) -> anyhow::Result<()> {
|
||||
let response = self
|
||||
._client
|
||||
.post(format!(
|
||||
"{}/api/scripted/playout/build/{}/add_search",
|
||||
self.host, self.build_id
|
||||
))
|
||||
.json(content)
|
||||
.post(format!("{}/{}", self.base_endpoint, endpoint))
|
||||
.json(payload)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
|
|
@ -68,17 +67,6 @@ impl Client {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait Dto: Serialize {}
|
||||
impl<T: Serialize> Dto for T {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ContextModel {
|
||||
|
|
@ -9,6 +12,14 @@ pub struct ContextModel {
|
|||
is_done: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoadSmartCollectionDto {
|
||||
pub key: String,
|
||||
pub order: SearchQueryOrder,
|
||||
pub smart_collection: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SearchQueryDto {
|
||||
pub key: String,
|
||||
|
|
@ -47,3 +58,21 @@ pub enum FillerKind {
|
|||
Midroll,
|
||||
Postroll,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScheduleContentUntilDto {
|
||||
#[serde(rename = "content")]
|
||||
pub content_key: String,
|
||||
pub when: String,
|
||||
pub tomorrow: bool,
|
||||
pub filler_kind: FillerKind,
|
||||
pub custom_title: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WaitUntilTimeDto {
|
||||
pub rewind_on_reset: bool,
|
||||
pub when: String,
|
||||
}
|
||||
|
|
|
|||
133
src/main.rs
133
src/main.rs
|
|
@ -1,43 +1,150 @@
|
|||
use clap::Parser;
|
||||
use tv_scheduler::{
|
||||
config::Args,
|
||||
config::{Args, ContentType, ScheduleBlock, ScheduleConfig, ScheduleType},
|
||||
ersatz_tv::{
|
||||
self,
|
||||
models::{
|
||||
FillerKind, ScheduleAllContentDto, SearchQueryDto, SearchQueryOrder, SearchQueryValue,
|
||||
FillerKind, LoadSmartCollectionDto, ScheduleAllContentDto, ScheduleContentUntilDto,
|
||||
SearchQueryDto, SearchQueryOrder, SearchQueryValue, WaitUntilTimeDto,
|
||||
},
|
||||
},
|
||||
jellyfin,
|
||||
};
|
||||
|
||||
async fn handle_schedule_block(
|
||||
tv: &ersatz_tv::Client,
|
||||
jelly: &jellyfin::Client,
|
||||
block: &ScheduleBlock,
|
||||
) -> anyhow::Result<()> {
|
||||
println!("{:#?}", block);
|
||||
|
||||
// add pre content until time
|
||||
if let Some(content) = &block.pre_filler {
|
||||
let payload = match &content.content_type {
|
||||
ContentType::SmartCollection { name } => LoadSmartCollectionDto {
|
||||
key: name.clone(),
|
||||
order: SearchQueryOrder::Chronological,
|
||||
smart_collection: name.clone(),
|
||||
},
|
||||
ContentType::RandomAlbum {
|
||||
container_id,
|
||||
album_name_as_title,
|
||||
} => todo!(),
|
||||
};
|
||||
|
||||
println!("Loading PreFiller: {:#?}", payload);
|
||||
tv.post("add_smart_collection", &payload).await?;
|
||||
|
||||
match &block.schedule_type {
|
||||
ScheduleType::All => todo!(),
|
||||
ScheduleType::StartAt {
|
||||
time,
|
||||
until_tomorrow,
|
||||
} => {
|
||||
tv.post(
|
||||
"pad_until",
|
||||
&ScheduleContentUntilDto {
|
||||
content_key: payload.key,
|
||||
when: time.clone(),
|
||||
tomorrow: *until_tomorrow,
|
||||
filler_kind: FillerKind::Preroll,
|
||||
custom_title: content.custom_title.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
} else if let ScheduleType::StartAt {
|
||||
time,
|
||||
until_tomorrow: _,
|
||||
} = &block.schedule_type
|
||||
{
|
||||
// Fill with nothing until the desire time
|
||||
tv.post(
|
||||
"wait_until_exact",
|
||||
&WaitUntilTimeDto {
|
||||
when: time.clone(),
|
||||
rewind_on_reset: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// add main content
|
||||
match &block.content_type {
|
||||
ContentType::SmartCollection { name } => todo!(),
|
||||
ContentType::RandomAlbum {
|
||||
container_id,
|
||||
album_name_as_title,
|
||||
} => {
|
||||
let item = jelly.get_random_vgm_album().await?;
|
||||
println!("Using album: {}", item.name);
|
||||
let songs = jelly.get_songs(item.id).await?;
|
||||
|
||||
let content_payload = songs.items.iter().map(|song| SearchQueryDto {
|
||||
key: song.id.clone(),
|
||||
order: SearchQueryOrder::Shuffle,
|
||||
query: SearchQueryValue::SongTitle(format!(
|
||||
r#"title:"{}" AND library_id:9"#,
|
||||
song.name
|
||||
)),
|
||||
});
|
||||
|
||||
for content in content_payload {
|
||||
tv.post("add_search", &content).await?;
|
||||
}
|
||||
|
||||
let schedule_payload = songs.items.iter().map(|song| ScheduleAllContentDto {
|
||||
key: song.id.clone(),
|
||||
custom_title: if *album_name_as_title {
|
||||
item.name.clone()
|
||||
} else {
|
||||
"VGM".to_string()
|
||||
},
|
||||
disable_watermarks: false,
|
||||
filler_kind: FillerKind::None,
|
||||
});
|
||||
|
||||
for content in schedule_payload {
|
||||
tv.post("add_all", &content).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all post content
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
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?;
|
||||
let config = ScheduleConfig::new(None)?;
|
||||
|
||||
let tv = ersatz_tv::Client::new(&args.host, &args.build_id)?;
|
||||
let jelly = jellyfin::Client::new("https://media.hoshikusu.xyz")?;
|
||||
|
||||
let content_payload = songs.items.iter().map(|song| SearchQueryDto {
|
||||
for block in config.blocks {
|
||||
handle_schedule_block(&tv, &jelly, &block).await?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
|
||||
let _item = jelly.get_random_vgm_album().await?;
|
||||
let _songs = jelly.get_songs(_item.id).await?;
|
||||
|
||||
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 {
|
||||
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(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue