diff --git a/assets/ffplayout.service b/assets/ffplayout.service index a65eb9d8..474d7ee2 100644 --- a/assets/ffplayout.service +++ b/assets/ffplayout.service @@ -3,7 +3,7 @@ Description=Rust and ffmpeg based playout solution After=network.target remote-fs.target [Service] -ExecStart= /usr/bin/ffplayout +ExecStart=/usr/bin/ffplayout ExecReload=/bin/kill -1 $MAINPID Restart=always RestartSec=1 diff --git a/assets/ffplayout.yml b/assets/ffplayout.yml index d483d7ec..0a1ffefc 100644 --- a/assets/ffplayout.yml +++ b/assets/ffplayout.yml @@ -10,7 +10,7 @@ general: rpc_server: help_text: Run a JSON RPC server, for getting infos about current playing, and control for some functions. - enable: false + enable: true address: 127.0.0.1:7070 authorization: av2Kx8g67lF9qj5wEH3ym1bI4cCs @@ -108,7 +108,7 @@ text: 'text_from_filename' activate the extraction from text of a filename. With 'style' you can define the drawtext parameters like position, color, etc. Post Text over API will override this. With 'regex' you can format file names, to get a title from it. - add_text: false + add_text: true text_from_filename: false fontfile: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" style: "x=(w-tw)/2:y=(h-line_h)*0.9:fontsize=24:fontcolor=#ffffff:box=1:boxcolor=#000000:boxborderw=4" diff --git a/docs/api.md b/docs/api.md index 759eda25..aa2ac104 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,186 +1,313 @@ -#### Possible endpoints +### Possible endpoints Run the API thru the systemd service, or like: ```BASH -ffpapi -l 127.0.0.1:8080 +ffpapi -l 127.0.0.1:8000 ``` For all endpoints an (Bearer) authentication is required.\ `{id}` represent the channel id, and at default is 1. -#### Login is +#### User Handling + +**Login** + +```BASH +curl -X POST http://127.0.0.1:8000/auth/login/ -H "Content-Type: application/json" \ +-d '{ "username": "", "password": "" }' +``` +**Response:** -- **POST** `/auth/login/`\ -JSON Data: `{"username": "", "password": ""}`\ -JSON Response: ```JSON { - "message": "login correct!", - "status": 200, - "data": { - "id": 1, - "mail": "user@example.org", - "username": "user", - "token": "" - } + "id": 1, + "mail": "user@example.org", + "username": "", + "token": "" } ``` From here on all request **must** contain the authorization header:\ `"Authorization: Bearer "` -#### User +**Get current User** -- **GET** `/api/user`\ -Get current user, response is in JSON format +```BASH +curl -X GET 'http://localhost:8000/api/user' -H 'Content-Type: application/json' \ +-H 'Authorization: Bearer ' +``` -- **PUT** `/api/user/{user id}`\ -JSON Data: `{"mail": "", "password": ""}` +**Update current User** + +```BASH +curl -X PUT http://localhost:8000/api/user/1 -H 'Content-Type: application/json' \ +-d '{"mail": "", "password": ""}' -H 'Authorization: ' +``` + +**Add User** + +```BASH +curl -X POST 'http://localhost:8000/api/user/' -H 'Content-Type: application/json' \ +-d '{"mail": "", "username": "", "password": "", "role_id": 1, "channel_id": 1}' \ +-H 'Authorization: Bearer ' +``` + +#### ffpapi Settings + +**Get Settings** + +```BASH +curl -X GET http://127.0.0.1:8000/api/settings/1 -H "Authorization: Bearer " +``` + +**Response:** -- **POST** `/api/user/`\ -JSON Data: ```JSON { - "mail": "", - "username": "", - "password": "", - "role_id": 1 + "id": 1, + "channel_name": "Channel 1", + "preview_url": "http://localhost/live/preview.m3u8", + "config_path": "/etc/ffplayout/ffplayout.yml", + "extra_extensions": "jpg,jpeg,png", + "timezone": "UTC", + "service": "ffplayout.service" } ``` -#### API Settings +**Get all Settings** -- **GET** `/api/settings`\ -Response is in JSON format - -- **GET** `/api/settings/{id}`\ -Response is in JSON format - -- **PATCH** `/api/settings/{id}`\ -JSON Data: -```JSON - "id": 1, - "channel_name": "Channel 1", - "preview_url": "http://localhost/live/stream.m3u8", - "config_path": "/etc/ffplayout/ffplayout.yml", - "extra_extensions": ".jpg,.jpeg,.png" +```BASH +curl -X GET http://127.0.0.1:8000/api/settings -H "Authorization: Bearer " ``` -#### Playout Config +**Update Settings** -- **GET** `/api/playout/config/{id}`\ -Response is in JSON format +```BASH +curl -X PATCH http://127.0.0.1:8000/api/settings/1 -H "Content-Type: application/json" \ +-d '{ "id": 1, "channel_name": "Channel 1", "preview_url": "http://localhost/live/stream.m3u8", \ +"config_path": "/etc/ffplayout/ffplayout.yml", "extra_extensions": "jpg,jpeg,png", +"role_id": 1, "channel_id": 1 }' \ +-H "Authorization: Bearer " +``` -- **PUT** `/api/playout/config/{id}`\ -JSON Data: `{ }`\ -Response is in TEXT format +#### ffplayout Config + +**Get Config** + +```BASH +curl -X GET http://localhost:8000/api/playout/config/1 -H 'Authorization: ' +``` + +Response is a JSON object from the ffplayout.yml + +**Update Config** + +```BASH +curl -X PUT http://localhost:8000/api/playout/config/1 -H "Content-Type: application/json" \ +-d { } -H 'Authorization: ' +``` #### Text Presets -- **GET** `/api/presets/`\ -Response is in JSON format +Text presets are made for sending text messages to the ffplayout engine, to overlay them as a lower third. + +**Get all Presets** + +```BASH +curl -X GET http://localhost:8000/api/presets/ -H 'Content-Type: application/json' \ +-H 'Authorization: ' +``` + +**Update Preset** + +```BASH +curl -X PUT http://localhost:8000/api/presets/1 -H 'Content-Type: application/json' \ +-d '{"name": "", "text": "", "x": "", "y": "", "fontsize": 24, \ +"line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0}' \ +-H 'Authorization: ' +``` + +**Ad new Preset** + +```BASH +curl -X POST http://localhost:8000/api/presets/ -H 'Content-Type: application/json' \ +-d '{"name": "", "text": "TEXT>", "x": "", "y": "", "fontsize": 24, \ +"line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0}}' \ +-H 'Authorization: ' +``` + +### ffplayout controlling + +here we communicate with the engine for: +- jump to last or next clip +- reset playlist state +- get infos about current, next, last clip +- send text to the engine, for overlaying it (as lower third etc.) + +**Send Text to ffplayout** + +```BASH +curl -X POST http://localhost:8000/api/control/1/text/ \ +-H 'Content-Type: application/json' -H 'Authorization: ' \ +-d '{"text": "Hello from ffplayout", "x": "(w-text_w)/2", "y": "(h-text_h)/2", \ + "fontsize": "24", "line_spacing": "4", "fontcolor": "#ffffff", "box": "1", \ + "boxcolor": "#000000", "boxborderw": "4", "alpha": "1.0"}' +``` + +**Jump to next Clip** + +```BASH +curl -X POST http://localhost:8000/api/control/1/playout/next/ -H 'Authorization: ' +``` + +**Jump to last Clip** + +```BASH +curl -X POST http://localhost:8000/api/control/1/playout/back/ -H 'Authorization: ' +``` + +**Reset ffplayout State** + +When before was jumped to next, or last clips, here we go back to the original clip. + +```BASH +curl -X POST http://localhost:8000/api/control/1/playout/reset/ -H 'Authorization: ' +``` + +**Get current Clip** + +```BASH +curl -X GET http://localhost:8000/api/control/1/media/current/ +-H 'Content-Type: application/json' -H 'Authorization: ' +``` + +**Response:** -- **PUT** `/api/playout/presets/{id}`\ -JSON Data: ```JSON { - "name": "", - "text": "", - "x": "", - "y": "", - "fontsize": 24, - "line_spacing": 4, - "fontcolor": "#ffffff", - "box": 1, - "boxcolor": "#000000", - "boxborderw": 4, - "alpha": "" -} - -``` -Response is in TEXT format - -- **POST** `/api/playout/presets/`\ -JSON Data: `{ }`\ -Response is in TEXT format - -#### Playout Process Control - -- **POST** `/api/control/{id}/text/`ΒΈ -JSON Data: -```JSON -{ - "text": "Hello from ffplayout", - "x": "(w-text_w)/2", - "y": "(h-text_h)/2", - "fontsize": "24", - "line_spacing": "4", - "fontcolor": "#ffffff", - "box": "1", - "boxcolor": "#000000", - "boxborderw": "4", - "alpha": "1.0" + "jsonrpc": "2.0", + "result": { + "current_media": { + "category": "", + "duration": 154.2, + "out": 154.2, + "seek": 0.0, + "source": "/opt/tv-media/clip.mp4" + }, + "index": 39, + "play_mode": "playlist", + "played_sec": 67.80771999300123, + "remaining_sec": 86.39228000699876, + "start_sec": 24713.631999999998, + "start_time": "06:51:53.631" + }, + "id": 1 } ``` -Response is in TEXT format -- **POST** `api/control/{id}/playout/next/`\ -Response is in TEXT format +**Get next Clip** -- **POST** `api/control/{id}/playout/back/`\ -Response is in TEXT format +```BASH +curl -X GET http://localhost:8000/api/control/1/media/next/ -H 'Authorization: ' +``` -- **POST** `api/control/{id}/playout/reset/`\ -Response is in TEXT format +**Get last Clip** -- **GET** `/api/control/{id}/media/current`\ -Response is in JSON format +```BASH +curl -X GET http://localhost:8000/api/control/1/media/last/ +-H 'Content-Type: application/json' -H 'Authorization: ' +``` -- **GET** `/api/control/{id}/media/next`\ -Response is in JSON format +#### ffplayout Process Control -- **GET** `/api/control/{id}/media/last`\ -Response is in JSON format +Control ffplayout process, like: +- start +- stop +- restart +- status -- **POST** `/api/control/{id}/process/`\ -JSON Data: `{"command": ""}` -Response is in TEXT format +```BASH +curl -X POST http://localhost:8000/api/control/1/process/ +-H 'Content-Type: application/json' -H 'Authorization: ' +-d '{"command": "start"}' +``` -#### Playlist Operations +#### ffplayout Playlist Operations -- **GET** `/api/playlist/{id}/2022-06-20`\ -Response is in JSON format +**Get playlist** -- **POST** `/api/playlist/1/`\ -JSON Data: `{ }`\ -Response is in TEXT format +```BASH +curl -X GET http://localhost:8000/api/playlist/1?date=2022-06-20 +-H 'Content-Type: application/json' -H 'Authorization: ' +``` -- **GET** `/api/playlist/{id}/generate/2022-06-20`\ -Response is in JSON format +**Save playlist** -- **DELETE** `/api/playlist/{id}/2022-06-20`\ -Response is in TEXT format +```BASH +curl -X POST http://localhost:8000/api/playlist/1/ +-H 'Content-Type: application/json' -H 'Authorization: ' +-- data "{}" +``` -#### Log File +**Generate Playlist** -- **GET** `/api/file/{id}/browse/`\ -Response is in JSON format +A new playlist will be generated and response. +```BASH +curl -X GET http://localhost:8000/api/playlist/1/generate/2022-06-20 +-H 'Content-Type: application/json' -H 'Authorization: ' +``` -#### File Operations +**Delete Playlist** -- **GET** `/api/log/{id}(/{date})`\ -Response is in TEXT format +```BASH +curl -X DELETE http://localhost:8000/api/playlist/1/2022-06-20 +-H 'Content-Type: application/json' -H 'Authorization: ' +``` -- **POST** `/api/file/{id}/move/`\ -JSON Data: `{"source": "", "target": ""}`\ -Response is in JSON format +### Log file -- **DELETE** `/api/file/{id}/remove/`\ -JSON Data: `{"source": ""}`\ -Response is in JSON format +**Read Log Life** -- **POST** `/file/{id}/upload/`\ -Multipart Form: `name=, filename=`\ -Response is in TEXT format +```BASH +curl -X Get http://localhost:8000/api/log/1 +-H 'Content-Type: application/json' -H 'Authorization: ' +``` + +### File Operations + +**Get File/Folder List** + +```BASH +curl -X POST http://localhost:8000/api/file/1/browse/ -H 'Content-Type: application/json' +-d '{ "source": "/" }' -H 'Authorization: ' +``` + +**Create Folder** + +```BASH +curl -X POST http://localhost:8000/api/file/1/create-folder/ -H 'Content-Type: application/json' +-d '{"source": ""}' -H 'Authorization: ' +``` + +**Rename File** + +```BASH +curl -X POST http://localhost:8000/api/file/1/rename/ -H 'Content-Type: application/json' +-d '{"source": "", "target": ""}' -H 'Authorization: ' +``` + +**Remove File/Folder** + +```BASH +curl -X POST http://localhost:8000/api/file/1/remove/ -H 'Content-Type: application/json' +-d '{"source": ""}' -H 'Authorization: ' +``` + +**Upload File** + +```BASH +curl -X POST http://localhost:8000/api/file/1/upload/ -H 'Authorization: ' +-F "file=@file.mp4" +``` diff --git a/ffplayout-api/src/main.rs b/ffplayout-api/src/main.rs index dd021257..84794f38 100644 --- a/ffplayout-api/src/main.rs +++ b/ffplayout-api/src/main.rs @@ -17,9 +17,9 @@ use utils::{ routes::{ add_dir, add_preset, add_user, del_playlist, file_browser, gen_playlist, get_all_settings, get_log, get_playlist, get_playout_config, get_presets, get_settings, get_user, - jump_to_last, jump_to_next, login, 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, + control_playout, login, media_current, media_last, media_next, move_rename, + patch_settings, process_control, remove, save_file, save_playlist, + send_text_message, update_playout_config, update_preset, update_user, delete_preset, }, run_args, Role, }; @@ -83,14 +83,13 @@ async fn main() -> std::io::Result<()> { .service(add_preset) .service(get_presets) .service(update_preset) + .service(delete_preset) .service(get_settings) .service(get_all_settings) .service(patch_settings) .service(update_user) .service(send_text_message) - .service(jump_to_next) - .service(jump_to_last) - .service(reset_playout) + .service(control_playout) .service(media_current) .service(media_next) .service(media_last) diff --git a/ffplayout-api/src/utils/control.rs b/ffplayout-api/src/utils/control.rs index 8802b50f..60d4949a 100644 --- a/ffplayout-api/src/utils/control.rs +++ b/ffplayout-api/src/utils/control.rs @@ -97,7 +97,7 @@ impl SystemD { let output = Command::new("sudo").args(self.cmd).output()?; - Ok(String::from_utf8_lossy(&output.stdout).to_string()) + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } } diff --git a/ffplayout-api/src/utils/handles.rs b/ffplayout-api/src/utils/handles.rs index 34f24511..6d3540e6 100644 --- a/ffplayout-api/src/utils/handles.rs +++ b/ffplayout-api/src/utils/handles.rs @@ -33,6 +33,17 @@ async fn create_schema() -> Result { name TEXT NOT NULL, UNIQUE(name) ); + CREATE TABLE IF NOT EXISTS settings + ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channel_name TEXT NOT NULL, + preview_url TEXT NOT NULL, + config_path TEXT NOT NULL, + extra_extensions TEXT NOT NULL, + timezone TEXT NOT NULL, + service TEXT NOT NULL, + UNIQUE(channel_name) + ); CREATE TABLE IF NOT EXISTS presets ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -51,17 +62,6 @@ async fn create_schema() -> Result { FOREIGN KEY (channel_id) REFERENCES settings (id) ON UPDATE SET NULL ON DELETE SET NULL, UNIQUE(name) ); - CREATE TABLE IF NOT EXISTS settings - ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - channel_name TEXT NOT NULL, - preview_url TEXT NOT NULL, - config_path TEXT NOT NULL, - extra_extensions TEXT NOT NULL, - timezone TEXT NOT NULL, - service TEXT NOT NULL, - UNIQUE(channel_name) - ); CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -70,7 +70,9 @@ async fn create_schema() -> Result { password TEXT NOT NULL, salt TEXT NOT NULL, role_id INTEGER NOT NULL DEFAULT 2, + channel_id INTEGER NOT NULL DEFAULT 1, FOREIGN KEY (role_id) REFERENCES roles (id) ON UPDATE SET NULL ON DELETE SET NULL, + FOREIGN KEY (channel_id) REFERENCES settings (id) ON UPDATE SET NULL ON DELETE SET NULL, UNIQUE(mail, username) );"; let result = sqlx::query(query).execute(&conn).await; @@ -294,3 +296,12 @@ pub async fn db_add_preset(preset: TextPreset) -> Result Result { + let conn = db_connection().await?; + let query = "DELETE FROM presets WHERE id = $1;"; + let result: SqliteQueryResult = sqlx::query(query).bind(id).execute(&conn).await?; + conn.close().await; + + Ok(result) +} diff --git a/ffplayout-api/src/utils/mod.rs b/ffplayout-api/src/utils/mod.rs index df3bf87a..22ab49b1 100644 --- a/ffplayout-api/src/utils/mod.rs +++ b/ffplayout-api/src/utils/mod.rs @@ -159,6 +159,7 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> { password: args.password.unwrap(), salt: None, role_id: Some(1), + channel_id: Some(1), token: None, }; diff --git a/ffplayout-api/src/utils/models.rs b/ffplayout-api/src/utils/models.rs index ec95b79d..5791c61f 100644 --- a/ffplayout-api/src/utils/models.rs +++ b/ffplayout-api/src/utils/models.rs @@ -18,6 +18,9 @@ pub struct User { #[serde(skip_serializing)] pub role_id: Option, #[sqlx(default)] + #[serde(skip_serializing)] + pub channel_id: Option, + #[sqlx(default)] pub token: Option, } @@ -42,7 +45,6 @@ pub struct TextPreset { #[serde(skip_deserializing)] pub id: i64, pub channel_id: i64, - #[serde(skip_deserializing)] pub name: String, pub text: String, pub x: String, diff --git a/ffplayout-api/src/utils/routes.rs b/ffplayout-api/src/utils/routes.rs index 899d6491..3bb3d562 100644 --- a/ffplayout-api/src/utils/routes.rs +++ b/ffplayout-api/src/utils/routes.rs @@ -1,3 +1,14 @@ +/// ### Possible endpoints +/// +/// Run the API thru the systemd service, or like: +/// +/// ```BASH +/// ffpapi -l 127.0.0.1:8000 +/// ``` +/// +/// For all endpoints an (Bearer) authentication is required.\ +/// `{id}` represent the channel id, and at default is 1. + use std::collections::HashMap; use actix_multipart::Multipart; @@ -21,6 +32,7 @@ use crate::utils::{ handles::{ db_add_preset, db_add_user, db_get_all_settings, db_get_presets, db_get_settings, db_get_user, db_login, db_role, db_update_preset, db_update_settings, db_update_user, + db_delete_preset, }, models::{LoginUser, Settings, TextPreset, User}, playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist}, @@ -53,8 +65,24 @@ pub struct FileObj { path: String, } -/// curl -X POST http://127.0.0.1:8080/auth/login/ -H "Content-Type: application/json" \ -/// -d '{"username": "", "password": "" }' +/// #### User Handling +/// +/// **Login** +/// +/// ```BASH +/// curl -X POST http://127.0.0.1:8000/auth/login/ -H "Content-Type: application/json" \ +/// -d '{ "username": "", "password": "" }' +/// ``` +/// **Response:** +/// +/// ```JSON +/// { +/// "id": 1, +/// "mail": "user@example.org", +/// "username": "", +/// "token": "" +/// } +/// ``` #[post("/auth/login/")] pub async fn login(credentials: web::Json) -> impl Responder { match db_login(&credentials.username).await { @@ -107,8 +135,15 @@ pub async fn login(credentials: web::Json) -> impl Responder { } } -/// curl -X GET 'http://localhost:8080/api/user' --header 'Content-Type: application/json' \ -/// --header 'Authorization: Bearer ' +/// From here on all request **must** contain the authorization header:\ +/// `"Authorization: Bearer "` + +/// **Get current User** +/// +/// ```BASH +/// curl -X GET 'http://localhost:8000/api/user' -H 'Content-Type: application/json' \ +/// -H 'Authorization: Bearer ' +/// ``` #[get("/user")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_user(user: web::ReqData) -> Result { @@ -121,8 +156,12 @@ async fn get_user(user: web::ReqData) -> Result", "password": ""}' --header 'Authorization: ' +/// **Update current User** +/// +/// ```BASH +/// curl -X PUT http://localhost:8000/api/user/1 -H 'Content-Type: application/json' \ +/// -d '{"mail": "", "password": ""}' -H 'Authorization: ' +/// ``` #[put("/user/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn update_user( @@ -160,9 +199,13 @@ async fn update_user( Err(ServiceError::Unauthorized) } -/// curl -X POST 'http://localhost:8080/api/user/' --header 'Content-Type: application/json' \ -/// -d '{"mail": "", "username": "", "password": "", "role_id": 1}' \ -/// --header 'Authorization: Bearer ' +/// **Add User** +/// +/// ```BASH +/// curl -X POST 'http://localhost:8000/api/user/' -H 'Content-Type: application/json' \ +/// -d '{"mail": "", "username": "", "password": "", "role_id": 1, "channel_id": 1}' \ +/// -H 'Authorization: Bearer ' +/// ``` #[post("/user/")] #[has_any_role("Role::Admin", type = "Role")] async fn add_user(data: web::Json) -> Result { @@ -175,7 +218,27 @@ async fn add_user(data: web::Json) -> Result } } -/// curl -X GET http://127.0.0.1:8080/api/settings/1 -H "Authorization: Bearer " +/// #### ffpapi Settings +/// +/// **Get Settings** +/// +/// ```BASH +/// curl -X GET http://127.0.0.1:8000/api/settings/1 -H "Authorization: Bearer " +/// ``` +/// +/// **Response:** +/// +/// ```JSON +/// { +/// "id": 1, +/// "channel_name": "Channel 1", +/// "preview_url": "http://localhost/live/preview.m3u8", +/// "config_path": "/etc/ffplayout/ffplayout.yml", +/// "extra_extensions": "jpg,jpeg,png", +/// "timezone": "UTC", +/// "service": "ffplayout.service" +/// } +/// ``` #[get("/settings/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_settings(id: web::Path) -> Result { @@ -186,7 +249,11 @@ async fn get_settings(id: web::Path) -> Result" +/// **Get all Settings** +/// +/// ```BASH +/// curl -X GET http://127.0.0.1:8000/api/settings -H "Authorization: Bearer " +/// ``` #[get("/settings")] #[has_any_role("Role::Admin", type = "Role")] async fn get_all_settings() -> Result { @@ -197,10 +264,15 @@ async fn get_all_settings() -> Result { Err(ServiceError::InternalServerError) } -/// curl -X PATCH http://127.0.0.1:8080/api/settings/1 -H "Content-Type: application/json" \ -/// --data '{"id":1,"channel_name":"Channel 1","preview_url":"http://localhost/live/stream.m3u8", \ -/// "config_path":"/etc/ffplayout/ffplayout.yml","extra_extensions":".jpg,.jpeg,.png"}' \ +/// **Update Settings** +/// +/// ```BASH +/// curl -X PATCH http://127.0.0.1:8000/api/settings/1 -H "Content-Type: application/json" \ +/// -d '{ "id": 1, "channel_name": "Channel 1", "preview_url": "http://localhost/live/stream.m3u8", \ +/// "config_path": "/etc/ffplayout/ffplayout.yml", "extra_extensions": "jpg,jpeg,png", +/// "role_id": 1, "channel_id": 1 }' \ /// -H "Authorization: Bearer " +/// ``` #[patch("/settings/{id}")] #[has_any_role("Role::Admin", type = "Role")] async fn patch_settings( @@ -214,7 +286,15 @@ async fn patch_settings( Err(ServiceError::InternalServerError) } -/// curl -X GET http://localhost:8080/api/playout/config/1 --header 'Authorization: ' +/// #### ffplayout Config +/// +/// **Get Config** +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/playout/config/1 -H 'Authorization: ' +/// ``` +/// +/// Response is a JSON object from the ffplayout.yml #[get("/playout/config/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_playout_config( @@ -230,8 +310,12 @@ async fn get_playout_config( Err(ServiceError::InternalServerError) } -/// curl -X PUT http://localhost:8080/api/playout/config/1 -H "Content-Type: application/json" \ -/// --data { } --header 'Authorization: ' +/// **Update Config** +/// +/// ```BASH +/// curl -X PUT http://localhost:8000/api/playout/config/1 -H "Content-Type: application/json" \ +/// -d { } -H 'Authorization: ' +/// ``` #[put("/playout/config/{id}")] #[has_any_role("Role::Admin", type = "Role")] async fn update_playout_config( @@ -255,8 +339,16 @@ async fn update_playout_config( Err(ServiceError::InternalServerError) } -/// curl -X GET http://localhost:8080/api/presets/ --header 'Content-Type: application/json' \ -/// --data '{"mail": "", "password": ""}' --header 'Authorization: ' +/// #### Text Presets +/// +/// Text presets are made for sending text messages to the ffplayout engine, to overlay them as a lower third. +/// +/// **Get all Presets** +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/presets/ -H 'Content-Type: application/json' \ +/// -H 'Authorization: ' +/// ``` #[get("/presets/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_presets(id: web::Path) -> Result { @@ -267,10 +359,14 @@ async fn get_presets(id: web::Path) -> Result Err(ServiceError::InternalServerError) } -/// curl -X PUT http://localhost:8080/api/presets/1 --header 'Content-Type: application/json' \ -/// --data '{"name": "", "text": "", "x": "", "y": "", "fontsize": 24, \ -/// "line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0}' \ -/// --header 'Authorization: ' +/// **Update Preset** +/// +/// ```BASH +/// curl -X PUT http://localhost:8000/api/presets/1 -H 'Content-Type: application/json' \ +/// -d '{ "name": "", "text": "", "x": "", "y": "", "fontsize": 24, \ +/// "line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0, "channel_id": 1 }' \ +/// -H 'Authorization: ' +/// ``` #[put("/presets/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn update_preset( @@ -284,10 +380,14 @@ async fn update_preset( Err(ServiceError::InternalServerError) } -/// curl -X POST http://localhost:8080/api/presets/ --header 'Content-Type: application/json' \ -/// --data '{"name": "", "text": "TEXT>", "x": "", "y": "", "fontsize": 24, \ -/// "line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0}}' \ -/// --header 'Authorization: ' +/// **Add new Preset** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/presets/ -H 'Content-Type: application/json' \ +/// -d '{ "name": "", "text": "TEXT>", "x": "", "y": "", "fontsize": 24, \ +/// "line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0, "channel_id": 1 }' \ +/// -H 'Authorization: ' +/// ``` #[post("/presets/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn add_preset(data: web::Json) -> Result { @@ -298,21 +398,39 @@ async fn add_preset(data: web::Json) -> Result' +/// ``` +#[delete("/presets/{id}")] +#[has_any_role("Role::Admin", "Role::User", type = "Role")] +async fn delete_preset(id: web::Path) -> Result { + if db_delete_preset(&id).await.is_ok() { + return Ok("Delete preset Success"); + } + + Err(ServiceError::InternalServerError) +} + +/// ### ffplayout controlling /// /// here we communicate with the engine for: /// - jump to last or next clip /// - reset playlist state /// - get infos about current, next, last clip -/// - send text the the engine, for overlaying it (as lower third etc.) -/// ---------------------------------------------------------------------------- - -/// curl -X POST http://localhost:8080/api/control/1/text/ \ -/// --header 'Content-Type: application/json' --header 'Authorization: ' \ -/// --data '{"text": "Hello from ffplayout", "x": "(w-text_w)/2", "y": "(h-text_h)/2", \ +/// - send text to the engine, for overlaying it (as lower third etc.) +/// +/// **Send Text to ffplayout** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/control/1/text/ \ +/// -H 'Content-Type: application/json' -H 'Authorization: ' \ +/// -d '{"text": "Hello from ffplayout", "x": "(w-text_w)/2", "y": "(h-text_h)/2", \ /// "fontsize": "24", "line_spacing": "4", "fontcolor": "#ffffff", "box": "1", \ /// "boxcolor": "#000000", "boxborderw": "4", "alpha": "1.0"}' +/// ``` #[post("/control/{id}/text/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn send_text_message( @@ -325,41 +443,55 @@ pub async fn send_text_message( } } -/// curl -X POST http://localhost:8080/api/control/1/playout/next/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' -#[post("/control/{id}/playout/next/")] +/// **Control Playout** +/// +/// - next +/// - back +/// - reset +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/control/1/playout/next/ -H 'Content-Type: application/json' +/// -d '{ "command": "reset" }' -H 'Authorization: ' +/// ``` +#[post("/control/{id}/playout/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] -pub async fn jump_to_next(id: web::Path) -> Result { - match control_state(*id, "next".into()).await { +pub async fn control_playout(id: web::Path, control: web::Json) -> Result { + match control_state(*id, control.command.clone()).await { Ok(res) => return Ok(res.text().await.unwrap_or_else(|_| "Success".into())), Err(e) => Err(e), } } -/// curl -X POST http://localhost:8080/api/control/1/playout/back/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' -#[post("/control/{id}/playout/back/")] -#[has_any_role("Role::Admin", "Role::User", type = "Role")] -pub async fn jump_to_last(id: web::Path) -> Result { - match control_state(*id, "back".into()).await { - Ok(res) => return Ok(res.text().await.unwrap_or_else(|_| "Success".into())), - Err(e) => Err(e), - } -} - -/// curl -X POST http://localhost:8080/api/control/1/playout/reset/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' -#[post("/control/{id}/playout/reset/")] -#[has_any_role("Role::Admin", "Role::User", type = "Role")] -pub async fn reset_playout(id: web::Path) -> Result { - match control_state(*id, "reset".into()).await { - Ok(res) => return Ok(res.text().await.unwrap_or_else(|_| "Success".into())), - Err(e) => Err(e), - } -} - -/// curl -X GET http://localhost:8080/api/control/1/media/current/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' +/// **Get current Clip** +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/control/1/media/current +/// -H 'Content-Type: application/json' -H 'Authorization: ' +/// ``` +/// +/// **Response:** +/// +/// ```JSON +/// { +/// "jsonrpc": "2.0", +/// "result": { +/// "current_media": { +/// "category": "", +/// "duration": 154.2, +/// "out": 154.2, +/// "seek": 0.0, +/// "source": "/opt/tv-media/clip.mp4" +/// }, +/// "index": 39, +/// "play_mode": "playlist", +/// "played_sec": 67.80771999300123, +/// "remaining_sec": 86.39228000699876, +/// "start_sec": 24713.631999999998, +/// "start_time": "06:51:53.631" +/// }, +/// "id": 1 +/// } +/// ``` #[get("/control/{id}/media/current")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn media_current(id: web::Path) -> Result { @@ -369,8 +501,11 @@ pub async fn media_current(id: web::Path) -> Result' +/// **Get next Clip** +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/control/1/media/next/ -H 'Authorization: ' +/// ``` #[get("/control/{id}/media/next")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn media_next(id: web::Path) -> Result { @@ -380,8 +515,12 @@ pub async fn media_next(id: web::Path) -> Result' +/// **Get last Clip** +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/control/1/media/last/ +/// -H 'Content-Type: application/json' -H 'Authorization: ' +/// ``` #[get("/control/{id}/media/last")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn media_last(id: web::Path) -> Result { @@ -391,9 +530,19 @@ pub async fn media_last(id: web::Path) -> Result' +/// #### ffplayout Process Control +/// +/// Control ffplayout process, like: +/// - start +/// - stop +/// - restart +/// - status +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/control/1/process/ +/// -H 'Content-Type: application/json' -H 'Authorization: ' /// -d '{"command": "start"}' +/// ``` #[post("/control/{id}/process/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn process_control( @@ -403,13 +552,14 @@ pub async fn process_control( control_service(*id, &proc.command).await } -/// ---------------------------------------------------------------------------- -/// ffplayout playlist operations +/// #### ffplayout Playlist Operations /// -/// ---------------------------------------------------------------------------- - -/// curl -X GET http://localhost:8080/api/playlist/1?date=2022-06-20 -/// --header 'Content-Type: application/json' --header 'Authorization: ' +/// **Get playlist** +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/playlist/1?date=2022-06-20 +/// -H 'Content-Type: application/json' -H 'Authorization: ' +/// ``` #[get("/playlist/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn get_playlist( @@ -422,9 +572,13 @@ pub async fn get_playlist( } } -/// curl -X POST http://localhost:8080/api/playlist/1/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' +/// **Save playlist** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/playlist/1/ +/// -H 'Content-Type: application/json' -H 'Authorization: ' /// -- data "{}" +/// ``` #[post("/playlist/{id}/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn save_playlist( @@ -437,8 +591,14 @@ pub async fn save_playlist( } } -/// curl -X GET http://localhost:8080/api/playlist/1/generate/2022-06-20 -/// --header 'Content-Type: application/json' --header 'Authorization: ' +/// **Generate Playlist** +/// +/// A new playlist will be generated and response. +/// +/// ```BASH +/// curl -X GET http://localhost:8000/api/playlist/1/generate/2022-06-20 +/// -H 'Content-Type: application/json' -H 'Authorization: ' +/// ``` #[get("/playlist/{id}/generate/{date}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn gen_playlist( @@ -450,8 +610,12 @@ pub async fn gen_playlist( } } -/// curl -X DELETE http://localhost:8080/api/playlist/1/2022-06-20 -/// --header 'Content-Type: application/json' --header 'Authorization: ' +/// **Delete Playlist** +/// +/// ```BASH +/// curl -X DELETE http://localhost:8000/api/playlist/1/2022-06-20 +/// -H 'Content-Type: application/json' -H 'Authorization: ' +/// ``` #[delete("/playlist/{id}/{date}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn del_playlist( @@ -463,11 +627,14 @@ pub async fn del_playlist( } } -/// ---------------------------------------------------------------------------- -/// read log file +/// ### Log file /// -/// ---------------------------------------------------------------------------- - +/// **Read Log Life** +/// +/// ```BASH +/// curl -X Get http://localhost:8000/api/log/1 +/// -H 'Content-Type: application/json' -H 'Authorization: ' +/// ``` #[get("/log/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn get_log( @@ -477,13 +644,14 @@ pub async fn get_log( read_log_file(&id, &log.date).await } -/// ---------------------------------------------------------------------------- -/// file operations +/// ### File Operations /// -/// ---------------------------------------------------------------------------- - -/// curl -X GET http://localhost:8080/api/file/1/browse/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' +/// **Get File/Folder List** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/file/1/browse/ -H 'Content-Type: application/json' +/// -d '{ "source": "/" }' -H 'Authorization: ' +/// ``` #[post("/file/{id}/browse/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn file_browser( @@ -496,9 +664,12 @@ pub async fn file_browser( } } -/// curl -X POST http://localhost:8080/api/file/1/create-folder/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' -/// -d '{"source": ""}' +/// **Create Folder** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/file/1/create-folder/ -H 'Content-Type: application/json' +/// -d '{"source": ""}' -H 'Authorization: ' +/// ``` #[post("/file/{id}/create-folder/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn add_dir( @@ -508,9 +679,12 @@ pub async fn add_dir( create_directory(*id, &data.into_inner()).await } -/// curl -X POST http://localhost:8080/api/file/1/move/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' -/// -d '{"source": "", "target": ""}' +/// **Rename File** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/file/1/rename/ -H 'Content-Type: application/json' +/// -d '{"source": "", "target": ""}' -H 'Authorization: ' +/// ``` #[post("/file/{id}/rename/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn move_rename( @@ -523,9 +697,12 @@ pub async fn move_rename( } } -/// curl -X POST http://localhost:8080/api/file/1/remove/ -/// --header 'Content-Type: application/json' --header 'Authorization: ' -/// -d '{"source": ""}' +/// **Remove File/Folder** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/file/1/remove/ -H 'Content-Type: application/json' +/// -d '{"source": ""}' -H 'Authorization: ' +/// ``` #[post("/file/{id}/remove/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] pub async fn remove( @@ -538,6 +715,12 @@ pub async fn remove( } } +/// **Upload File** +/// +/// ```BASH +/// curl -X POST http://localhost:8000/api/file/1/upload/ -H 'Authorization: ' +/// -F "file=@file.mp4" +/// ``` #[put("/file/{id}/upload/")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn save_file( diff --git a/build_all.sh b/scripts/build_all.sh similarity index 100% rename from build_all.sh rename to scripts/build_all.sh diff --git a/scripts/gen_doc.sh b/scripts/gen_doc.sh new file mode 100755 index 00000000..01024f5c --- /dev/null +++ b/scripts/gen_doc.sh @@ -0,0 +1,23 @@ +#!/usr/bin/bash + +input=$1 +output=$2 +print_block=false + +if [ ! "$input" ] || [ ! "$output" ]; then + echo "Run script like: den_doc.sh input.rs output.md" +fi + +:> "$output" + +while IFS= read -r line; do + if echo $line | grep -Eq "^///"; then + echo "$line" | sed -E "s|^/// ?||g" >> "$output" + print_block=true + fi + + if [ -z "$line" ] && [[ $print_block == true ]]; then + echo "" >> "$output" + print_block=false + fi +done < "$input"