auto create types for playout config, switch to real playout config form
This commit is contained in:
parent
e3955faaad
commit
7f7ca6a237
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[env]
|
||||
TS_RS_EXPORT_DIR = { value = "frontend/types", relative = true }
|
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -1266,6 +1266,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml_edit",
|
||||
"ts-rs",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
"zeromq",
|
||||
@ -3663,6 +3664,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "0.24.0-rc2"
|
||||
@ -3900,6 +3910,30 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"thiserror",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.2"
|
||||
|
@ -1,7 +1,7 @@
|
||||
## Closed Captions
|
||||
|
||||
#### Note:
|
||||
**This is only an _experimental feature_. Please be aware that bugs and unexpected behavior may occur. To utilize this feature, a [special patched](https://github.com/jb-alvarado/compile-ffmpeg-osx-linux) version of FFmpeg is required. Importantly, there is currently no official support for this functionality.**
|
||||
**This is only an _experimental feature_. Please be aware that bugs and unexpected behavior may occur. To utilize this feature, a version after 7.1 of FFmpeg is required. Importantly, there is currently no official support for this functionality.**
|
||||
|
||||
### Usage
|
||||
**ffplayout** can handle closed captions in WebVTT format for HLS streaming.
|
||||
@ -16,7 +16,7 @@ To encode the closed captions, the **hls** mode needs to be enabled, and specifi
|
||||
-profile:v Main -level 3.1 -c:a aac -ar 44100 -b:a 128k -flags +cgop \
|
||||
-muxpreload 0 -muxdelay 0 -f hls -hls_time 6 -hls_list_size 600 \
|
||||
-hls_flags append_list+delete_segments+omit_endlist \
|
||||
-var_stream_map v:0,a:0,s:0,sgroup:subs,name:English,language:en-US,default:YES \
|
||||
-var_stream_map v:0,a:0,s:0,sgroup:subs,sname:English,language:en-US,default:YES \
|
||||
-master_pl_name master.m3u8 \
|
||||
-hls_segment_filename \
|
||||
live/stream-%d.ts live/stream.m3u8
|
||||
|
@ -69,6 +69,11 @@ cargo deb --no-build --target=aarch64-unknown-linux-gnu --variant=arm64 -p ffpla
|
||||
cargo generate-rpm --target=x86_64-unknown-linux-musl
|
||||
```
|
||||
|
||||
## Generate types for Frontend
|
||||
The frontend uses TypeScript, to generate types for the rust structs run: `cargo test`.
|
||||
|
||||
The generated types are then in [types folder](/frontend/types).
|
||||
|
||||
## Setup Frontend
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
@ -61,6 +61,7 @@ time = { version = "0.3", features = ["formatting", "macros"] }
|
||||
tokio = { version = "1.29", features = ["full"] }
|
||||
tokio-stream = "0.1"
|
||||
toml_edit = {version = "0.22", features = ["serde"]}
|
||||
ts-rs = { version = "10", features = ["chrono-impl", "no-serde-warnings"] }
|
||||
uuid = "1.8"
|
||||
walkdir = "2"
|
||||
zeromq = { version = "0.4", default-features = false, features = [
|
||||
|
@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
use shlex::split;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tokio::{fs, io::AsyncReadExt};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::db::{handles, models};
|
||||
use crate::utils::{files::norm_abs_path, free_tcp_socket, time_to_sec};
|
||||
@ -52,7 +53,8 @@ pub const FFMPEG_UNRECOVERABLE_ERRORS: [&str; 6] = [
|
||||
"Unrecognized option",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum OutputMode {
|
||||
Desktop,
|
||||
@ -103,7 +105,8 @@ impl fmt::Display for OutputMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProcessMode {
|
||||
Folder,
|
||||
@ -141,14 +144,16 @@ impl FromStr for ProcessMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||
pub struct Template {
|
||||
pub sources: Vec<Source>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||
pub struct Source {
|
||||
#[ts(type = "string")]
|
||||
pub start: NaiveTime,
|
||||
#[ts(type = "string")]
|
||||
pub duration: NaiveTime,
|
||||
pub shuffle: bool,
|
||||
pub paths: Vec<PathBuf>,
|
||||
@ -157,10 +162,14 @@ pub struct Source {
|
||||
/// Channel Config
|
||||
///
|
||||
/// This we init ones, when ffplayout is starting and use them globally in the hole program.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct PlayoutConfig {
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub channel: Channel,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub advanced: AdvancedConfig,
|
||||
pub general: General,
|
||||
@ -176,7 +185,7 @@ pub struct PlayoutConfig {
|
||||
pub output: Output,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
pub struct Channel {
|
||||
pub logs: PathBuf,
|
||||
pub public: PathBuf,
|
||||
@ -197,23 +206,32 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct General {
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub id: i32,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub channel_id: i32,
|
||||
pub stop_threshold: f64,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub generate: Option<Vec<String>>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub ffmpeg_filters: Vec<String>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub ffmpeg_libs: Vec<String>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub template: Option<Template>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub skip_validation: bool,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub validate: bool,
|
||||
}
|
||||
@ -234,18 +252,24 @@ impl General {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Mail {
|
||||
pub subject: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub smtp_server: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub starttls: bool,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub sender_addr: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub sender_pass: String,
|
||||
pub recipient: String,
|
||||
#[ts(type = "string")]
|
||||
pub mail_level: Level,
|
||||
pub interval: i64,
|
||||
}
|
||||
@ -280,7 +304,8 @@ impl Default for Mail {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Logging {
|
||||
pub ffmpeg_level: String,
|
||||
pub ingest_level: String,
|
||||
@ -303,7 +328,8 @@ impl Logging {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Processing {
|
||||
pub mode: ProcessMode,
|
||||
pub audio_only: bool,
|
||||
@ -315,6 +341,7 @@ pub struct Processing {
|
||||
pub fps: f64,
|
||||
pub add_logo: bool,
|
||||
pub logo: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub logo_path: String,
|
||||
pub logo_scale: String,
|
||||
@ -330,6 +357,7 @@ pub struct Processing {
|
||||
pub vtt_enable: bool,
|
||||
#[serde(default)]
|
||||
pub vtt_dummy: Option<String>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub cmd: Option<Vec<String>>,
|
||||
}
|
||||
@ -363,11 +391,13 @@ impl Processing {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Ingest {
|
||||
pub enable: bool,
|
||||
pub input_param: String,
|
||||
pub custom_filter: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub input_cmd: Option<Vec<String>>,
|
||||
}
|
||||
@ -383,12 +413,15 @@ impl Ingest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Playlist {
|
||||
pub day_start: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub start_sec: Option<f64>,
|
||||
pub length: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub length_sec: Option<f64>,
|
||||
pub infinit: bool,
|
||||
@ -406,17 +439,22 @@ impl Playlist {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Storage {
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub path: PathBuf,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub paths: Vec<PathBuf>,
|
||||
pub filler: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub filler_path: PathBuf,
|
||||
pub extensions: Vec<String>,
|
||||
pub shuffle: bool,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub shared_storage: bool,
|
||||
}
|
||||
@ -439,17 +477,23 @@ impl Storage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Text {
|
||||
pub add_text: bool,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub node_pos: Option<usize>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub zmq_stream_socket: Option<String>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub zmq_server_socket: Option<String>,
|
||||
#[ts(rename = "font")]
|
||||
#[serde(alias = "fontfile")]
|
||||
pub font: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub font_path: String,
|
||||
pub text_from_filename: bool,
|
||||
@ -473,7 +517,8 @@ impl Text {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Task {
|
||||
pub enable: bool,
|
||||
pub path: PathBuf,
|
||||
@ -488,14 +533,18 @@ impl Task {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
|
||||
#[ts(export, export_to = "playout_config.d.ts")]
|
||||
pub struct Output {
|
||||
pub mode: OutputMode,
|
||||
pub output_param: String,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub output_count: usize,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub output_filter: Option<String>,
|
||||
#[ts(skip)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub output_cmd: Option<Vec<String>>,
|
||||
}
|
||||
|
@ -6,80 +6,614 @@
|
||||
class="mt-10 grid md:grid-cols-[180px_auto] gap-5"
|
||||
@submit.prevent="onSubmitPlayout"
|
||||
>
|
||||
<template v-for="(item, key) in configStore.playout" :key="key">
|
||||
<div class="text-xl pt-3 text-right">{{ setTitle(key.toString()) }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ setHelp(key.toString()) }}
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
v-for="(prop, name) in (item as Record<string, any>)"
|
||||
:key="name"
|
||||
class="form-control w-full"
|
||||
:class="[typeof prop === 'boolean' && 'flex-row', name.toString() !== 'help_text' && 'mt-2']"
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.general') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.generalHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Stop Threshold</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.general.stop_threshold"
|
||||
type="number"
|
||||
min="3"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">{{ t('config.stopThreshold') }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.mail') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.mailHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Subject</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.mail.subject"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Recipient</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.mail.recipient"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Mail Level</span>
|
||||
</div>
|
||||
<select
|
||||
v-model="configStore.playout.mail.mail_level"
|
||||
class="select select-sm select-bordered w-full max-w-xs"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
name.toString() !== 'startInSec' &&
|
||||
name.toString() !== 'lengthInSec' &&
|
||||
!(name.toString() === 'path' && key.toString() === 'storage')
|
||||
"
|
||||
>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">{{ name }}</span>
|
||||
</div>
|
||||
<input
|
||||
v-if="name.toString() === 'sender_pass'"
|
||||
v-model="item[name]"
|
||||
type="password"
|
||||
:placeholder="t('config.placeholderPass')"
|
||||
class="input input-sm input-bordered w-full"
|
||||
/>
|
||||
<textarea
|
||||
v-else-if="name.toString() === 'output_param' || name.toString() === 'custom_filter'"
|
||||
v-model="item[name]"
|
||||
class="textarea textarea-bordered"
|
||||
rows="3"
|
||||
/>
|
||||
<input
|
||||
v-else-if="typeof prop === 'number' && prop % 1 === 0"
|
||||
v-model="item[name]"
|
||||
type="number"
|
||||
class="input input-sm input-bordered w-full"
|
||||
/>
|
||||
<input
|
||||
v-else-if="typeof prop === 'number'"
|
||||
v-model="item[name]"
|
||||
type="number"
|
||||
class="input input-sm input-bordered w-full"
|
||||
step="0.0001"
|
||||
style="max-width: 250px"
|
||||
/>
|
||||
<input
|
||||
v-else-if="typeof prop === 'boolean'"
|
||||
v-model="item[name]"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm ms-2 mt-2"
|
||||
/>
|
||||
<input
|
||||
v-else-if="name === 'ignore_lines'"
|
||||
v-model="formatIgnoreLines"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
:id="name"
|
||||
v-model="item[name]"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full"
|
||||
/>
|
||||
</template>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<option v-for="level in logLevels" :key="level" :value="level">{{ level }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Interval</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.mail.interval"
|
||||
type="number"
|
||||
min="30"
|
||||
step="10"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">{{ t('config.mailInterval') }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.logging') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.logHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">ffmpeg Level</span>
|
||||
</div>
|
||||
<select
|
||||
v-model="configStore.playout.logging.ffmpeg_level"
|
||||
class="select select-sm select-bordered w-full max-w-xs"
|
||||
>
|
||||
<option v-for="level in logLevels" :key="level" :value="level">{{ level }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Ingest Level</span>
|
||||
</div>
|
||||
<select
|
||||
v-model="configStore.playout.logging.ingest_level"
|
||||
class="select select-sm select-bordered w-full max-w-xs"
|
||||
>
|
||||
<option v-for="level in logLevels" :key="level" :value="level">{{ level }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="flex flex-row">
|
||||
<input
|
||||
v-model="configStore.playout.logging.detect_silence"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Detect Silence</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label py-0">
|
||||
<span class="label-text-alt">{{ t('config.logDetect') }}</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Ignore Lines</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="formatIgnoreLines"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full truncate"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">{{ t('config.logIgnore') }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.processing') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.processingHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Mode</span>
|
||||
</div>
|
||||
<select
|
||||
v-model="configStore.playout.processing.mode"
|
||||
class="select select-sm select-bordered w-full max-w-xs"
|
||||
>
|
||||
<option v-for="mode in processingMode" :key="mode" :value="mode">{{ mode }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.processing.audio_only"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Audio Only</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.processing.copy_audio"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Copy Audio</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.processing.copy_video"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Copy Video</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Width</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.width"
|
||||
type="number"
|
||||
min="-1"
|
||||
step="1"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Height</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.height"
|
||||
type="number"
|
||||
min="-1"
|
||||
step="1"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Aspect</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.aspect"
|
||||
type="number"
|
||||
min="1"
|
||||
step="0.001"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">FPS</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.fps"
|
||||
type="number"
|
||||
min="1"
|
||||
step="0.01"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.processing.add_logo"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Add Logo</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Logo</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.logo"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Logo Opacity</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.logo_opacity"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Logo Scale</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.logo_scale"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-md"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Logo Position</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.logo_position"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-md"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Audio Tracks</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.audio_tracks"
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Audio Track Index</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.audio_track_index"
|
||||
type="number"
|
||||
min="-1"
|
||||
max="255"
|
||||
step="1"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Audio Channels</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.audio_channels"
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Volumen</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.volume"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.001"
|
||||
class="input input-sm input-bordered w-full max-w-36"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Custom Filter</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="configStore.playout.processing.custom_filter"
|
||||
class="textarea textarea-bordered"
|
||||
rows="3"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.processing.vtt_enable"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Enable VTT</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">VTT Dummy</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.processing.vtt_dummy"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.ingest') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.ingestHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.ingest.enable"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Enable</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Input Param</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.ingest.input_param"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Custom Filter</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="configStore.playout.ingest.custom_filter"
|
||||
class="textarea textarea-bordered"
|
||||
rows="3"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.playlist') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.playlistHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Day Start</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.playlist.day_start"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-xs"
|
||||
pattern="([01]?[0-9]|2[0-4]):[0-5][0-9]:[0-5][0-9]"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Length</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.playlist.length"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-xs"
|
||||
pattern="([01]?[0-9]|2[0-4]):[0-5][0-9]:[0-5][0-9]"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.playlist.infinit"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Enable</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.storage') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.storageHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Filler</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.storage.filler"
|
||||
type="text"
|
||||
name="filler"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Extensions</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.storage.extensions"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.storage.shuffle"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Shuffle</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.text') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.textHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.text.add_text"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Add Text</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Font</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.text.font"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.text.text_from_filename"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Text from File</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Style</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.text.style"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full truncate"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Regex</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.text.regex"
|
||||
type="text"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.task') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.taskHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full flex-row mt-2">
|
||||
<input
|
||||
v-model="configStore.playout.task.enable"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm me-1 mt-2"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Enable</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text text-base font-bold">Path</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="configStore.playout.task.path"
|
||||
type="text"
|
||||
name="task_path"
|
||||
class="input input-sm input-bordered w-full max-w-lg"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-xl pt-3 md:text-right">{{ t('config.output') }}:</div>
|
||||
<div class="md:pt-4">
|
||||
<label class="form-control mb-2">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ t('config.outputHelp') }}
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Mode</span>
|
||||
</div>
|
||||
<select
|
||||
v-model="configStore.playout.output.mode"
|
||||
class="select select-sm select-bordered w-full max-w-xs"
|
||||
>
|
||||
<option v-for="mode in outputMode" :key="mode" :value="mode">{{ mode }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-control w-full mt-2">
|
||||
<div class="label">
|
||||
<span class="label-text !text-md font-bold">Output Parameter</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="configStore.playout.output.output_param"
|
||||
class="textarea textarea-bordered"
|
||||
rows="6"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-5 mb-10">
|
||||
<button class="btn btn-primary" type="submit">{{ t('config.save') }}</button>
|
||||
</div>
|
||||
@ -104,6 +638,10 @@ const indexStore = useIndex()
|
||||
|
||||
const showModal = ref(false)
|
||||
|
||||
const logLevels = ['INFO', 'WARNING', 'ERROR']
|
||||
const processingMode = ['folder', 'playlist']
|
||||
const outputMode = ['desktop', 'hls', 'stream', 'null']
|
||||
|
||||
const formatIgnoreLines = computed({
|
||||
get() {
|
||||
return configStore.playout.logging.ignore_lines.join(';')
|
||||
@ -114,65 +652,6 @@ const formatIgnoreLines = computed({
|
||||
},
|
||||
})
|
||||
|
||||
function setTitle(input: string): string {
|
||||
switch (input) {
|
||||
case 'general':
|
||||
return t('config.general')
|
||||
case 'rpc_server':
|
||||
return t('config.rpcServer')
|
||||
case 'mail':
|
||||
return t('config.mail')
|
||||
case 'logging':
|
||||
return t('config.logging')
|
||||
case 'processing':
|
||||
return t('config.processing')
|
||||
case 'ingest':
|
||||
return t('config.ingest')
|
||||
case 'playlist':
|
||||
return t('config.playlist')
|
||||
case 'storage':
|
||||
return t('config.storage')
|
||||
case 'text':
|
||||
return t('config.text')
|
||||
case 'task':
|
||||
return t('config.task')
|
||||
case 'output':
|
||||
return t('config.output')
|
||||
default:
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
function setHelp(key: string): string {
|
||||
console.log('key:', key)
|
||||
switch (key) {
|
||||
case 'general':
|
||||
return t('config.generalHelp')
|
||||
case 'rpc_server':
|
||||
return t('config.rpcHelp')
|
||||
case 'mail':
|
||||
return t('config.mailHelp')
|
||||
case 'logging':
|
||||
return t('config.logHelp')
|
||||
case 'processing':
|
||||
return t('config.processingHelp')
|
||||
case 'ingest':
|
||||
return t('config.ingestHelp')
|
||||
case 'playlist':
|
||||
return t('config.playlistHelp')
|
||||
case 'storage':
|
||||
return t('config.storageHelp')
|
||||
case 'text':
|
||||
return t('config.textHelp')
|
||||
case 'task':
|
||||
return t('config.taskHelp')
|
||||
case 'output':
|
||||
return t('config.outputHelp')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmitPlayout() {
|
||||
const update = await configStore.setPlayoutConfig(configStore.playout)
|
||||
configStore.onetimeInfo = true
|
||||
|
@ -183,10 +183,9 @@ export default {
|
||||
placeholderPass: 'Passwort',
|
||||
help: 'Hilfe',
|
||||
generalHelp: `Manchmal kann es vorkommen, dass eine Datei beschädigt ist, aber dennoch abspielbar. Dies kann einen Streaming-Fehler für alle folgenden Dateien verursachen. Die einzige Lösung in diesem Fall ist, ffplayout zu stoppen und es erneut zu starten.
|
||||
'stop_threshold' stoppt ffplayout, wenn es asynchron in der Zeit über diesen Wert hinaus ist. Ein Wert unter 3 kann unerwartete Fehler verursachen.`,
|
||||
'Stop Threshold' stoppt ffplayout, wenn es asynchron in der Zeit über diesen Wert hinaus ist. Ein Wert unter 3 kann unerwartete Fehler verursachen.`,
|
||||
mailHelp: `Sende Fehlermeldungen an eine E-Mail-Adresse, wie z.B. fehlende Clips, fehlendes oder ungültiges Playlist-Format usw. Lass den Empfänger leer, wenn dies nicht benötigt wird.
|
||||
'mail_level' kann INFO, WARNING oder ERROR sein.
|
||||
'interval' bezieht sich auf die Anzahl der Sekunden bis zur nächsten E-Mail; der Wert muss in Schritten von 10 erfolgen und darf nicht weniger als 30 Sekunden betragen.`,
|
||||
'Interval' bezieht sich auf die Anzahl der Sekunden bis zur nächsten E-Mail; der Wert muss in Schritten von 10 erfolgen und darf nicht weniger als 30 Sekunden betragen.`,
|
||||
logHelp: `'ffmpeg_level/ingest_level' kann INFO, WARNING oder ERROR sein.
|
||||
'detect_silence' protokolliert eine Fehlermeldung, wenn die Audioleitung während des Validierungsprozesses 15 Sekunden lang stumm ist.
|
||||
'ignore' erlaubt dem Protokoll, Zeichenfolgen zu ignorieren, die übereinstimmende Zeilen enthalten; das Format ist eine durch Semikolons getrennte Liste.`,
|
||||
|
@ -182,23 +182,23 @@ export default {
|
||||
output: 'Output',
|
||||
placeholderPass: 'Password',
|
||||
help: 'Help',
|
||||
generalHelp: `Sometimes it can happen that a file is corrupt but still playable. This can produce a streaming error for all following files. The only solution in this case is to stop ffplayout and start it again.
|
||||
'stop_threshold' stops ffplayout if it is asynchronous in time above this value. A number below 3 can cause unexpected errors.`,
|
||||
mailHelp: `Send error messages to an email address, such as missing clips, missing or invalid playlist format, etc.. Leave the recipient blank if you don't need this.
|
||||
'mail_level' can be INFO, WARNING, or ERROR.
|
||||
'interval' refers to the number of seconds until a new email is sent; the value must be in increments of 10 and not lower then 30 seconds.`,
|
||||
logHelp: `'ffmpeg_level/ingest_level' can be INFO, WARNING, or ERROR.
|
||||
'detect_silence' logs an error message if the audio line is silent for 15 seconds during the validation process.
|
||||
'ignore' allows logging to ignore strings that contain matched lines; the format is a semicolon-separated list.`,
|
||||
generalHelp: 'Sometimes it can happen that a file is corrupt but still playable. This can produce a streaming error for all following files. The only solution in this case is to stop ffplayout and start it again.',
|
||||
stopThreshold: 'The threshold stops ffplayout if it is asynchronous in time above this value. A number below 3 can cause unexpected errors.',
|
||||
mailHelp: `Send error messages to an email address, such as missing clips, missing or invalid playlist format, etc.. Leave the recipient blank if you don't need this.`,
|
||||
mailInterval: 'The interval refers to the number of seconds until a new email is sent; the value must be in increments of 10 and not lower then 30 seconds.',
|
||||
logHelp: 'Adjust logging behavior.',
|
||||
logDetect: 'Logs an error message if the audio line is silent for 15 seconds during the validation process.',
|
||||
logIgnore: 'Ignore strings that contain matched lines; the format is a semicolon-separated list.',
|
||||
processingHelp: `Default processing for all clips ensures uniqueness. The mode can be either 'playlist' or 'folder'.
|
||||
The 'aspect' parameter must be a float number.
|
||||
The 'audio_tracks' parameter specifies how many audio tracks should be processed.'audio_channels' can be used if the audio has more channels than stereo.
|
||||
The 'logo' is used only if the path exists; the path is relative to your storage folder.
|
||||
'logo_scale' scales the logo to the target size. Leave it blank if no scaling is needed. The format is 'width:height', for example, '100:-1' for proportional scaling. The 'logo_opacity' option allows the logo to become transparent.'logo_position' is specified in the format 'x:y', which sets the logo's position.
|
||||
With 'custom_filter', it is possible to apply additional filters. The filter outputs should end with [c_v_out] for video filters and [c_a_out] for audio filters.
|
||||
'vtt_enable' can only be used in HLS mode, and only when *.vtt files with the same filename as the video file exist.`,
|
||||
The 'Aspect' parameter must be a float number.
|
||||
The 'Audio Tracks' parameter specifies how many audio tracks should be processed. 'Audio Channels' can be used if the audio has more channels than stereo.
|
||||
The 'Logo' is used only if the path exists; the path is relative to your storage folder.
|
||||
'Logo Scale' scales the logo to the target size. Leave it blank if no scaling is needed. The format is 'width:height', for example: '100:-1' for proportional scaling. The 'logo_opacity' option allows the logo to become transparent.
|
||||
'Logo Position' is specified in the format 'x:y', which sets the logo's position.
|
||||
With 'Custom Filter', it is possible to apply additional filters. The filter outputs should end with [c_v_out] for video filters and [c_a_out] for audio filters.
|
||||
'Enable VTT' can only be used in HLS mode, and only when *.vtt files with the same filename as the video file exist.`,
|
||||
ingestHelp: `Run a server for an ingest stream. This stream will override the normal streaming until it is finished. There is only a very simple authentication mechanism, which checks if the stream name is correct.
|
||||
'custom_filter' can be used in the same way as the one in the process section.`,
|
||||
'Custom Filter' can be used in the same way as the one in the process section.`,
|
||||
playlistHelp: `'day_start' indicates at what time the playlist should start; leave 'day_start' blank if the playlist should always start at the beginning. 'length' represents the target length of the playlist; when it is blank, the real length will not be considered.
|
||||
'infinite: true' works with a single playlist file and loops it infinitely.`,
|
||||
storageHelp: `'filler' is used to play in place of a missing file or to fill the remaining time to reach a total of 24 hours. It can be a file or folder and will loop when necessary.
|
||||
|
@ -183,10 +183,9 @@ export default {
|
||||
placeholderPass: 'Senha',
|
||||
help: 'Ajuda',
|
||||
generalHelp: `Às vezes, pode acontecer que um arquivo esteja corrompido, mas ainda seja reproduzível. Isso pode produzir um erro de streaming para todos os arquivos seguintes. A única solução nesse caso é parar o ffplayout e iniciá-lo novamente.
|
||||
'stop_threshold' para o ffplayout se ele estiver fora de sincronia no tempo acima desse valor. Um número abaixo de 3 pode causar erros inesperados.`,
|
||||
'Stop Threshold' para o ffplayout se ele estiver fora de sincronia no tempo acima desse valor. Um número abaixo de 3 pode causar erros inesperados.`,
|
||||
mailHelp: `Envie mensagens de erro para um endereço de e-mail, como clipes ausentes, formato de playlist ausente ou inválido, etc. Deixe o destinatário em branco se você não precisar disso.
|
||||
'mail_level' pode ser INFO, WARNING ou ERROR.
|
||||
'interval' refere-se ao número de segundos até que um novo e-mail seja enviado; o valor deve ser em incrementos de 10 e não inferior a 30 segundos.`,
|
||||
'Interval' refere-se ao número de segundos até que um novo e-mail seja enviado; o valor deve ser em incrementos de 10 e não inferior a 30 segundos.`,
|
||||
logHelp: `'ffmpeg_level/ingest_level' pode ser INFO, WARNING ou ERROR.
|
||||
'detect_silence' registra uma mensagem de erro se a linha de áudio estiver em silêncio por 15 segundos durante o processo de validação.
|
||||
'ignore' permite que o log ignore cadeias de caracteres que contenham linhas correspondentes; o formato é uma lista separada por ponto e vírgula.`,
|
||||
|
@ -183,10 +183,9 @@ export default {
|
||||
placeholderPass: 'Password',
|
||||
help: 'Help',
|
||||
generalHelp: `Sometimes it can happen that a file is corrupt but still playable. This can produce a streaming error for all following files. The only solution in this case is to stop ffplayout and start it again.
|
||||
'stop_threshold' stops ffplayout if it is asynchronous in time above this value. A number below 3 can cause unexpected errors.`,
|
||||
'Stop Threshold' stops ffplayout if it is asynchronous in time above this value. A number below 3 can cause unexpected errors.`,
|
||||
mailHelp: `Send error messages to an email address, such as missing clips, missing or invalid playlist format, etc.. Leave the recipient blank if you don't need this.
|
||||
'mail_level' can be INFO, WARNING, or ERROR.
|
||||
'interval' refers to the number of seconds until a new email is sent; the value must be in increments of 10 and not lower then 30 seconds.`,
|
||||
'Interval' refers to the number of seconds until a new email is sent; the value must be in increments of 10 and not lower then 30 seconds.`,
|
||||
logHelp: `'ffmpeg_level/ingest_level' can be INFO, WARNING, or ERROR.
|
||||
'detect_silence' logs an error message if the audio line is silent for 15 seconds during the validation process.
|
||||
'ignore' allows logging to ignore strings that contain matched lines; the format is a semicolon-separated list.`,
|
||||
|
@ -57,7 +57,7 @@
|
||||
<i class="bi-files" />
|
||||
</button>
|
||||
<button
|
||||
v-if="!configStore.playout.playlist.loop"
|
||||
v-if="!configStore.playout.playlist.infinit"
|
||||
class="btn btn-sm btn-primary join-item"
|
||||
:title="t('player.loop')"
|
||||
@click="loopClips()"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
|
||||
export const useConfig = defineStore('config', {
|
||||
state: () => ({
|
||||
i: 0,
|
||||
@ -10,7 +11,7 @@ export const useConfig = defineStore('config', {
|
||||
channelsRaw: [] as Channel[],
|
||||
playlistLength: 86400.0,
|
||||
advanced: {} as any,
|
||||
playout: {} as any,
|
||||
playout: {} as PlayoutConfigExt,
|
||||
currentUser: 0,
|
||||
configUser: {} as User,
|
||||
utcOffset: 0,
|
||||
|
10
frontend/types/index.d.ts
vendored
10
frontend/types/index.d.ts
vendored
@ -1,4 +1,5 @@
|
||||
import type { JwtPayload } from 'jwt-decode'
|
||||
import type { PlayoutConfig, Playlist as Ply } from '~/types/playout_config'
|
||||
|
||||
export {}
|
||||
|
||||
@ -9,6 +10,15 @@ declare global {
|
||||
role: string
|
||||
}
|
||||
|
||||
interface PlaylistExt extends Ply {
|
||||
startInSec: number,
|
||||
lengthInSec: number
|
||||
}
|
||||
|
||||
interface PlayoutConfigExt extends PlayoutConfig {
|
||||
playlist: PlaylistExt
|
||||
}
|
||||
|
||||
interface LoginObj {
|
||||
message: string
|
||||
status: number
|
||||
|
32
frontend/types/playout_config.d.ts
vendored
Normal file
32
frontend/types/playout_config.d.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type General = { stop_threshold: number, };
|
||||
|
||||
export type Ingest = { enable: boolean, input_param: string, custom_filter: string, };
|
||||
|
||||
export type Logging = { ffmpeg_level: string, ingest_level: string, detect_silence: boolean, ignore_lines: Array<string>, };
|
||||
|
||||
export type Mail = { subject: string, recipient: string, mail_level: string, interval: bigint, };
|
||||
|
||||
export type Output = { mode: OutputMode, output_param: string, };
|
||||
|
||||
export type OutputMode = "desktop" | "hls" | "null" | "stream";
|
||||
|
||||
export type Playlist = { day_start: string, length: string, infinit: boolean, };
|
||||
|
||||
/**
|
||||
* Channel Config
|
||||
*
|
||||
* This we init ones, when ffplayout is starting and use them globally in the hole program.
|
||||
*/
|
||||
export type PlayoutConfig = { general: General, mail: Mail, logging: Logging, processing: Processing, ingest: Ingest, playlist: Playlist, storage: Storage, text: Text, task: Task, output: Output, };
|
||||
|
||||
export type ProcessMode = "folder" | "playlist";
|
||||
|
||||
export type Processing = { mode: ProcessMode, audio_only: boolean, copy_audio: boolean, copy_video: boolean, width: bigint, height: bigint, aspect: number, fps: number, add_logo: boolean, logo: string, logo_scale: string, logo_opacity: number, logo_position: string, audio_tracks: number, audio_track_index: number, audio_channels: number, volume: number, custom_filter: string, vtt_enable: boolean, vtt_dummy: string | null, };
|
||||
|
||||
export type Storage = { filler: string, extensions: Array<string>, shuffle: boolean, };
|
||||
|
||||
export type Task = { enable: boolean, path: string, };
|
||||
|
||||
export type Text = { add_text: boolean, font: string, text_from_filename: boolean, style: string, regex: string, };
|
Loading…
Reference in New Issue
Block a user