Merge pull request #141 from jb-alvarado/master
fix index out of bounds, integrate infinit loop
This commit is contained in:
commit
7b42314a93
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1009,7 +1009,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"crossbeam-channel 0.5.5",
|
||||
@ -1027,7 +1027,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-api"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"actix-multipart",
|
||||
"actix-web",
|
||||
@ -1055,7 +1055,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-lib"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"chrono 0.4.19 (git+https://github.com/chronotope/chrono.git)",
|
||||
"crossbeam-channel 0.5.5",
|
||||
|
3
assets/11-ffplayout
Normal file
3
assets/11-ffplayout
Normal file
@ -0,0 +1,3 @@
|
||||
# give user www-data permission to control the ffplayout systemd service
|
||||
|
||||
www-data ALL = NOPASSWD: /bin/systemctl start ffplayout.service, /bin/systemctl stop ffplayout.service, /bin/systemctl reload ffplayout.service, /bin/systemctl restart ffplayout.service, /bin/systemctl status ffplayout.service, /bin/systemctl is-active ffplayout.service
|
@ -3,7 +3,7 @@ Description=Rest API for ffplayout
|
||||
After=network.target remote-fs.target
|
||||
|
||||
[Service]
|
||||
ExecStart= /usr/bin/ffpapi
|
||||
ExecStart=/usr/bin/ffpapi -l 127.0.0.1:8080
|
||||
ExecReload=/bin/kill -1 $MAINPID
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
|
@ -36,7 +36,7 @@ logging:
|
||||
log_to_file: false
|
||||
backup_count: 7
|
||||
local_time: true
|
||||
timestamp: false
|
||||
timestamp: true
|
||||
log_path: /var/log/ffplayout/
|
||||
log_level: DEBUG
|
||||
ffmpeg_level: error
|
||||
|
@ -138,6 +138,10 @@ Response is in JSON format
|
||||
- **GET** `/api/control/{id}/media/last/`\
|
||||
Response is in JSON format
|
||||
|
||||
- **POST** `/api/control/{id}/process/`\
|
||||
JSON Data: `{"command": "<start/stop/restart/status>"}`
|
||||
Response is in TEXT format
|
||||
|
||||
#### Playlist Operations
|
||||
|
||||
- **GET** `/api/playlist/{id}/2022-06-20`\
|
||||
|
@ -4,7 +4,7 @@ description = "Rest API for ffplayout"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||
readme = "README.md"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
@ -17,9 +17,9 @@ use utils::{
|
||||
routes::{
|
||||
add_preset, add_user, del_playlist, file_browser, gen_playlist, get_playlist,
|
||||
get_playout_config, get_presets, get_settings, jump_to_last, jump_to_next, login,
|
||||
media_current, media_last, media_next, move_rename, patch_settings, remove, reset_playout,
|
||||
save_file, save_playlist, send_text_message, update_playout_config, update_preset,
|
||||
update_user,
|
||||
media_current, media_last, media_next, move_rename, patch_settings, process_control,
|
||||
remove, reset_playout, save_file, save_playlist, send_text_message, update_playout_config,
|
||||
update_preset, update_user,
|
||||
},
|
||||
run_args, Role,
|
||||
};
|
||||
@ -92,6 +92,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(media_current)
|
||||
.service(media_next)
|
||||
.service(media_last)
|
||||
.service(process_control)
|
||||
.service(get_playlist)
|
||||
.service(save_playlist)
|
||||
.service(gen_playlist)
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, process::Command};
|
||||
|
||||
use reqwest::{
|
||||
header::{HeaderMap, AUTHORIZATION, CONTENT_TYPE},
|
||||
@ -7,7 +7,8 @@ use reqwest::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use simplelog::*;
|
||||
|
||||
use crate::utils::{errors::ServiceError, playout_config};
|
||||
use crate::utils::{errors::ServiceError, handles::db_get_settings, playout_config};
|
||||
use ffplayout_lib::vec_strings;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct RpcObj<T> {
|
||||
@ -44,6 +45,62 @@ impl<T> RpcObj<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Process {
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
struct SystemD {
|
||||
service: String,
|
||||
cmd: Vec<String>,
|
||||
}
|
||||
|
||||
impl SystemD {
|
||||
async fn new(id: i64) -> Result<Self, ServiceError> {
|
||||
let settings = db_get_settings(&id).await?;
|
||||
|
||||
Ok(Self {
|
||||
service: settings.service,
|
||||
cmd: vec_strings!["systemctl"],
|
||||
})
|
||||
}
|
||||
|
||||
fn start(mut self) -> Result<String, ServiceError> {
|
||||
self.cmd
|
||||
.append(&mut vec!["start".to_string(), self.service]);
|
||||
|
||||
Command::new("sudo").args(self.cmd).spawn()?;
|
||||
|
||||
Ok("Success".to_string())
|
||||
}
|
||||
|
||||
fn stop(mut self) -> Result<String, ServiceError> {
|
||||
self.cmd.append(&mut vec!["stop".to_string(), self.service]);
|
||||
|
||||
Command::new("sudo").args(self.cmd).spawn()?;
|
||||
|
||||
Ok("Success".to_string())
|
||||
}
|
||||
|
||||
fn restart(mut self) -> Result<String, ServiceError> {
|
||||
self.cmd
|
||||
.append(&mut vec!["restart".to_string(), self.service]);
|
||||
|
||||
Command::new("sudo").args(self.cmd).spawn()?;
|
||||
|
||||
Ok("Success".to_string())
|
||||
}
|
||||
|
||||
fn status(mut self) -> Result<String, ServiceError> {
|
||||
self.cmd
|
||||
.append(&mut vec!["is-active".to_string(), self.service]);
|
||||
|
||||
let output = Command::new("sudo").args(self.cmd).output()?;
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_header(auth: &str) -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
@ -105,3 +162,15 @@ pub async fn media_info(id: i64, command: String) -> Result<Response, ServiceErr
|
||||
|
||||
post_request(id, json_obj).await
|
||||
}
|
||||
|
||||
pub async fn control_service(id: i64, command: &str) -> Result<String, ServiceError> {
|
||||
let system_d = SystemD::new(id).await?;
|
||||
|
||||
match command {
|
||||
"start" => system_d.start(),
|
||||
"stop" => system_d.stop(),
|
||||
"restart" => system_d.restart(),
|
||||
"status" => system_d.status(),
|
||||
_ => Err(ServiceError::BadRequest("Command not found!".to_string())),
|
||||
}
|
||||
}
|
||||
|
@ -59,3 +59,9 @@ impl From<actix_web::error::BlockingError> for ServiceError {
|
||||
ServiceError::BadRequest(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for ServiceError {
|
||||
fn from(err: sqlx::Error) -> ServiceError {
|
||||
ServiceError::BadRequest(err.to_string())
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ async fn create_schema() -> Result<SqliteQueryResult, sqlx::Error> {
|
||||
preview_url TEXT NOT NULL,
|
||||
config_path TEXT NOT NULL,
|
||||
extra_extensions TEXT NOT NULL,
|
||||
service TEXT NOT NULL,
|
||||
UNIQUE(channel_name)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS user
|
||||
@ -108,9 +109,9 @@ pub async fn db_init() -> Result<&'static str, Box<dyn std::error::Error>> {
|
||||
('Scrolling Text', 'We have a very important announcement to make.', 'ifnot(ld(1),st(1,t));if(lt(t,ld(1)+1),w+4,w-w/12*mod(t-ld(1),12*(w+tw)/w))', '(h-line_h)*0.9',
|
||||
'24', '4', '#ffffff', '1.0', '1', '#000000@0x80', '4');
|
||||
INSERT INTO roles(name) VALUES('admin'), ('user'), ('guest');
|
||||
INSERT INTO settings(channel_name, preview_url, config_path, extra_extensions)
|
||||
INSERT INTO settings(channel_name, preview_url, config_path, extra_extensions, service)
|
||||
VALUES('Channel 1', 'http://localhost/live/preview.m3u8',
|
||||
'/etc/ffplayout/ffplayout.yml', '.jpg,.jpeg,.png');";
|
||||
'/etc/ffplayout/ffplayout.yml', '.jpg,.jpeg,.png', 'ffplayout.service');";
|
||||
sqlx::query(query).bind(secret).execute(&instances).await?;
|
||||
instances.close().await;
|
||||
|
||||
|
@ -66,4 +66,5 @@ pub struct Settings {
|
||||
#[sqlx(default)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub secret: String,
|
||||
pub service: String,
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use simplelog::*;
|
||||
|
||||
use crate::utils::{
|
||||
auth::{create_jwt, Claims},
|
||||
control::{control_state, media_info, send_message},
|
||||
control::{control_service, control_state, media_info, send_message, Process},
|
||||
errors::ServiceError,
|
||||
files::{browser, remove_file_or_folder, rename_file, upload, MoveObject, PathObject},
|
||||
handles::{
|
||||
@ -352,6 +352,18 @@ pub async fn media_last(id: web::Path<i64>) -> Result<impl Responder, ServiceErr
|
||||
}
|
||||
}
|
||||
|
||||
/// curl -X GET http://localhost:8080/api/control/1/process/
|
||||
/// --header 'Content-Type: application/json' --header 'Authorization: <TOKEN>'
|
||||
/// -d '{"command": "start"}'
|
||||
#[post("/control/{id}/process/")]
|
||||
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
|
||||
pub async fn process_control(
|
||||
id: web::Path<i64>,
|
||||
proc: web::Json<Process>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
control_service(*id, &proc.command).await
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// ffplayout playlist operations
|
||||
///
|
||||
|
@ -4,7 +4,7 @@ description = "24/7 playout based on rust and ffmpeg"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||
readme = "README.md"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@ -52,6 +52,7 @@ assets = [
|
||||
"755"
|
||||
],
|
||||
["../assets/ffpapi.service", "/lib/systemd/system/ffpapi.service", "644"],
|
||||
["../assets/11-ffplayout", "/etc/sudoers.d/11-ffplayout", "644"],
|
||||
["../assets/ffplayout.yml", "/etc/ffplayout/ffplayout.yml", "644"],
|
||||
["../assets/logo.png", "/usr/share/ffplayout/logo.png", "644"],
|
||||
["../README.md", "/usr/share/doc/ffplayout/README", "644"],
|
||||
@ -69,6 +70,7 @@ assets = [
|
||||
{ source = "../assets/ffplayout.yml", dest = "/etc/ffplayout/ffplayout.yml", mode = "644", config = true },
|
||||
{ source = "../assets/ffpapi.service", dest = "/lib/systemd/system/ffpapi.service", mode = "644" },
|
||||
{ source = "../assets/ffplayout.service", dest = "/lib/systemd/system/ffplayout.service", mode = "644" },
|
||||
{ source = "../assets/11-ffplayout", dest = "/etc/sudoers.d/11-ffplayout", mode = "644" },
|
||||
{ source = "../README.md", dest = "/usr/share/doc/ffplayout/README", mode = "644", doc = true },
|
||||
{ source = "../LICENSE", dest = "/usr/share/doc/ffplayout/LICENSE", mode = "644" },
|
||||
{ source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" },
|
||||
|
@ -275,19 +275,20 @@ impl Iterator for CurrentProgram {
|
||||
// On init load, playlist could be not long enough,
|
||||
// so we check if we can take the next playlist already,
|
||||
// or we fill the gap with a dummy.
|
||||
let list_length = self.nodes.lock().unwrap().len();
|
||||
self.current_node = self.nodes.lock().unwrap()[list_length - 1].clone();
|
||||
self.check_for_next_playlist();
|
||||
|
||||
let new_node = self.nodes.lock().unwrap()[list_length - 1].clone();
|
||||
let last_index = self.nodes.lock().unwrap().len() - 1;
|
||||
self.current_node = self.nodes.lock().unwrap()[last_index].clone();
|
||||
let new_node = self.nodes.lock().unwrap()[last_index].clone();
|
||||
let new_length = new_node.begin.unwrap() + new_node.duration;
|
||||
|
||||
self.check_for_next_playlist();
|
||||
|
||||
if new_length
|
||||
>= self.config.playlist.length_sec.unwrap()
|
||||
+ self.config.playlist.start_sec.unwrap()
|
||||
{
|
||||
self.init_clip();
|
||||
} else {
|
||||
// fill missing length from playlist
|
||||
let mut current_time = get_sec();
|
||||
let (_, total_delta) = get_delta(&self.config, ¤t_time);
|
||||
let mut duration = DUMMY_LEN;
|
||||
@ -347,7 +348,8 @@ impl Iterator for CurrentProgram {
|
||||
let (_, total_delta) =
|
||||
get_delta(&self.config, &self.config.playlist.start_sec.unwrap());
|
||||
|
||||
if last_playlist == self.json_path
|
||||
if !self.config.playlist.infinit
|
||||
&& last_playlist == self.json_path
|
||||
&& total_delta.abs() > self.config.general.stop_threshold
|
||||
{
|
||||
// Test if playlist is to early finish,
|
||||
|
@ -4,7 +4,7 @@ description = "Library for ffplayout"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||
readme = "README.md"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
@ -17,6 +17,7 @@ pub const DUMMY_LEN: f64 = 60.0;
|
||||
/// This is our main playlist object, it holds all necessary information for the current day.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct JsonPlaylist {
|
||||
#[serde(default = "default_channel")]
|
||||
pub channel: String,
|
||||
pub date: String,
|
||||
|
||||
@ -57,6 +58,10 @@ impl PartialEq for JsonPlaylist {
|
||||
|
||||
impl Eq for JsonPlaylist {}
|
||||
|
||||
fn default_channel() -> String {
|
||||
"Channel 1".to_string()
|
||||
}
|
||||
|
||||
fn set_defaults(
|
||||
mut playlist: JsonPlaylist,
|
||||
current_file: String,
|
||||
@ -80,6 +85,54 @@ fn set_defaults(
|
||||
playlist
|
||||
}
|
||||
|
||||
fn loop_playlist(
|
||||
config: &PlayoutConfig,
|
||||
current_file: String,
|
||||
mut playlist: JsonPlaylist,
|
||||
) -> JsonPlaylist {
|
||||
let start_sec = config.playlist.start_sec.unwrap();
|
||||
let mut begin = start_sec;
|
||||
let length = config.playlist.length_sec.unwrap();
|
||||
let mut program_list = vec![];
|
||||
let mut index = 0;
|
||||
|
||||
playlist.current_file = Some(current_file);
|
||||
playlist.start_sec = Some(start_sec);
|
||||
|
||||
'program_looper: loop {
|
||||
for item in playlist.program.iter() {
|
||||
let media = Media {
|
||||
index: Some(index),
|
||||
begin: Some(begin),
|
||||
seek: item.seek,
|
||||
out: item.out,
|
||||
duration: item.duration,
|
||||
category: item.category.clone(),
|
||||
source: item.source.clone(),
|
||||
cmd: item.cmd.clone(),
|
||||
probe: item.probe.clone(),
|
||||
process: Some(true),
|
||||
last_ad: Some(false),
|
||||
next_ad: Some(false),
|
||||
filter: Some(vec![]),
|
||||
};
|
||||
|
||||
if begin < start_sec + length {
|
||||
program_list.push(media);
|
||||
} else {
|
||||
break 'program_looper;
|
||||
}
|
||||
|
||||
begin += item.out - item.seek;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
playlist.program = program_list;
|
||||
|
||||
playlist
|
||||
}
|
||||
|
||||
/// Read json playlist file, fills JsonPlaylist struct and set some extra values,
|
||||
/// which we need to process.
|
||||
pub fn read_json(
|
||||
@ -131,10 +184,14 @@ pub fn read_json(
|
||||
validate_playlist(list_clone, is_terminated, config_clone)
|
||||
});
|
||||
|
||||
if config.playlist.infinit {
|
||||
return loop_playlist(config, current_file, playlist);
|
||||
} else {
|
||||
return set_defaults(playlist, current_file, start_sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if playlist_path.is_file() {
|
||||
let f = File::options()
|
||||
.read(true)
|
||||
@ -149,10 +206,14 @@ pub fn read_json(
|
||||
|
||||
thread::spawn(move || validate_playlist(list_clone, is_terminated, config_clone));
|
||||
|
||||
if config.playlist.infinit {
|
||||
return loop_playlist(config, current_file, playlist);
|
||||
} else {
|
||||
return set_defaults(playlist, current_file, start_sec);
|
||||
}
|
||||
}
|
||||
|
||||
error!("Read playlist error, on: <b><magenta>{current_file}</></b>");
|
||||
error!("Playlist <b><magenta>{current_file}</></b> not exist!");
|
||||
|
||||
JsonPlaylist::new(date, start_sec)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ pub fn validate_playlist(
|
||||
begin += item.out - item.seek;
|
||||
}
|
||||
|
||||
if length > begin + 1.0 {
|
||||
if !config.playlist.infinit && length > begin + 1.0 {
|
||||
error!(
|
||||
"Playlist from <yellow>{date}</> not long enough, <yellow>{}</> needed!",
|
||||
sec_to_time(length - begin),
|
||||
|
@ -48,6 +48,7 @@ pub struct Media {
|
||||
pub out: f64,
|
||||
pub duration: f64,
|
||||
|
||||
#[serde(default)]
|
||||
pub category: String,
|
||||
pub source: String,
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user