against INFO THREADS, ps and top.\r
o Created automatically generated build number, so that it is easier to see\r
whether a build is newer or older than an other.\r
+ o Changed configuration element mipmapping_default_on to mipmapping-default-on\r
+ for consistency with the rest of the configuration (Jesper Stærkær).\r
+ o Handle stdin EOF as EXIT.\r
+ o Added support for RESTART in Linux startup script run.sh.\r
+ o Copy casparcg_auto_restart.bat into Windows releases.\r
+ o Fixed bug with thumbnail generation when there are .-files in the media\r
+ folder.\r
+\r
+Consumers\r
+---------\r
+\r
+ o FFmpeg consumer:\r
+ + Fixed long overdue bug where HD material was always recorded using the\r
+ BT.601 color matrix instead of the BT.709 color matrix. RGB codecs like\r
+ qtrle was never affected but all the YCbCr based codecs were.\r
+ + Fixed bug in parsing of paths containing -.\r
+ o DeckLink consumer:\r
+ + Fixed possible dead-lock in frame queue.\r
\r
Producers\r
---------\r
o FFmpeg producer:\r
+ Increased the max number of frames that audio/video can be badly\r
interleaved with (Dimitry Ishenko).\r
+ + Fixed bug where decoders sometimes requires more than one video packet to\r
+ decode the first frame.\r
+ o Framerate producer:\r
+ + Fixed bug when INFO was used on a not yet playing framerate producer.\r
+ o HTML producer:\r
+ + Fixed bug where only URL:s with . in them where recognized.\r
+ o Image producer:\r
+ + Added LENGTH parameter to allow for queueing with LOADBG AUTO.\r
+\r
+Mixer\r
+-----\r
+\r
+ o Fixed bug in the contrast/saturation/brightness code where the wrong luma\r
+ coefficients was used.\r
+ o Rewrote the chroma key code to support variable hue, instead of fixed green\r
+ or blue. Threshold setting was removed in favour of separate hue width,\r
+ minimum saturation and minimum brightness constraints.\r
\r
AMCP\r
----\r
\r
o INFO PATHS now adds all the path elements even if they are using the default\r
values.\r
+ o MIXER CHROMA syntax deprecated (still supported) in favour of the more\r
+ advanced syntax required by the rewritten chroma key code.\r
+ o Added special command REQ that can be prepended before any command to\r
+ identify the response with a client specified request id, allowing a client\r
+ to know exactly what asynchronous response matched a specific request.\r
+ o Added support for listing contents of a specific directory for CLS, TLS,\r
+ DATA LIST and THUMBNAIL LIST.\r
+ o Fixed bug where CINF only returned the first match.\r
+\r
+\r
\r
CasparCG 2.1.0 Beta 1 (w.r.t 2.0.7 Stable)\r
==========================================\r
const float AvgLumG = 0.5;
const float AvgLumB = 0.5;
- const vec3 LumCoeff = vec3(0.2125, 0.7154, 0.0721);
+ vec3 LumCoeff = is_hd
+ ? vec3(0.0722, 0.7152, 0.2126)
+ : vec3(0.114, 0.587, 0.299);
vec3 AvgLumin = vec3(AvgLumR, AvgLumG, AvgLumB);
vec3 brtColor = color * brt;
// by F. van den Bergh & V. Lalioti
// but as a pixel shader algorithm.
//
-
- float chroma_blend_w = chroma_blend.y - chroma_blend.x;
- const vec4 grey_xfer = vec4(0.3, 0.59, 0.11, 0.0);
-
- float fma(float a, float b, float c) { return a*b + c; }
+ vec4 grey_xfer = is_hd
+ ? vec4(0.2126, 0.7152, 0.0722, 0)
+ : vec4(0.299, 0.587, 0.114, 0);
// This allows us to implement the paper's alphaMap curve in software
// rather than a largeish array
float alpha_map(float d)
{
- return 1.0-smoothstep(chroma_blend.x, chroma_blend.y, d);
+ return 1.0 - smoothstep(1.0, chroma_softness, d);
}
vec4 supress_spill(vec4 c, float d)
{
- float ds = smoothstep(chroma_spill, 1.0, d/chroma_blend.y);
+ float ds = smoothstep(chroma_spill, clamp(chroma_spill + 0.2, 0, 1), d / chroma_softness);
float gl = dot(grey_xfer, c);
- return mix(c, vec4(vec3(gl*gl), gl), ds);
+ return mix(c, vec4(vec3(pow(gl, chroma_spill_darken)), gl), ds);
+ }
+
+ // http://stackoverflow.com/questions/15095909/from-rgb-to-hsv-in-opengl-glsl
+ vec3 rgb2hsv(vec3 c)
+ {
+ vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+ float d = q.x - min(q.w, q.y);
+ float e = 1.0e-10;
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+ }
+
+ float AngleDiff(float angle1, float angle2)
+ {
+ return 0.5 - abs(abs(angle1 - angle2) - 0.5);
+ }
+
+ float Distance(float actual, float target)
+ {
+ return min(0.0, target - actual);
}
- // Key on green
- vec4 ChromaOnGreen(vec4 c)
+ float ColorDistance(vec3 hsv)
{
- float d = fma(2.0, c.g, -c.r - c.b)/2.0;
- c *= alpha_map(d);
- return supress_spill(c, d);
+ float hueDiff = AngleDiff(hsv.x, chroma_target_hue) * 2;
+ float saturationDiff = Distance(hsv.y, chroma_min_saturation);
+ float brightnessDiff = Distance(hsv.z, chroma_min_brightness);
+
+ float saturationBrightnessScore = max(brightnessDiff, saturationDiff);
+ float hueScore = hueDiff - chroma_hue_width;
+
+ return -hueScore * saturationBrightnessScore;
}
- //Key on blue
- vec4 ChromaOnBlue(vec4 c)
+ // Key on any color
+ vec4 ChromaOnCustomColor(vec4 c)
{
- float d = fma(2.0, c.b, -c.r - c.g)/2.0;
- c *= alpha_map(d);
- return supress_spill(c, d);
+ vec3 hsv = rgb2hsv(c.rgb);
+ float distance = ColorDistance(hsv);
+ float d = distance * -2.0 + 1.0;
+ vec4 suppressed = supress_spill(c.rgba, d).rgba;
+ float alpha = alpha_map(d);
+
+ suppressed *= alpha;
+
+ return chroma_show_mask ? vec4(suppressed.a, suppressed.a, suppressed.a, 1) : suppressed;
}
)shader";
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00};
-
+
GLubyte lower_pattern[] = {
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff};
struct image_kernel::impl
-{
+{
spl::shared_ptr<device> ogl_;
spl::shared_ptr<shader> shader_;
bool blend_modes_;
void draw(draw_params params)
{
- static const double epsilon = 0.001;
-
+ static const double epsilon = 0.001;
+
CASPAR_ASSERT(params.pix_desc.planes.size() == params.textures.size());
if(params.textures.empty() || !params.background)
if(params.local_key)
params.local_key->bind(static_cast<int>(texture_id::local_key));
-
+
if(params.layer_key)
params.layer_key->bind(static_cast<int>(texture_id::layer_key));
-
+
// Setup shader
-
+
shader_->use();
shader_->set("post_processing", false);
shader_->set("plane[2]", texture_id::plane2);
shader_->set("plane[3]", texture_id::plane3);
for (int n = 0; n < params.textures.size(); ++n)
- shader_->set("plane_size[" + boost::lexical_cast<std::string>(n) + "]",
- static_cast<float>(params.textures[n]->width()),
+ shader_->set("plane_size[" + boost::lexical_cast<std::string>(n) + "]",
+ static_cast<float>(params.textures[n]->width()),
static_cast<float>(params.textures[n]->height()));
shader_->set("local_key", texture_id::local_key);
shader_->set("is_hd", params.pix_desc.planes.at(0).height > 700 ? 1 : 0);
shader_->set("has_local_key", static_cast<bool>(params.local_key));
shader_->set("has_layer_key", static_cast<bool>(params.layer_key));
- shader_->set("pixel_format", params.pix_desc.format);
- shader_->set("opacity", params.transform.is_key ? 1.0 : params.transform.opacity);
+ shader_->set("pixel_format", params.pix_desc.format);
+ shader_->set("opacity", params.transform.is_key ? 1.0 : params.transform.opacity);
- if (params.transform.chroma.key != core::chroma::type::none)
+ if (params.transform.chroma.enable)
{
- shader_->set("chroma", true);
- shader_->set("chroma_mode", static_cast<int>(params.transform.chroma.key));
- shader_->set("chroma_blend", params.transform.chroma.threshold, params.transform.chroma.softness);
- shader_->set("chroma_spill", params.transform.chroma.spill);
+ shader_->set("chroma", true);
+
+ shader_->set("chroma_show_mask", params.transform.chroma.show_mask);
+ shader_->set("chroma_target_hue", params.transform.chroma.target_hue / 360.0);
+ shader_->set("chroma_hue_width", params.transform.chroma.hue_width);
+ shader_->set("chroma_min_saturation", params.transform.chroma.min_saturation);
+ shader_->set("chroma_min_brightness", params.transform.chroma.min_brightness);
+ shader_->set("chroma_softness", 1.0 + params.transform.chroma.softness);
+ shader_->set("chroma_spill", params.transform.chroma.spill);
+ shader_->set("chroma_spill_darken", params.transform.chroma.spill_darken);
}
else
shader_->set("chroma", false);
// Setup blend_func
-
+
if(params.transform.is_key)
params.blend_mode = core::blend_mode::normal;
switch(params.keyer)
{
case keyer::additive:
- GL(glBlendFunc(GL_ONE, GL_ONE));
+ GL(glBlendFunc(GL_ONE, GL_ONE));
break;
case keyer::linear:
- default:
+ default:
GL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
- }
+ }
}
// Setup image-adjustements
-
+
if (params.transform.levels.min_input > epsilon ||
params.transform.levels.max_input < 1.0-epsilon ||
params.transform.levels.min_output > epsilon ||
params.transform.levels.max_output < 1.0-epsilon ||
std::abs(params.transform.levels.gamma - 1.0) > epsilon)
{
- shader_->set("levels", true);
- shader_->set("min_input", params.transform.levels.min_input);
+ shader_->set("levels", true);
+ shader_->set("min_input", params.transform.levels.min_input);
shader_->set("max_input", params.transform.levels.max_input);
shader_->set("min_output", params.transform.levels.min_output);
shader_->set("max_output", params.transform.levels.max_output);
shader_->set("gamma", params.transform.levels.gamma);
}
else
- shader_->set("levels", false);
+ shader_->set("levels", false);
if (std::abs(params.transform.brightness - 1.0) > epsilon ||
std::abs(params.transform.saturation - 1.0) > epsilon ||
std::abs(params.transform.contrast - 1.0) > epsilon)
{
- shader_->set("csb", true);
-
- shader_->set("brt", params.transform.brightness);
+ shader_->set("csb", true);
+
+ shader_->set("brt", params.transform.brightness);
shader_->set("sat", params.transform.saturation);
shader_->set("con", params.transform.contrast);
}
else
- shader_->set("csb", false);
-
+ shader_->set("csb", false);
+
// Setup interlacing
-
- if (params.transform.field_mode != core::field_mode::progressive)
+
+ if (params.transform.field_mode != core::field_mode::progressive)
{
GL(glEnable(GL_POLYGON_STIPPLE));
}
// Setup drawing area
-
+
GL(glViewport(0, 0, params.background->width(), params.background->height()));
glDisable(GL_DEPTH_TEST);
-
+
auto m_p = params.transform.clip_translation;
auto m_s = params.transform.clip_scale;
{
double w = static_cast<double>(params.background->width());
double h = static_cast<double>(params.background->height());
-
+
GL(glEnable(GL_SCISSOR_TEST));
glScissor(static_cast<int>(m_p[0] * w), static_cast<int>(m_p[1] * h), std::max(0, static_cast<int>(m_s[0] * w)), std::max(0, static_cast<int>(m_s[1] * h)));
}
// Synchronize and set render target
-
+
if (blend_modes_)
{
// http://www.opengl.org/registry/specs/NV/texture_barrier.txt
// This allows us to use framebuffer (background) both as source and target while blending.
- glTextureBarrierNV();
+ glTextureBarrierNV();
}
params.background->attach();
default:
break;
}
-
+
// Cleanup
GL(glDisable(GL_SCISSOR_TEST));
GL(glDisable(GL_POLYGON_STIPPLE));
}
)shader";
}
-
+
std::string get_simple_blend_color_func()
{
return
return
get_chroma_glsl()
-
+
+
-
+
R"shader(
vec4 chroma_key(vec4 c)
{
- switch (chroma_mode)
- {
- case 0: return c;
- case 1: return ChromaOnGreen(c.bgra).bgra;
- case 2: return ChromaOnBlue(c.bgra).bgra;
- }
-
- return c;
+ return ChromaOnCustomColor(c.bgra).bgra;
}
)shader";
}
uniform bool straighten_alpha;
uniform bool chroma;
- uniform int chroma_mode;
- uniform vec2 chroma_blend;
+ uniform bool chroma_show_mask;
+ uniform float chroma_target_hue;
+ uniform float chroma_hue_width;
+ uniform float chroma_min_saturation;
+ uniform float chroma_min_brightness;
+ uniform float chroma_softness;
uniform float chroma_spill;
+ uniform float chroma_spill_darken;
)shader"
+
color.rgb /= color.a + 0.0000001;
return color;
- }
+ }
void main()
{
delete p;
});
};
-
+
try
- {
+ {
g_blend_modes = glTextureBarrierNV ? blend_modes_wanted : false;
g_post_processing = straight_alpha_wanted;
existing_shader.reset(new shader(get_vertex(), get_fragment(g_blend_modes, g_post_processing)), deleter);
{
CASPAR_LOG_CURRENT_EXCEPTION();
CASPAR_LOG(warning) << "Failed to compile shader. Trying to compile without blend-modes.";
-
+
g_blend_modes = false;
existing_shader.reset(new shader(get_vertex(), get_fragment(g_blend_modes, g_post_processing)), deleter);
}
copy shell\*.dll "%SERVER_FOLDER%\Server" || goto :error
copy shell\RelWithDebInfo\casparcg.exe "%SERVER_FOLDER%\Server" || goto :error
copy shell\RelWithDebInfo\casparcg.pdb "%SERVER_FOLDER%\Server" || goto :error
+copy ..\shell\casparcg_auto_restart.bat "%SERVER_FOLDER%\Server" || goto :error
copy shell\casparcg.config "%SERVER_FOLDER%\Server" || goto :error
copy shell\*.ttf "%SERVER_FOLDER%\Server" || goto :error
copy shell\*.pak "%SERVER_FOLDER%\Server" || goto :error
#include "stdafx.h"
#include "filesystem.h"
+#include "except.h"
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
const boost::filesystem::path& file,
const boost::filesystem::path& relative_to)
{
- auto result = file.filename();
+ auto result = file.filename();
+ auto current_path = file;
- boost::filesystem::path current_path = file;
+ if (boost::filesystem::equivalent(current_path, relative_to))
+ return L"";
while (true)
{
break;
if (current_path.empty())
- throw std::runtime_error("File not relative to folder");
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("File " + file.string() + " not relative to folder " + relative_to.string()));
result = current_path.filename() / result;
}
image_transform& image_transform::operator*=(const image_transform &other)
{
- opacity *= other.opacity;
+ opacity *= other.opacity;
brightness *= other.brightness;
contrast *= other.contrast;
saturation *= other.saturation;
transform_corners(perspective, other.perspective);
levels.min_input = std::max(levels.min_input, other.levels.min_input);
- levels.max_input = std::min(levels.max_input, other.levels.max_input);
+ levels.max_input = std::min(levels.max_input, other.levels.max_input);
levels.min_output = std::max(levels.min_output, other.levels.min_output);
levels.max_output = std::min(levels.max_output, other.levels.max_output);
levels.gamma *= other.levels.gamma;
- chroma.key = std::max(chroma.key, other.chroma.key);
- chroma.threshold += other.chroma.threshold;
- chroma.softness += other.chroma.softness;
- chroma.spill += other.chroma.spill;
+ chroma.enable |= other.chroma.enable;
+ chroma.show_mask |= other.chroma.show_mask;
+ chroma.target_hue = std::max(other.chroma.target_hue, chroma.target_hue);
+ chroma.min_saturation = std::max(other.chroma.min_saturation, chroma.min_saturation);
+ chroma.min_brightness = std::max(other.chroma.min_brightness, chroma.min_brightness);
+ chroma.hue_width = std::max(other.chroma.hue_width, chroma.hue_width);
+ chroma.softness = std::max(other.chroma.softness, chroma.softness);
+ chroma.spill = std::min(other.chroma.spill, chroma.spill);
+ chroma.spill_darken = std::max(other.chroma.spill_darken, chroma.spill_darken);
field_mode = field_mode & other.field_mode;
is_key |= other.is_key;
is_mix |= other.is_mix;
image_transform image_transform::tween(double time, const image_transform& source, const image_transform& dest, double duration, const tweener& tween)
{
- image_transform result;
-
- result.brightness = do_tween(time, source.brightness, dest.brightness, duration, tween);
- result.contrast = do_tween(time, source.contrast, dest.contrast, duration, tween);
- result.saturation = do_tween(time, source.saturation, dest.saturation, duration, tween);
- result.opacity = do_tween(time, source.opacity, dest.opacity, duration, tween);
- result.anchor[0] = do_tween(time, source.anchor[0], dest.anchor[0], duration, tween);
- result.anchor[1] = do_tween(time, source.anchor[1], dest.anchor[1], duration, tween);
- result.fill_translation[0] = do_tween(time, source.fill_translation[0], dest.fill_translation[0], duration, tween);
- result.fill_translation[1] = do_tween(time, source.fill_translation[1], dest.fill_translation[1], duration, tween);
- result.fill_scale[0] = do_tween(time, source.fill_scale[0], dest.fill_scale[0], duration, tween);
- result.fill_scale[1] = do_tween(time, source.fill_scale[1], dest.fill_scale[1], duration, tween);
- result.clip_translation[0] = do_tween(time, source.clip_translation[0], dest.clip_translation[0], duration, tween);
- result.clip_translation[1] = do_tween(time, source.clip_translation[1], dest.clip_translation[1], duration, tween);
- result.clip_scale[0] = do_tween(time, source.clip_scale[0], dest.clip_scale[0], duration, tween);
- result.clip_scale[1] = do_tween(time, source.clip_scale[1], dest.clip_scale[1], duration, tween);
- result.angle = do_tween(time, source.angle, dest.angle, duration, tween);
- result.levels.max_input = do_tween(time, source.levels.max_input, dest.levels.max_input, duration, tween);
- result.levels.min_input = do_tween(time, source.levels.min_input, dest.levels.min_input, duration, tween);
- result.levels.max_output = do_tween(time, source.levels.max_output, dest.levels.max_output, duration, tween);
- result.levels.min_output = do_tween(time, source.levels.min_output, dest.levels.min_output, duration, tween);
- result.levels.gamma = do_tween(time, source.levels.gamma, dest.levels.gamma, duration, tween);
- result.chroma.threshold = do_tween(time, source.chroma.threshold, dest.chroma.threshold, duration, tween);
- result.chroma.softness = do_tween(time, source.chroma.softness, dest.chroma.softness, duration, tween);
- result.chroma.spill = do_tween(time, source.chroma.spill, dest.chroma.spill, duration, tween);
- result.chroma.key = dest.chroma.key;
- result.field_mode = source.field_mode & dest.field_mode;
- result.is_key = source.is_key | dest.is_key;
- result.is_mix = source.is_mix | dest.is_mix;
- result.is_still = source.is_still | dest.is_still;
- result.use_mipmap = source.use_mipmap | dest.use_mipmap;
- result.blend_mode = std::max(source.blend_mode, dest.blend_mode);
- result.layer_depth = dest.layer_depth;
+ image_transform result;
+
+ result.brightness = do_tween(time, source.brightness, dest.brightness, duration, tween);
+ result.contrast = do_tween(time, source.contrast, dest.contrast, duration, tween);
+ result.saturation = do_tween(time, source.saturation, dest.saturation, duration, tween);
+ result.opacity = do_tween(time, source.opacity, dest.opacity, duration, tween);
+ result.anchor[0] = do_tween(time, source.anchor[0], dest.anchor[0], duration, tween);
+ result.anchor[1] = do_tween(time, source.anchor[1], dest.anchor[1], duration, tween);
+ result.fill_translation[0] = do_tween(time, source.fill_translation[0], dest.fill_translation[0], duration, tween);
+ result.fill_translation[1] = do_tween(time, source.fill_translation[1], dest.fill_translation[1], duration, tween);
+ result.fill_scale[0] = do_tween(time, source.fill_scale[0], dest.fill_scale[0], duration, tween);
+ result.fill_scale[1] = do_tween(time, source.fill_scale[1], dest.fill_scale[1], duration, tween);
+ result.clip_translation[0] = do_tween(time, source.clip_translation[0], dest.clip_translation[0], duration, tween);
+ result.clip_translation[1] = do_tween(time, source.clip_translation[1], dest.clip_translation[1], duration, tween);
+ result.clip_scale[0] = do_tween(time, source.clip_scale[0], dest.clip_scale[0], duration, tween);
+ result.clip_scale[1] = do_tween(time, source.clip_scale[1], dest.clip_scale[1], duration, tween);
+ result.angle = do_tween(time, source.angle, dest.angle, duration, tween);
+ result.levels.max_input = do_tween(time, source.levels.max_input, dest.levels.max_input, duration, tween);
+ result.levels.min_input = do_tween(time, source.levels.min_input, dest.levels.min_input, duration, tween);
+ result.levels.max_output = do_tween(time, source.levels.max_output, dest.levels.max_output, duration, tween);
+ result.levels.min_output = do_tween(time, source.levels.min_output, dest.levels.min_output, duration, tween);
+ result.levels.gamma = do_tween(time, source.levels.gamma, dest.levels.gamma, duration, tween);
+ result.chroma.target_hue = do_tween(time, source.chroma.target_hue, dest.chroma.target_hue, duration, tween);
+ result.chroma.hue_width = do_tween(time, source.chroma.hue_width, dest.chroma.hue_width, duration, tween);
+ result.chroma.min_saturation = do_tween(time, source.chroma.min_saturation, dest.chroma.min_saturation, duration, tween);
+ result.chroma.min_brightness = do_tween(time, source.chroma.min_brightness, dest.chroma.min_brightness, duration, tween);
+ result.chroma.softness = do_tween(time, source.chroma.softness, dest.chroma.softness, duration, tween);
+ result.chroma.spill = do_tween(time, source.chroma.spill, dest.chroma.spill, duration, tween);
+ result.chroma.spill_darken = do_tween(time, source.chroma.spill_darken, dest.chroma.spill_darken, duration, tween);
+ result.chroma.enable = dest.chroma.enable;
+ result.chroma.show_mask = dest.chroma.show_mask;
+ result.field_mode = source.field_mode & dest.field_mode;
+ result.is_key = source.is_key | dest.is_key;
+ result.is_mix = source.is_mix | dest.is_mix;
+ result.is_still = source.is_still | dest.is_still;
+ result.use_mipmap = source.use_mipmap | dest.use_mipmap;
+ result.blend_mode = std::max(source.blend_mode, dest.blend_mode);
+ result.layer_depth = dest.layer_depth;
do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tween);
do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tween);
bool operator==(const image_transform& lhs, const image_transform& rhs)
{
- return
+ return
eq(lhs.opacity, rhs.opacity) &&
eq(lhs.contrast, rhs.contrast) &&
eq(lhs.brightness, rhs.brightness) &&
lhs.use_mipmap == rhs.use_mipmap &&
lhs.blend_mode == rhs.blend_mode &&
lhs.layer_depth == rhs.layer_depth &&
+ lhs.chroma.enable == rhs.chroma.enable &&
+ lhs.chroma.show_mask == rhs.chroma.show_mask &&
+ eq(lhs.chroma.target_hue, rhs.chroma.target_hue) &&
+ eq(lhs.chroma.hue_width, rhs.chroma.hue_width) &&
+ eq(lhs.chroma.min_saturation, rhs.chroma.min_saturation) &&
+ eq(lhs.chroma.min_brightness, rhs.chroma.min_brightness) &&
+ eq(lhs.chroma.softness, rhs.chroma.softness) &&
+ eq(lhs.chroma.spill, rhs.chroma.spill) &&
+ eq(lhs.chroma.spill_darken, rhs.chroma.spill_darken) &&
lhs.crop == rhs.crop &&
lhs.perspective == rhs.perspective;
}
audio_transform& audio_transform::operator*=(const audio_transform &other)
{
- volume *= other.volume;
+ volume *= other.volume;
is_still |= other.is_still;
return *this;
}
}
audio_transform audio_transform::tween(double time, const audio_transform& source, const audio_transform& dest, double duration, const tweener& tween)
-{
+{
audio_transform result;
result.is_still = source.is_still | dest.is_still;
result.volume = do_tween(time, source.volume, dest.volume, duration, tween);
-
+
return result;
}
bool operator==(const frame_transform& lhs, const frame_transform& rhs)
{
- return lhs.image_transform == rhs.image_transform &&
+ return lhs.image_transform == rhs.image_transform &&
lhs.audio_transform == rhs.audio_transform;
}
}
-core::chroma::type get_chroma_mode(const std::wstring& str)
+boost::optional<core::chroma::legacy_type> get_chroma_mode(const std::wstring& str)
{
if (boost::iequals(str, L"none"))
- return core::chroma::type::none;
+ return core::chroma::legacy_type::none;
else if (boost::iequals(str, L"green"))
- return core::chroma::type::green;
+ return core::chroma::legacy_type::green;
else if (boost::iequals(str, L"blue"))
- return core::chroma::type::blue;
+ return core::chroma::legacy_type::blue;
else
- CASPAR_THROW_EXCEPTION(user_error() << msg_info("chroma mode has to be one of none, green or blue"));
-}
-
-std::wstring get_chroma_mode(core::chroma::type type)
-{
- switch (type)
- {
- case core::chroma::type::none:
- return L"none";
- case core::chroma::type::green:
- return L"green";
- case core::chroma::type::blue:
- return L"blue";
- default:
- CASPAR_THROW_EXCEPTION(programming_error() << msg_info("Unhandled enum constant"));
- };
+ return boost::none;
}
namespace detail {
#include <core/mixer/image/blend_modes.h>
#include <boost/array.hpp>
+#include <boost/optional.hpp>
#include <boost/property_tree/ptree.hpp>
namespace caspar { namespace core {
struct chroma
{
- enum class type
+ enum class legacy_type
{
none,
green,
blue
};
- type key = type::none;
- double threshold = 0.0;
- double softness = 0.0;
- double spill = 0.0;
+ bool enable = false;
+ bool show_mask = false;
+ double target_hue = 0.0;
+ double hue_width = 0.0;
+ double min_saturation = 0.0;
+ double min_brightness = 0.0;
+ double softness = 0.0;
+ double spill = 1.0;
+ double spill_darken = 0.0;
};
struct levels final
bool use_mipmap = false;
core::blend_mode blend_mode = core::blend_mode::normal;
int layer_depth = 0;
-
+
image_transform& operator*=(const image_transform &other);
image_transform operator*(const image_transform &other) const;
{
double volume = 1.0;
bool is_still = false;
-
+
audio_transform& operator*=(const audio_transform &other);
audio_transform operator*(const audio_transform &other) const;
bool operator==(const audio_transform& lhs, const audio_transform& rhs);
bool operator!=(const audio_transform& lhs, const audio_transform& rhs);
-//__declspec(align(16))
+//__declspec(align(16))
struct frame_transform final
{
public:
frame_transform();
-
+
core::image_transform image_transform;
core::audio_transform audio_transform;
//char padding[(sizeof(core::image_transform) + sizeof(core::audio_transform)) % 16];
-
+
frame_transform& operator*=(const frame_transform &other);
frame_transform operator*(const frame_transform &other) const;
int duration_;
int time_;
tweener tweener_;
-public:
+public:
tweened_transform()
: duration_(0)
, time_(0)
{
- dest_.image_transform.use_mipmap = env::properties().get(L"configuration.mixer.mipmapping_default_on", false);
+ dest_.image_transform.use_mipmap = env::properties().get(L"configuration.mixer.mipmapping-default-on", false);
}
tweened_transform(const frame_transform& source, const frame_transform& dest, int duration, const tweener& tween)
{
return dest_;
}
-
+
frame_transform fetch()
{
return time_ == duration_ ? dest_ : frame_transform::tween(static_cast<double>(time_), source_, dest_, static_cast<double>(duration_), tweener_);
}
frame_transform fetch_and_tick(int num)
- {
+ {
time_ = std::min(time_+num, duration_);
return fetch();
}
};
-chroma::type get_chroma_mode(const std::wstring& str);
-std::wstring get_chroma_mode(chroma::type type);
+boost::optional<chroma::legacy_type> get_chroma_mode(const std::wstring& str);
namespace detail {
uint32_t nb_frames() const override
{
+ if (!is_initialized())
+ return std::numeric_limits<uint32_t>::max();
+
auto source_nb_frames = source_->nb_frames();
auto multiple = boost::rational_cast<double>(1 / get_speed() * (output_repeat_ != 0 ? 2 : 1));
uint32_t frame_number() const override
{
+ if (!is_initialized())
+ return 0;
+
auto source_frame_number = source_->frame_number() - 1; // next frame already received
auto multiple = boost::rational_cast<double>(1 / get_speed() * (output_repeat_ != 0 ? 2 : 1));
return source_->pixel_constraints();
}
private:
+ bool is_initialized() const
+ {
+ return source_framerate_ != -1;
+ }
+
draw_frame do_render_progressive_frame(bool sound)
{
user_speed_.fetch_and_tick();
auto speed_variable = std::make_shared<core::variable_impl<double>>(L"1.0", true, 1.0);
store_variable(L"scene_speed", speed_variable);
speed_ = speed_variable->value();
-
+
auto frame_variable = std::make_shared<core::variable_impl<double>>(L"-1", true, -1);
store_variable(L"frame", frame_variable);
frame_number_ = frame_variable->value();
angle = layer.rotation.get() * PI / 180.0;
- transform.image_transform.opacity = layer.adjustments.opacity.get();
- transform.image_transform.is_key = layer.is_key.get();
- transform.image_transform.use_mipmap = layer.use_mipmap.get();
- transform.image_transform.blend_mode = layer.blend_mode.get();
- transform.image_transform.chroma.key = layer.chroma_key.key.get();
- transform.image_transform.chroma.threshold = layer.chroma_key.threshold.get();
- transform.image_transform.chroma.softness = layer.chroma_key.softness.get();
- transform.image_transform.chroma.spill = layer.chroma_key.spill.get();
+ transform.image_transform.opacity = layer.adjustments.opacity.get();
+ transform.image_transform.is_key = layer.is_key.get();
+ transform.image_transform.use_mipmap = layer.use_mipmap.get();
+ transform.image_transform.blend_mode = layer.blend_mode.get();
+ transform.image_transform.chroma.enable = layer.chroma_key.enable.get();
+ transform.image_transform.chroma.target_hue = layer.chroma_key.target_hue.get();
+ transform.image_transform.chroma.hue_width = layer.chroma_key.hue_width.get();
+ transform.image_transform.chroma.min_saturation = layer.chroma_key.min_saturation.get();
+ transform.image_transform.chroma.min_brightness = layer.chroma_key.min_brightness.get();
+ transform.image_transform.chroma.softness = layer.chroma_key.softness.get();
+ transform.image_transform.chroma.spill = layer.chroma_key.spill.get();
+ transform.image_transform.chroma.spill_darken = layer.chroma_key.spill_darken.get();
// Mark as sublayer, so it will be composited separately by the mixer.
transform.image_transform.layer_depth = 1;
{
prec_timer timer;
timer.tick_millis(0);
-
+
auto field1 = render_progressive_frame();
-
+
timer.tick(0.5 / format_desc_.fps);
auto field2 = render_progressive_frame();
return boost::optional<interaction_target>();
}
- std::future<std::wstring> call(const std::vector<std::wstring>& params)
+ std::future<std::wstring> call(const std::vector<std::wstring>& params)
{
if (!params.empty() && boost::ends_with(params.at(0), L"()"))
return make_ready_future(handle_call(params));
{
return producer_name_;
}
-
+
boost::property_tree::wptree info() const
{
boost::property_tree::wptree info;
return impl_->info();
}
-std::future<std::wstring> scene_producer::call(const std::vector<std::wstring>& params)
+std::future<std::wstring> scene_producer::call(const std::vector<std::wstring>& params)
{
return impl_->call(params);
}
struct chroma_key
{
- binding<core::chroma::type> key;
- binding<double> threshold;
+ binding<bool> enable;
+ binding<double> target_hue;
+ binding<double> hue_width;
+ binding<double> min_saturation;
+ binding<double> min_brightness;
binding<double> softness;
binding<double> spill;
+ binding<double> spill_darken;
};
struct layer
static_cast<double>(duration)));
to_affect.set(tweened);
-
+
//CASPAR_LOG(info) << relative_frame << L" " << *start_value << L" " << duration << L" " << tweened;
};
layer.is_key = scene->create_variable<bool>(variable_prefix + L"is_key", false, elem.second.get(L"is_key", L"false"));
layer.use_mipmap = scene->create_variable<bool>(variable_prefix + L"use_mipmap", false, elem.second.get(L"use_mipmap", L"false"));
layer.blend_mode = scene->create_variable<std::wstring>(variable_prefix + L"blend_mode", false, elem.second.get(L"blend_mode", L"normal")).transformed([](const std::wstring& b) { return get_blend_mode(b); });
- layer.chroma_key.key = scene->create_variable<std::wstring>(variable_prefix + L"chroma_key.key", false, elem.second.get(L"chroma_key.key", L"none")).transformed([](const std::wstring& k) { return get_chroma_mode(k); });
- layer.chroma_key.threshold = scene->create_variable<double>(variable_prefix + L"chroma_key.threshold", false, elem.second.get(L"chroma_key.threshold", L"0.0"));
+ layer.chroma_key.enable = scene->create_variable<bool>(variable_prefix + L"chroma_key.enable", false, elem.second.get(L"chroma_key.enable", L"false"));
+ layer.chroma_key.target_hue = scene->create_variable<double>(variable_prefix + L"chroma_key.target_hue", false, elem.second.get(L"chroma_key.target_hue", L"120.0"));
+ layer.chroma_key.hue_width = scene->create_variable<double>(variable_prefix + L"chroma_key.hue_width", false, elem.second.get(L"chroma_key.hue_width", L"0.1"));
+ layer.chroma_key.min_saturation = scene->create_variable<double>(variable_prefix + L"chroma_key.min_saturation", false, elem.second.get(L"chroma_key.min_saturation", L"0.0"));
+ layer.chroma_key.min_brightness = scene->create_variable<double>(variable_prefix + L"chroma_key.min_brightness", false, elem.second.get(L"chroma_key.min_brightness", L"0.0"));
layer.chroma_key.softness = scene->create_variable<double>(variable_prefix + L"chroma_key.softness", false, elem.second.get(L"chroma_key.softness", L"0.0"));
- layer.chroma_key.spill = scene->create_variable<double>(variable_prefix + L"chroma_key.spill", false, elem.second.get(L"chroma_key.spill", L"0.0"));
+ layer.chroma_key.spill = scene->create_variable<double>(variable_prefix + L"chroma_key.spill", false, elem.second.get(L"chroma_key.spill", L"1.0"));
+ layer.chroma_key.spill_darken = scene->create_variable<double>(variable_prefix + L"chroma_key.spill_darken", false, elem.second.get(L"chroma_key.spill_darken", L"2.0"));
scene->create_variable<double>(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width;
scene->create_variable<double>(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height;
auto repo = [&scene](const std::wstring& name) -> variable&
{
- return scene->get_variable(name);
+ return scene->get_variable(name);
};
for (auto& var_name : scene->get_variables())
#!/bin/sh
-LD_LIBRARY_PATH=lib bin/casparcg "$@"
+RET=5
+
+while [ $RET -eq 5 ]
+do
+ LD_LIBRARY_PATH=lib bin/casparcg "$@"
+ RET=$?
+done
if(!is_running_)
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
+ boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
+
if (frame_buffer_.try_push(frame))
+ {
+ send_completion_ = std::packaged_task<bool()>();
return make_ready_future(true);
-
- boost::lock_guard<boost::mutex> lock(send_completion_mutex_);
+ }
send_completion_ = std::packaged_task<bool ()>([frame, this] () mutable -> bool
{
{
if(oc_)
{
- video_encoder_executor_.begin_invoke([&] { encode_video(core::const_frame::empty(), nullptr); });
- audio_encoder_executor_.begin_invoke([&] { encode_audio(core::const_frame::empty(), nullptr); });
+ try
+ {
+ video_encoder_executor_.begin_invoke([&] { encode_video(core::const_frame::empty(), nullptr); });
+ audio_encoder_executor_.begin_invoke([&] { encode_audio(core::const_frame::empty(), nullptr); });
- video_encoder_executor_.stop();
- audio_encoder_executor_.stop();
- video_encoder_executor_.join();
- audio_encoder_executor_.join();
+ video_encoder_executor_.stop();
+ audio_encoder_executor_.stop();
+ video_encoder_executor_.join();
+ audio_encoder_executor_.join();
- video_graph_.reset();
- audio_filter_.reset();
- video_st_.reset();
- audio_sts_.clear();
+ video_graph_.reset();
+ audio_filter_.reset();
+ video_st_.reset();
+ audio_sts_.clear();
- write_packet(nullptr, nullptr);
+ write_packet(nullptr, nullptr);
- write_executor_.stop();
- write_executor_.join();
+ write_executor_.stop();
+ write_executor_.join();
- FF(av_write_trailer(oc_.get()));
+ FF(av_write_trailer(oc_.get()));
- if (!(oc_->oformat->flags & AVFMT_NOFILE) && oc_->pb)
- avio_close(oc_->pb);
+ if (!(oc_->oformat->flags & AVFMT_NOFILE) && oc_->pb)
+ avio_close(oc_->pb);
- oc_.reset();
+ oc_.reset();
+ }
+ catch (...)
+ {
+ CASPAR_LOG_CURRENT_EXCEPTION();
+ }
}
}
adjust_video_filter(codec, in_video_format_, filt_vsink, filtergraph);
+ if (in_video_format_.width < 1280)
+ video_graph_->scale_sws_opts = "out_color_matrix=bt601";
+ else
+ video_graph_->scale_sws_opts = "out_color_matrix=bt709";
+
configure_filtergraph(
*video_graph_,
filtergraph,
bool mono_streams = get_and_consume_flag(L"MONO_STREAMS", params2);
auto compatibility_mode = boost::iequals(params.at(0), L"FILE");
auto path = u8(params2.size() > 1 ? params2.at(1) : L"");
+
+ // remove FILE or STREAM
+ params2.erase(params2.begin());
+
+ // remove path
+ if (!path.empty())
+ params2.erase(params2.begin());
+
+ // join only the args
auto args = u8(boost::join(params2, L" "));
return spl::make_shared<ffmpeg_consumer_proxy>(path, args, separate_key, mono_streams, compatibility_mode);
tbb::parallel_invoke(
[&]
{
- if (!muxer_->video_ready() && video_decoder_)
- video = video_decoder_->poll();
+ do
+ {
+ if (!muxer_->video_ready() && video_decoder_)
+ {
+ video = video_decoder_->poll();
+ if (video)
+ break;
+ }
+ else
+ break;
+ } while (!video_decoder_->empty());
},
[&]
{
file_frame_number = std::max(file_frame_number, video_decoder_ ? video_decoder_->file_frame_number() : 0);
for (auto frame = muxer_->poll(); frame != core::draw_frame::empty(); frame = muxer_->poll())
- frame_buffer_.push(std::make_pair(frame, file_frame_number));
+ if (frame != core::draw_frame::empty())
+ frame_buffer_.push(std::make_pair(frame, file_frame_number));
}
bool audio_only() const
return packets_.size() >= 8;
}
+ bool empty() const
+ {
+ return packets_.empty();
+ }
+
uint32_t nb_frames() const
{
return std::max(nb_frames_, static_cast<uint32_t>(file_frame_number_));
void video_decoder::push(const std::shared_ptr<AVPacket>& packet){impl_->push(packet);}
std::shared_ptr<AVFrame> video_decoder::poll(){return impl_->poll();}
bool video_decoder::ready() const{return impl_->ready();}
+bool video_decoder::empty() const { return impl_->empty(); }
int video_decoder::width() const{return impl_->width_;}
int video_decoder::height() const{return impl_->height_;}
uint32_t video_decoder::nb_frames() const{return impl_->nb_frames();}
{
public:
explicit video_decoder(const spl::shared_ptr<AVFormatContext>& context);
-
+
bool ready() const;
+ bool empty() const;
void push(const std::shared_ptr<AVPacket>& packet);
std::shared_ptr<AVFrame> poll();
-
+
int width() const;
int height() const;
spl::shared_ptr<implementation> impl_;
};
-}}
\ No newline at end of file
+}}
#pragma comment (lib, "libcef_dll_wrapper.lib")
namespace caspar { namespace html {
-
+
class html_client
: public CefClient
, public CefRenderHandler
window_info.SetTransparentPainting(true);
window_info.SetAsOffScreen(nullptr);
//window_info.SetAsWindowless(nullptr, true);
-
+
CefBrowserSettings browser_settings;
browser_settings.web_security = cef_state_t::STATE_DISABLED;
CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
const core::frame_producer_dependencies& dependencies,
const std::vector<std::wstring>& params)
{
- const auto filename = env::template_folder() + params.at(0) + L".html";
- const auto found_filename = find_case_insensitive(filename);
+ const auto filename = env::template_folder() + params.at(0) + L".html";
+ const auto found_filename = find_case_insensitive(filename);
+ const auto html_prefix = boost::iequals(params.at(0), L"[HTML]");
- if (!found_filename && !boost::iequals(params.at(0), L"[HTML]"))
+ if (!found_filename && !html_prefix)
return core::frame_producer::empty();
- const auto url = found_filename
+ const auto url = found_filename
? L"file://" + *found_filename
: params.at(1);
-
- if (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
+
+ if (!html_prefix && (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA")))
return core::frame_producer::empty();
return core::create_destroy_proxy(spl::make_shared<html_producer>(
#include <common/log.h>
#include <common/array.h>
#include <common/base64.h>
+#include <common/param.h>
#include <common/os/filesystem.h>
#include <boost/filesystem.hpp>
core::monitor::subject monitor_subject_;
const std::wstring description_;
const spl::shared_ptr<core::frame_factory> frame_factory_;
+ const uint32_t length_;
core::draw_frame frame_ = core::draw_frame::empty();
core::constraints constraints_;
- image_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& description, bool thumbnail_mode)
+ image_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& description, bool thumbnail_mode, uint32_t length)
: description_(description)
, frame_factory_(frame_factory)
+ , length_(length)
{
load(load_image(description_));
CASPAR_LOG(info) << print() << L" Initialized";
}
- image_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const void* png_data, size_t size)
+ image_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const void* png_data, size_t size, uint32_t length)
: description_(L"png from memory")
, frame_factory_(frame_factory)
+ , length_(length)
{
load(load_png_from_memory(png_data, size));
return frame_;
}
+ uint32_t nb_frames() const override
+ {
+ return length_;
+ }
+
core::constraints& pixel_constraints() override
{
return constraints_;
void describe_producer(core::help_sink& sink, const core::help_repository& repo)
{
sink.short_description(L"Loads a still image.");
- sink.syntax(L"{[image_file:string]},{[PNG_BASE64] [encoded:string]}");
+ sink.syntax(L"{[image_file:string]},{[PNG_BASE64] [encoded:string]} {LENGTH [length:int]}");
sink.para()->text(L"Loads a still image, either from disk or via a base64 encoded image submitted via AMCP.");
+ sink.para()->text(L"The ")->code(L"length")->text(L" parameter can be used to limit the number of frames that the image will be shown for.");
sink.para()->text(L"Examples:");
sink.example(L">> PLAY 1-10 image_file", L"Plays an image from the media folder.");
sink.example(L">> PLAY 1-10 [PNG_BASE64] data...", L"Plays a PNG image transferred as a base64 encoded string.");
+ sink.example(
+ L">> PLAY 1-10 slide_show1 LENGTH 100\n"
+ L">> LOADBG 1-10 slide_show2 LENGTH 100 MIX 20 AUTO\n"
+ L"delay until foreground layer becomes slide_show2 and then\n"
+ L">> LOADBG 1-10 slide_show3 LENGTH 100 MIX 20 AUTO\n"
+ L"delay until foreground layer becomes slide_show3 and then\n"
+ L">> LOADBG 1-10 EMPTY MIX 20 AUTO\n", L"Plays a slide show of 3 images for 100 frames each and fades to black.");
}
static const auto g_extensions = {
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
+ auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max());
if (boost::iequals(params.at(0), L"[IMG_SEQUENCE]"))
{
auto png_data = from_base64(std::string(params.at(1).begin(), params.at(1).end()));
- return spl::make_shared<image_producer>(dependencies.frame_factory, png_data.data(), png_data.size());
+ return spl::make_shared<image_producer>(dependencies.frame_factory, png_data.data(), png_data.size(), length);
}
std::wstring filename = env::media_folder() + params.at(0);
if(ext == g_extensions.end())
return core::frame_producer::empty();
- return spl::make_shared<image_producer>(dependencies.frame_factory, *caspar::find_case_insensitive(filename + *ext), false);
+ return spl::make_shared<image_producer>(dependencies.frame_factory, *caspar::find_case_insensitive(filename + *ext), false, length);
}
spl::shared_ptr<core::frame_producer> producer = spl::make_shared<image_producer>(
dependencies.frame_factory,
*caspar::find_case_insensitive(filename + *ext),
- true);
+ true,
+ 1);
return producer->receive();
}
int min_num_params_;
std::wstring name_;
std::wstring replyString_;
+ std::wstring request_id_;
public:
AMCPCommand(const command_context& ctx, const amcp_command_func& command, int min_num_params, const std::wstring& name)
: ctx_(ctx)
return name_;
}
+ void set_request_id(std::wstring request_id)
+ {
+ request_id_ = std::move(request_id);
+ }
+
void SetReplyString(const std::wstring& str)
{
- replyString_ = str;
+ if (request_id_.empty())
+ replyString_ = str;
+ else
+ replyString_ = L"RES " + request_id_ + L" " + str;
}
};
}}}
+ L"\r\n";
}
-std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
+std::wstring get_sub_directory(const std::wstring& base_folder, const std::wstring& sub_directory)
+{
+ if (sub_directory.empty())
+ return base_folder;
+
+ auto found = find_case_insensitive(base_folder + L"/" + sub_directory);
+
+ if (!found)
+ CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Sub directory " + sub_directory + L" not found."));
+
+ return *found;
+}
+
+std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo, const std::wstring& sub_directory = L"")
{
std::wstringstream replyString;
- for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
+ for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::media_folder(), sub_directory)), end; itr != end; ++itr)
replyString << MediaInfo(itr->path(), media_info_repo);
return boost::to_upper_copy(replyString.str());
}
-std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
+std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry, const std::wstring& sub_directory = L"")
{
std::wstringstream replyString;
- for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
+ for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::template_folder(), sub_directory)), end; itr != end; ++itr)
{
if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
{
void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
{
sink.short_description(L"List stored datasets.");
- sink.syntax(L"DATA LIST");
- sink.para()->text(L"Returns a list of all stored datasets.");
+ sink.syntax(L"DATA LIST {[sub_directory:string]}");
+ sink.para()->text(L"Returns a list of stored datasets.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the datasets in that sub directory will be returned.");
}
std::wstring data_list_command(command_context& ctx)
{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
std::wstringstream replyString;
replyString << L"200 DATA LIST OK\r\n";
- for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
+ for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::data_folder(), sub_directory)), end; itr != end; ++itr)
{
if (boost::filesystem::is_regular_file(itr->path()))
{
void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
{
sink.short_description(L"Enable chroma keying on a layer.");
- sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
+ sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[enable:0,1] {[target_hue:float] [hue_width:float] [min_saturation:float] [min_brightness:float] [softness:float] [spill:float] [spill_darken:float] [show_mask:0,1]}}" + ANIMATION_SYNTAX);
sink.para()
->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
- sink.para()->text(L"Examples:");
- sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
- sink.example(L">> MIXER 1-1 CHROMA none");
+ sink.para()->text(L"The chroma keying is done in the HSB/HSV color space.");
+ sink.para()->text(L"Parameters:");
+ sink.definitions()
+ ->item(L"enable", L"0 to disable chroma keying on layer. The rest of the parameters should not be given when disabling.")
+ ->item(L"target_hue", L"The hue in degrees between 0-360 where the center of the hue window will open up.")
+ ->item(L"hue_width", L"The width of the hue window within 0.0-1.0 where 1.0 means 100% of 360 degrees around target_hue.")
+ ->item(L"min_saturation", L"The minimum saturation within 0.0-1.0 required for a color to be within the chroma window.")
+ ->item(L"min_brightness", L"The minimum brightness within 0.0-1.0 required for a color to be within the chroma window.")
+ ->item(L"softness", L"The softness of the chroma keying window.")
+ ->item(L"spill", L"Controls the amount of spill. A value of 1.0 does not suppress any spill. A lower value gradually turns the spill into grayscale and more transparent.")
+ ->item(L"spill_darken", L"Controls the shade of gray that the spill suppression is done towards. Lower values goes towards white and higher values goes towards black.")
+ ->item(L"show_mask", L"If enabled, only shows the mask. Useful while editing the chroma key settings.")
+ ;
+ sink.example(L">> MIXER 1-1 CHROMA 1 120 0.1 0 0 0.1 1 2 0", L"for enabling chroma keying centered around a hue of 120 degrees (green) and with a 10% hue width");
+ sink.example(L">> MIXER 1-1 CHROMA 0", L"for disabling chroma keying");
sink.example(
- L">> MIXER 1-1 BLEND\n"
- L"<< 201 MIXER OK\n"
- L"<< SCREEN", L"for getting the current blend mode");
+ L">> MIXER 1-1 CHROMA 0\n"
+ L"<< 202 MIXER OK\n"
+ L"<< 1 120 0.1 0 0 0.1 1 2 0", L"for getting the current chroma key mode");
+ sink.para()->text(L"Deprecated legacy syntax:");
+ sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] [spill:float]}}" + ANIMATION_SYNTAX);
+ sink.para()->text(L"Deprecated legacy examples:");
+ sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 1.0 25 easeinsine");
+ sink.example(L">> MIXER 1-1 CHROMA none");
}
std::wstring mixer_chroma_command(command_context& ctx)
{
auto chroma = get_current_transform(ctx).image_transform.chroma;
return L"201 MIXER OK\r\n"
- + core::get_chroma_mode(chroma.key) + L" "
- + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
+ + std::wstring(chroma.enable ? L"1 " : L"0 ")
+ + boost::lexical_cast<std::wstring>(chroma.target_hue) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.hue_width) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.min_saturation) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.min_brightness) + L" "
+ boost::lexical_cast<std::wstring>(chroma.softness) + L" "
- + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
+ + boost::lexical_cast<std::wstring>(chroma.spill) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.spill_darken) + L" "
+ + std::wstring(chroma.show_mask ? L"1" : L"0") + L"\r\n";
}
transforms_applier transforms(ctx);
- int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
- std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
-
core::chroma chroma;
- chroma.key = get_chroma_mode(ctx.parameters.at(0));
- if (chroma.key != core::chroma::type::none)
+ int duration;
+ std::wstring tween;
+
+ auto legacy_mode = core::get_chroma_mode(ctx.parameters.at(0));
+
+ if (legacy_mode)
{
- chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
- chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
- chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
+
+ duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
+ tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
+
+ if (*legacy_mode == chroma::legacy_type::none)
+ {
+ chroma.enable = false;
+ }
+ else
+ {
+ chroma.enable = true;
+ chroma.hue_width = 0.5 - boost::lexical_cast<double>(ctx.parameters.at(1)) * 0.5;
+ chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2)) - boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(3));
+ chroma.spill_darken = 2;
+
+ if (*legacy_mode == chroma::legacy_type::green)
+ chroma.target_hue = 120;
+ else if (*legacy_mode == chroma::legacy_type::blue)
+ chroma.target_hue = 240;
+ }
}
+ else
+ {
+ duration = ctx.parameters.size() > 9 ? boost::lexical_cast<int>(ctx.parameters.at(9)) : 0;
+ tween = ctx.parameters.size() > 10 ? ctx.parameters.at(10) : L"linear";
+
+ chroma.enable = ctx.parameters.at(0) == L"1";
+
+ if (chroma.enable)
+ {
+ chroma.target_hue = boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.hue_width = boost::lexical_cast<double>(ctx.parameters.at(2));
+ chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(3));
+ chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(4));
+ chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(5));
+ chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(6));
+ chroma.spill_darken = boost::lexical_cast<double>(ctx.parameters.at(7));
+ chroma.show_mask = boost::lexical_cast<double>(ctx.parameters.at(8));
+ }
+ }
+
transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
{
void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
{
- sink.short_description(L"List all thumbnails.");
- sink.syntax(L"THUMBNAIL LIST");
- sink.para()->text(L"Lists all thumbnails.");
+ sink.short_description(L"List thumbnails.");
+ sink.syntax(L"THUMBNAIL LIST {[sub_directory:string]}");
+ sink.para()->text(L"Lists thumbnails.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the thumbnails in that sub directory will be returned.");
sink.para()->text(L"Examples:");
sink.example(
L">> THUMBNAIL LIST\n"
std::wstring thumbnail_list_command(command_context& ctx)
{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
std::wstringstream replyString;
replyString << L"200 THUMBNAIL LIST OK\r\n";
- for (boost::filesystem::recursive_directory_iterator itr(env::thumbnail_folder()), end; itr != end; ++itr)
+ for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::thumbnail_folder(), sub_directory)), end; itr != end; ++itr)
{
if (boost::filesystem::is_regular_file(itr->path()))
{
sink.short_description(L"Get information about a media file.");
sink.syntax(L"CINF [filename:string]");
sink.para()->text(L"Returns information about a media file.");
+ sink.para()->text(L"If a file with the same name exist in multiple directories, all of them are returned.");
}
std::wstring cinf_command(command_context& ctx)
{
std::wstring info;
- for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
+ for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
{
auto path = itr->path();
- auto file = path.replace_extension(L"").filename().wstring();
+ auto file = path.stem().wstring();
if (boost::iequals(file, ctx.parameters.at(0)))
info += MediaInfo(itr->path(), ctx.media_info_repo);
}
void cls_describer(core::help_sink& sink, const core::help_repository& repo)
{
- sink.short_description(L"List all media files.");
- sink.syntax(L"CLS");
+ sink.short_description(L"List media files.");
+ sink.syntax(L"CLS {[sub_directory:string]}");
sink.para()
- ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
+ ->text(L"Lists media files in the ")->code(L"media")->text(L" folder. Use the command ")
->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the media files in that sub directory will be returned.");
}
std::wstring cls_command(command_context& ctx)
{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
std::wstringstream replyString;
replyString << L"200 CLS OK\r\n";
- replyString << ListMedia(ctx.media_info_repo);
+ replyString << ListMedia(ctx.media_info_repo, sub_directory);
replyString << L"\r\n";
return boost::to_upper_copy(replyString.str());
}
void tls_describer(core::help_sink& sink, const core::help_repository& repo)
{
- sink.short_description(L"List all templates.");
- sink.syntax(L"TLS");
+ sink.short_description(L"List templates.");
+ sink.syntax(L"TLS {[sub_directory:string]}");
sink.para()
- ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
+ ->text(L"Lists template files in the ")->code(L"templates")->text(L" folder. Use the command ")
->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the template files in that sub directory will be returned.");
}
std::wstring tls_command(command_context& ctx)
{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
std::wstringstream replyString;
replyString << L"200 TLS OK\r\n";
- replyString << ListTemplates(ctx.cg_registry);
+ replyString << ListTemplates(ctx.cg_registry, sub_directory);
replyString << L"\r\n";
return replyString.str();
CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
}
+void req_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Perform any command with an additional request id identifying the response.");
+ sink.syntax(L"REQ [request_id:string] COMMAND...");
+ sink.para()
+ ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
+ ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
+ sink.para()->text(L"Examples:");
+ sink.example(L"REQ unique PLAY 1-0 AMB\n");
+ sink.example(
+ L">> REQ unique PLAY 1-0 AMB\n"
+ L"<< RES unique 202 PLAY OK");
+}
+
+
void register_commands(amcp_command_repository& repo)
{
repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
+
+ repo.help_repo()->register_item({ L"AMCP", L"Protocol Commands" }, L"REQ", req_describer);
}
} //namespace amcp
* Author: Nicklas P Andersson
*/
-
+
#include "../StdAfx.h"
#include "AMCPProtocolStrategy.h"
void Parse(const std::wstring& message, ClientInfoPtr client)
{
CASPAR_LOG_COMMUNICATION(info) << L"Received message from " << client->address() << ": " << message << L"\\r\\n";
-
+
command_interpreter_result result;
if(interpret_command_string(message, result, client))
{
else
result.queue->AddCommand(result.command);
}
-
+
if (result.error != error_state::no_error)
{
std::wstringstream answer;
if (!tokens.empty() && tokens.front().at(0) == L'/')
tokens.pop_front();
+ std::wstring request_id;
+
+ if (!tokens.empty() && boost::iequals(tokens.front(), L"REQ"))
+ {
+ tokens.pop_front();
+
+ if (tokens.empty())
+ {
+ result.error = error_state::parameters_error;
+ return false;
+ }
+
+ request_id = tokens.front();
+ tokens.pop_front();
+ }
+
// Fail if no more tokens.
if (tokens.empty())
{
if (result.command->parameters().size() < result.command->minimum_parameters())
result.error = error_state::parameters_error;
}
+
+ if (result.command)
+ result.command->set_request_id(std::move(request_id));
}
catch (std::out_of_range&)
{
self.channel_commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params)));
}
+spl::shared_ptr<core::help_repository> amcp_command_repository::help_repo() const
+{
+ return impl_->help_repo;
+}
+
}}}
void register_command(std::wstring category, std::wstring name, core::help_item_describer describer, amcp_command_func command, int min_num_params);
void register_channel_command(std::wstring category, std::wstring name, core::help_item_describer describer, amcp_command_func command, int min_num_params);
+ spl::shared_ptr<core::help_repository> help_repo() const;
private:
struct impl;
spl::shared_ptr<impl> impl_;
<channel-grid> false [true|false]</channel-grid>\r
<mixer>\r
<blend-modes> false [true|false]</blend-modes>\r
- <mipmapping_default_on>false [true|false]</mipmapping_default_on>\r
+ <mipmapping-default-on>false [true|false]</mipmapping-default-on>\r
<straight-alpha> false [true|false]</straight-alpha>\r
</mixer>\r
<accelerator>auto [cpu|gpu|auto]</accelerator>\r
std::wstring wcmd;
while(true)
{
- std::getline(std::wcin, wcmd); // TODO: It's blocking...
-
- //boost::to_upper(wcmd);
+ if (!std::getline(std::wcin, wcmd)) // TODO: It's blocking...
+ wcmd = L"EXIT"; // EOF, handle as EXIT
if(boost::iequals(wcmd, L"EXIT") || boost::iequals(wcmd, L"Q") || boost::iequals(wcmd, L"QUIT") || boost::iequals(wcmd, L"BYE"))
{