commit
b39d70f2fc
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -1217,7 +1217,7 @@ checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout"
|
name = "ffplayout"
|
||||||
version = "0.21.0-beta2"
|
version = "0.21.0-beta3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
@ -1239,7 +1239,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout-api"
|
name = "ffplayout-api"
|
||||||
version = "0.21.0-beta2"
|
version = "0.21.0-beta3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
@ -1278,7 +1278,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout-lib"
|
name = "ffplayout-lib"
|
||||||
version = "0.21.0-beta2"
|
version = "0.21.0-beta3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
@ -3458,7 +3458,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tests"
|
name = "tests"
|
||||||
version = "0.21.0-beta2"
|
version = "0.21.0-beta3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.21.0-beta2"
|
version = "0.21.0-beta3"
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
repository = "https://github.com/ffplayout/ffplayout"
|
repository = "https://github.com/ffplayout/ffplayout"
|
||||||
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]
|
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]
|
||||||
|
@ -62,10 +62,10 @@ processing:
|
|||||||
is needed, format is 'width:height', for example '100:-1' for proportional
|
is needed, format is 'width:height', for example '100:-1' for proportional
|
||||||
scaling. With 'logo_opacity' logo can become transparent. With 'audio_tracks' it
|
scaling. With 'logo_opacity' logo can become transparent. With 'audio_tracks' it
|
||||||
is possible to configure how many audio tracks should be processed. 'audio_channels'
|
is possible to configure how many audio tracks should be processed. 'audio_channels'
|
||||||
can be use, if audio has more channels then only stereo. With 'logo_filter'
|
can be use, if audio has more channels then only stereo. With 'logo_position' in format
|
||||||
'overlay=W-w-12:12' you can modify the logo position. With 'custom_filter'
|
'x:y' you set the logo position. With 'custom_filter' it is possible, to apply further
|
||||||
it is possible, to apply further filters. The filter outputs should end with
|
filters. The filter outputs should end with [c_v_out] for video filter, and
|
||||||
[c_v_out] for video filter, and [c_a_out] for audio filter.
|
[c_a_out] for audio filter.
|
||||||
mode: playlist
|
mode: playlist
|
||||||
audio_only: false
|
audio_only: false
|
||||||
copy_audio: false
|
copy_audio: false
|
||||||
@ -78,7 +78,7 @@ processing:
|
|||||||
logo: /usr/share/ffplayout/logo.png
|
logo: /usr/share/ffplayout/logo.png
|
||||||
logo_scale:
|
logo_scale:
|
||||||
logo_opacity: 0.7
|
logo_opacity: 0.7
|
||||||
logo_filter: overlay=W-w-12:12
|
logo_position: W-w-12:12
|
||||||
audio_tracks: 1
|
audio_tracks: 1
|
||||||
audio_track_index: -1
|
audio_track_index: -1
|
||||||
audio_channels: 2
|
audio_channels: 2
|
||||||
|
@ -48,3 +48,5 @@ Control the engine, playlist and config with a ~REST API
|
|||||||
### **[Stream Copy](/docs/stream_copy.md)**
|
### **[Stream Copy](/docs/stream_copy.md)**
|
||||||
|
|
||||||
Copy audio and or video stream
|
Copy audio and or video stream
|
||||||
|
|
||||||
|
### **[Advanced Settings](/docs/advanced_settings.md)**
|
||||||
|
57
docs/advanced_settings.md
Normal file
57
docs/advanced_settings.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
## Advanced settings
|
||||||
|
|
||||||
|
Within **/etc/ffplayout/advanced.yml** you can control all ffmpeg inputs/decoder output and filters.
|
||||||
|
|
||||||
|
> **_Note:_** Changing these settings is for advanced users only! There will be no support or guarantee that it will work and be stable after changing them!
|
||||||
|
|
||||||
|
For changing this settings you need to have knowledge about hardware encoding with ffmpeg. Good starting points are:
|
||||||
|
|
||||||
|
- [HWAccelIntro](https://trac.ffmpeg.org/wiki/HWAccelIntro)
|
||||||
|
- [VAAPI](https://trac.ffmpeg.org/wiki/Hardware/VAAPI)
|
||||||
|
- [QuickSync](https://trac.ffmpeg.org/wiki/Hardware/QuickSync)
|
||||||
|
|
||||||
|
### Example config
|
||||||
|
|
||||||
|
Here an example with Intel QuickSync:
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
help: Changing these settings is for advanced users only! There will be no support or guarantee that ffplayout will be stable after changing them.
|
||||||
|
decoder:
|
||||||
|
input_param: -hwaccel qsv -init_hw_device qsv=hw -filter_hw_device hw -hwaccel_output_format qsv
|
||||||
|
# output_param get also applied to ingest instance.
|
||||||
|
output_param: -c:v mpeg2_qsv -g 1 -b:v 50000k -minrate 50000k -maxrate 50000k -bufsize 25000k -c:a s302m -strict -2 -sample_fmt s16 -ar 48000 -ac 2
|
||||||
|
filters:
|
||||||
|
deinterlace: deinterlace_qsv
|
||||||
|
pad_scale_w: scale_qsv={}:-1,
|
||||||
|
pad_scale_h: scale_qsv=-1:{},
|
||||||
|
pad_video: 'null' # 'pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2'
|
||||||
|
fps: vpp_qsv=framerate=25
|
||||||
|
scale: scale_qsv={}:{}
|
||||||
|
set_dar: 'null' # setdar=dar={}
|
||||||
|
fade_in: 'null' # fade=in:st=0:d=0.5
|
||||||
|
fade_out: 'null' # fade=out:st={}:d=1.0
|
||||||
|
overlay_logo_scale: 'scale_qsv={}'
|
||||||
|
overlay_logo: null[v];movie={}:loop=0,setpts=N/(FRAME_RATE*TB),format=rgba,colorchannelmixer=aa={}{},hwupload=extra_hw_frames=64,format=qsv[l];[v][l]overlay_qsv={}:shortest=1
|
||||||
|
overlay_logo_fade_in: 'null' # ',fade=in:st=0:d=1.0:alpha=1'
|
||||||
|
overlay_logo_fade_out: 'null' # ',fade=out:st={}:d=1.0:alpha=1'
|
||||||
|
tpad: 'null' # tpad=stop_mode=add:stop_duration={}
|
||||||
|
drawtext_from_file: hwdownload,format=nv12,drawtext=text='{}':{}{} # drawtext=text='{}':{}{}
|
||||||
|
drawtext_from_zmq: hwdownload,format=nv12,zmq=b=tcp\\://'{}',drawtext@dyntext={} # zmq=b=tcp\\\\://'{}',drawtext@dyntext={}
|
||||||
|
aevalsrc: # aevalsrc=0:channel_layout=stereo:duration={}:sample_rate=48000
|
||||||
|
afade_in: # afade=in:st=0:d=0.5
|
||||||
|
afade_out: # afade=out:st={}:d=1.0
|
||||||
|
apad: # apad=whole_dur={}
|
||||||
|
volume: # volume={}
|
||||||
|
split: # split={}{}
|
||||||
|
encoder:
|
||||||
|
# use `-hwaccel vulkan` when output mode is desktop
|
||||||
|
input_param: -hwaccel qsv -init_hw_device qsv=hw -filter_hw_device hw -hwaccel_output_format qsv
|
||||||
|
ingest:
|
||||||
|
input_param: -hwaccel qsv -init_hw_device qsv=hw -filter_hw_device hw -hwaccel_output_format qsv
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**At the moment this function is _experimental_, if you think you found a bug: check full decoder/encoder/ingest command with ffmpeg in terminal. When there the command works you can open a bug report issue.**
|
||||||
|
|
||||||
|
Please don't open issues for general command line helps!
|
@ -1,4 +1,4 @@
|
|||||||
### Possible endpoints
|
## Possible endpoints
|
||||||
|
|
||||||
Run the API thru the systemd service, or like:
|
Run the API thru the systemd service, or like:
|
||||||
|
|
||||||
|
@ -217,18 +217,16 @@ impl CurrentProgram {
|
|||||||
// On init or reload we need to seek for the current clip.
|
// On init or reload we need to seek for the current clip.
|
||||||
fn get_current_clip(&mut self) {
|
fn get_current_clip(&mut self) {
|
||||||
let mut time_sec = self.get_current_time();
|
let mut time_sec = self.get_current_time();
|
||||||
let shift = self.playout_stat.time_shift.lock().unwrap();
|
let shift = *self.playout_stat.time_shift.lock().unwrap();
|
||||||
|
|
||||||
if *self.playout_stat.current_date.lock().unwrap()
|
if *self.playout_stat.current_date.lock().unwrap()
|
||||||
== *self.playout_stat.date.lock().unwrap()
|
== *self.playout_stat.date.lock().unwrap()
|
||||||
&& *shift != 0.0
|
&& shift != 0.0
|
||||||
{
|
{
|
||||||
info!("Shift playlist start for <yellow>{}</> seconds", *shift);
|
info!("Shift playlist start for <yellow>{shift:.3}</> seconds");
|
||||||
time_sec += *shift;
|
time_sec += shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(shift);
|
|
||||||
|
|
||||||
if self.config.playlist.infinit
|
if self.config.playlist.infinit
|
||||||
&& self.json_playlist.length.unwrap() < 86400.0
|
&& self.json_playlist.length.unwrap() < 86400.0
|
||||||
&& time_sec > self.json_playlist.length.unwrap() + self.start_sec
|
&& time_sec > self.json_playlist.length.unwrap() + self.start_sec
|
||||||
|
@ -5,7 +5,7 @@ use simplelog::*;
|
|||||||
use crate::utils::prepare_output_cmd;
|
use crate::utils::prepare_output_cmd;
|
||||||
use ffplayout_lib::{
|
use ffplayout_lib::{
|
||||||
utils::{Media, PlayoutConfig, ProcessUnit::*},
|
utils::{Media, PlayoutConfig, ProcessUnit::*},
|
||||||
vec_strings,
|
vec_strings, ADVANCED_CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Streaming Output
|
/// Streaming Output
|
||||||
@ -16,15 +16,13 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
|||||||
media.unit = Encoder;
|
media.unit = Encoder;
|
||||||
media.add_filter(config, &None);
|
media.add_filter(config, &None);
|
||||||
|
|
||||||
let enc_prefix = vec_strings![
|
let mut enc_prefix = vec_strings!["-hide_banner", "-nostats", "-v", log_format];
|
||||||
"-hide_banner",
|
|
||||||
"-nostats",
|
if let Some(input_cmd) = &ADVANCED_CONFIG.encoder.input_cmd {
|
||||||
"-v",
|
enc_prefix.append(&mut input_cmd.clone());
|
||||||
log_format,
|
}
|
||||||
"-re",
|
|
||||||
"-i",
|
enc_prefix.append(&mut vec_strings!["-re", "-i", "pipe:0"]);
|
||||||
"pipe:0"
|
|
||||||
];
|
|
||||||
|
|
||||||
let enc_cmd = prepare_output_cmd(config, enc_prefix, &media.filter);
|
let enc_cmd = prepare_output_cmd(config, enc_prefix, &media.filter);
|
||||||
|
|
||||||
|
@ -216,9 +216,8 @@ fn pad(aspect: f64, chain: &mut Filters, v_stream: &ffprobe::Stream, config: &Pl
|
|||||||
|
|
||||||
let pad = match &ADVANCED_CONFIG.decoder.filters.pad_video {
|
let pad = match &ADVANCED_CONFIG.decoder.filters.pad_video {
|
||||||
Some(pad_video) => custom_format(
|
Some(pad_video) => custom_format(
|
||||||
pad_video,
|
&format!("{scale}{pad_video}"),
|
||||||
&[
|
&[
|
||||||
&scale,
|
|
||||||
&config.processing.width.to_string(),
|
&config.processing.width.to_string(),
|
||||||
&config.processing.height.to_string(),
|
&config.processing.height.to_string(),
|
||||||
],
|
],
|
||||||
@ -313,18 +312,28 @@ fn fade(node: &mut Media, chain: &mut Filters, nr: i32, filter_type: FilterType)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if node.seek > 0.0 || node.unit == Ingest {
|
if node.seek > 0.0 || node.unit == Ingest {
|
||||||
let fade_in = match &ADVANCED_CONFIG.decoder.filters.fade_in {
|
let mut fade_in = format!("{t}fade=in:st=0:d=0.5");
|
||||||
Some(fade) => custom_format(fade, &[t]),
|
|
||||||
None => format!("{t}fade=in:st=0:d=0.5"),
|
if t == "a" {
|
||||||
|
if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.afade_in {
|
||||||
|
fade_in = custom_format(fade, &[t]);
|
||||||
|
}
|
||||||
|
} else if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.fade_in {
|
||||||
|
fade_in = custom_format(fade, &[t]);
|
||||||
};
|
};
|
||||||
|
|
||||||
chain.add_filter(&fade_in, nr, filter_type);
|
chain.add_filter(&fade_in, nr, filter_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.out != node.duration && node.out - node.seek > 1.0) || fade_audio {
|
if (node.out != node.duration && node.out - node.seek > 1.0) || fade_audio {
|
||||||
let fade_out = match &ADVANCED_CONFIG.decoder.filters.fade_out {
|
let mut fade_out = format!("{t}fade=out:st={}:d=1.0", (node.out - node.seek - 1.0));
|
||||||
Some(fade) => custom_format(fade, &[t, &(node.out - node.seek - 1.0).to_string()]),
|
|
||||||
None => format!("{t}fade=out:st={}:d=1.0", (node.out - node.seek - 1.0)),
|
if t == "a" {
|
||||||
|
if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.afade_out {
|
||||||
|
fade_out = custom_format(fade, &[t]);
|
||||||
|
}
|
||||||
|
} else if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.fade_out {
|
||||||
|
fade_out = custom_format(fade, &[t]);
|
||||||
};
|
};
|
||||||
|
|
||||||
chain.add_filter(&fade_out, nr, filter_type);
|
chain.add_filter(&fade_out, nr, filter_type);
|
||||||
@ -340,7 +349,9 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
|||||||
|
|
||||||
if !config.processing.logo_scale.is_empty() {
|
if !config.processing.logo_scale.is_empty() {
|
||||||
scale = match &ADVANCED_CONFIG.decoder.filters.overlay_logo_scale {
|
scale = match &ADVANCED_CONFIG.decoder.filters.overlay_logo_scale {
|
||||||
Some(logo_scale) => custom_format(logo_scale, &[&config.processing.logo_scale]),
|
Some(logo_scale) => {
|
||||||
|
custom_format(&format!(",{logo_scale}"), &[&config.processing.logo_scale])
|
||||||
|
}
|
||||||
None => format!(",scale={}", config.processing.logo_scale),
|
None => format!(",scale={}", config.processing.logo_scale),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,11 +361,11 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
|||||||
&config.processing.logo.replace('\\', "/").replace(':', "\\\\:"),
|
&config.processing.logo.replace('\\', "/").replace(':', "\\\\:"),
|
||||||
&config.processing.logo_opacity.to_string(),
|
&config.processing.logo_opacity.to_string(),
|
||||||
&scale.to_string(),
|
&scale.to_string(),
|
||||||
&config.processing.logo_filter,
|
&config.processing.logo_position,
|
||||||
]),
|
]),
|
||||||
None => format!(
|
None => format!(
|
||||||
"null[v];movie={}:loop=0,setpts=N/(FRAME_RATE*TB),format=rgba,colorchannelmixer=aa={}{scale}[l];[v][l]{}:shortest=1",
|
"null[v];movie={}:loop=0,setpts=N/(FRAME_RATE*TB),format=rgba,colorchannelmixer=aa={}{scale}[l];[v][l]overlay={}:shortest=1",
|
||||||
config.processing.logo.replace('\\', "/").replace(':', "\\\\:"), config.processing.logo_opacity, config.processing.logo_filter
|
config.processing.logo.replace('\\', "/").replace(':', "\\\\:"), config.processing.logo_opacity, config.processing.logo_position
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ pub struct Filters {
|
|||||||
pub drawtext_from_file: Option<String>,
|
pub drawtext_from_file: Option<String>,
|
||||||
pub drawtext_from_zmq: Option<String>,
|
pub drawtext_from_zmq: Option<String>,
|
||||||
pub aevalsrc: Option<String>,
|
pub aevalsrc: Option<String>,
|
||||||
|
pub afade_in: Option<String>,
|
||||||
|
pub afade_out: Option<String>,
|
||||||
pub apad: Option<String>,
|
pub apad: Option<String>,
|
||||||
pub volume: Option<String>,
|
pub volume: Option<String>,
|
||||||
pub split: Option<String>,
|
pub split: Option<String>,
|
||||||
|
@ -253,7 +253,7 @@ pub struct Processing {
|
|||||||
pub logo: String,
|
pub logo: String,
|
||||||
pub logo_scale: String,
|
pub logo_scale: String,
|
||||||
pub logo_opacity: f32,
|
pub logo_opacity: f32,
|
||||||
pub logo_filter: String,
|
pub logo_position: String,
|
||||||
#[serde(default = "default_tracks")]
|
#[serde(default = "default_tracks")]
|
||||||
pub audio_tracks: i32,
|
pub audio_tracks: i32,
|
||||||
#[serde(default = "default_channels")]
|
#[serde(default = "default_channels")]
|
||||||
@ -435,10 +435,6 @@ impl PlayoutConfig {
|
|||||||
} else if config.processing.copy_video {
|
} else if config.processing.copy_video {
|
||||||
process_cmd.append(&mut vec_strings!["-c:v", "copy"]);
|
process_cmd.append(&mut vec_strings!["-c:v", "copy"]);
|
||||||
} else if let Some(decoder_cmd) = &ADVANCED_CONFIG.decoder.output_cmd {
|
} else if let Some(decoder_cmd) = &ADVANCED_CONFIG.decoder.output_cmd {
|
||||||
if !decoder_cmd.contains(&"-r".to_string()) {
|
|
||||||
process_cmd.append(&mut vec_strings!["-r", &config.processing.fps]);
|
|
||||||
}
|
|
||||||
|
|
||||||
process_cmd.append(&mut decoder_cmd.clone());
|
process_cmd.append(&mut decoder_cmd.clone());
|
||||||
} else {
|
} else {
|
||||||
let bitrate = format!(
|
let bitrate = format!(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user