From: HellGore Date: Fri, 27 Jan 2017 13:21:04 +0000 (+0100) Subject: Merge pull request #501 from dimitry-ishenko-casparcg/next X-Git-Tag: 2.1.0_Beta2~113 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=7655718811e2643e1b6c35950dc8898b9b55b110;hp=4e2ab9bba40fca263e759faa5635c181bd81a6ec;p=casparcg Merge pull request #501 from dimitry-ishenko-casparcg/next Add support for IN and OUT parameters in PLAY, CALL and SEEK commands --- diff --git a/CHANGELOG b/CHANGELOG index 749f02d6f..b62b3394e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,24 @@ General against INFO THREADS, ps and top. o Created automatically generated build number, so that it is easier to see whether a build is newer or older than an other. + o Changed configuration element mipmapping_default_on to mipmapping-default-on + for consistency with the rest of the configuration (Jesper Stærkær). + o Handle stdin EOF as EXIT. + o Added support for RESTART in Linux startup script run.sh. + o Copy casparcg_auto_restart.bat into Windows releases. + o Fixed bug with thumbnail generation when there are .-files in the media + folder. + +Consumers +--------- + + o FFmpeg consumer: + + Fixed long overdue bug where HD material was always recorded using the + BT.601 color matrix instead of the BT.709 color matrix. RGB codecs like + qtrle was never affected but all the YCbCr based codecs were. + + Fixed bug in parsing of paths containing -. + o DeckLink consumer: + + Fixed possible dead-lock in frame queue. Producers --------- @@ -24,12 +42,39 @@ Producers o FFmpeg producer: + Increased the max number of frames that audio/video can be badly interleaved with (Dimitry Ishenko). + + Fixed bug where decoders sometimes requires more than one video packet to + decode the first frame. + o Framerate producer: + + Fixed bug when INFO was used on a not yet playing framerate producer. + o HTML producer: + + Fixed bug where only URL:s with . in them where recognized. + o Image producer: + + Added LENGTH parameter to allow for queueing with LOADBG AUTO. + +Mixer +----- + + o Fixed bug in the contrast/saturation/brightness code where the wrong luma + coefficients was used. + o Rewrote the chroma key code to support variable hue, instead of fixed green + or blue. Threshold setting was removed in favour of separate hue width, + minimum saturation and minimum brightness constraints. AMCP ---- o INFO PATHS now adds all the path elements even if they are using the default values. + o MIXER CHROMA syntax deprecated (still supported) in favour of the more + advanced syntax required by the rewritten chroma key code. + o Added special command REQ that can be prepended before any command to + identify the response with a client specified request id, allowing a client + to know exactly what asynchronous response matched a specific request. + o Added support for listing contents of a specific directory for CLS, TLS, + DATA LIST and THUMBNAIL LIST. + o Fixed bug where CINF only returned the first match. + + CasparCG 2.1.0 Beta 1 (w.r.t 2.0.7 Stable) ========================================== diff --git a/accelerator/ogl/image/blending_glsl.h b/accelerator/ogl/image/blending_glsl.h index 42e95f37f..7f3429d7f 100644 --- a/accelerator/ogl/image/blending_glsl.h +++ b/accelerator/ogl/image/blending_glsl.h @@ -36,7 +36,9 @@ static std::string get_adjustement_glsl() 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; @@ -279,40 +281,70 @@ static std::string get_chroma_glsl() // 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"; diff --git a/accelerator/ogl/image/image_kernel.cpp b/accelerator/ogl/image/image_kernel.cpp index b10accd13..37bb44ff7 100644 --- a/accelerator/ogl/image/image_kernel.cpp +++ b/accelerator/ogl/image/image_kernel.cpp @@ -123,15 +123,15 @@ GLubyte upper_pattern[] = { 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 ogl_; spl::shared_ptr shader_; bool blend_modes_; @@ -148,8 +148,8 @@ struct image_kernel::impl 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) @@ -240,12 +240,12 @@ struct image_kernel::impl if(params.local_key) params.local_key->bind(static_cast(texture_id::local_key)); - + if(params.layer_key) params.layer_key->bind(static_cast(texture_id::layer_key)); - + // Setup shader - + shader_->use(); shader_->set("post_processing", false); @@ -254,8 +254,8 @@ struct image_kernel::impl 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(n) + "]", - static_cast(params.textures[n]->width()), + shader_->set("plane_size[" + boost::lexical_cast(n) + "]", + static_cast(params.textures[n]->width()), static_cast(params.textures[n]->height())); shader_->set("local_key", texture_id::local_key); @@ -263,22 +263,28 @@ struct image_kernel::impl shader_->set("is_hd", params.pix_desc.planes.at(0).height > 700 ? 1 : 0); shader_->set("has_local_key", static_cast(params.local_key)); shader_->set("has_layer_key", static_cast(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(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; @@ -297,48 +303,48 @@ struct image_kernel::impl 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)); @@ -349,10 +355,10 @@ struct image_kernel::impl } // 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; @@ -363,18 +369,18 @@ struct image_kernel::impl { double w = static_cast(params.background->width()); double h = static_cast(params.background->height()); - + GL(glEnable(GL_SCISSOR_TEST)); glScissor(static_cast(m_p[0] * w), static_cast(m_p[1] * h), std::max(0, static_cast(m_s[0] * w)), std::max(0, static_cast(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(); @@ -451,7 +457,7 @@ struct image_kernel::impl default: break; } - + // Cleanup GL(glDisable(GL_SCISSOR_TEST)); GL(glDisable(GL_POLYGON_STIPPLE)); diff --git a/accelerator/ogl/image/image_shader.cpp b/accelerator/ogl/image/image_shader.cpp index dfc47a0b2..c4a5ecdc3 100644 --- a/accelerator/ogl/image/image_shader.cpp +++ b/accelerator/ogl/image/image_shader.cpp @@ -103,7 +103,7 @@ std::string get_blend_color_func() } )shader"; } - + std::string get_simple_blend_color_func() { return @@ -125,20 +125,13 @@ std::string get_chroma_func() 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"; } @@ -211,9 +204,14 @@ std::string get_fragment(bool blend_modes, bool post_processing) 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" + @@ -319,7 +317,7 @@ std::string get_fragment(bool blend_modes, bool post_processing) color.rgb /= color.a + 0.0000001; return color; - } + } void main() { @@ -383,9 +381,9 @@ std::shared_ptr get_image_shader( 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); @@ -394,7 +392,7 @@ std::shared_ptr get_image_shader( { 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); } diff --git a/build-scripts/build-windows.bat b/build-scripts/build-windows.bat index 8830d34a9..a1417e442 100644 --- a/build-scripts/build-windows.bat +++ b/build-scripts/build-windows.bat @@ -45,6 +45,7 @@ echo Copying binaries... 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 diff --git a/common/filesystem.cpp b/common/filesystem.cpp index 41869ad04..877ac7ca6 100644 --- a/common/filesystem.cpp +++ b/common/filesystem.cpp @@ -22,6 +22,7 @@ #include "stdafx.h" #include "filesystem.h" +#include "except.h" #include #include @@ -32,9 +33,11 @@ boost::filesystem::path get_relative( 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) { @@ -44,7 +47,7 @@ boost::filesystem::path get_relative( 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; } diff --git a/core/frame/frame_transform.cpp b/core/frame/frame_transform.cpp index 4ba189920..393214b69 100644 --- a/core/frame/frame_transform.cpp +++ b/core/frame/frame_transform.cpp @@ -53,7 +53,7 @@ void transform_corners(corners& self, const corners& other) image_transform& image_transform::operator*=(const image_transform &other) { - opacity *= other.opacity; + opacity *= other.opacity; brightness *= other.brightness; contrast *= other.contrast; saturation *= other.saturation; @@ -87,14 +87,19 @@ image_transform& image_transform::operator*=(const image_transform &other) 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; @@ -137,39 +142,44 @@ void do_tween_corners(const corners& source, const corners& dest, corners& out, 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); @@ -200,7 +210,7 @@ bool operator==(const rectangle& lhs, const rectangle& rhs) 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) && @@ -218,6 +228,15 @@ bool operator==(const image_transform& lhs, const image_transform& rhs) 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; } @@ -231,7 +250,7 @@ bool operator!=(const image_transform& lhs, const image_transform& rhs) audio_transform& audio_transform::operator*=(const audio_transform &other) { - volume *= other.volume; + volume *= other.volume; is_still |= other.is_still; return *this; } @@ -242,11 +261,11 @@ audio_transform audio_transform::operator*(const audio_transform &other) const } 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; } @@ -287,7 +306,7 @@ frame_transform frame_transform::tween(double time, const frame_transform& sourc 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; } @@ -297,31 +316,16 @@ bool operator!=(const frame_transform& lhs, const frame_transform& rhs) } -core::chroma::type get_chroma_mode(const std::wstring& str) +boost::optional 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 { diff --git a/core/frame/frame_transform.h b/core/frame/frame_transform.h index bf2cdb35f..724d35cd1 100644 --- a/core/frame/frame_transform.h +++ b/core/frame/frame_transform.h @@ -28,23 +28,29 @@ #include #include +#include #include 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 @@ -98,7 +104,7 @@ struct image_transform 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; @@ -112,7 +118,7 @@ struct audio_transform final { double volume = 1.0; bool is_still = false; - + audio_transform& operator*=(const audio_transform &other); audio_transform operator*(const audio_transform &other) const; @@ -122,17 +128,17 @@ struct audio_transform final 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; @@ -149,12 +155,12 @@ class tweened_transform 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) @@ -170,21 +176,20 @@ public: { return dest_; } - + frame_transform fetch() { return time_ == duration_ ? dest_ : frame_transform::tween(static_cast(time_), source_, dest_, static_cast(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 get_chroma_mode(const std::wstring& str); namespace detail { diff --git a/core/producer/framerate/framerate_producer.cpp b/core/producer/framerate/framerate_producer.cpp index b6141cc0b..5125ebae3 100644 --- a/core/producer/framerate/framerate_producer.cpp +++ b/core/producer/framerate/framerate_producer.cpp @@ -313,6 +313,9 @@ public: uint32_t nb_frames() const override { + if (!is_initialized()) + return std::numeric_limits::max(); + auto source_nb_frames = source_->nb_frames(); auto multiple = boost::rational_cast(1 / get_speed() * (output_repeat_ != 0 ? 2 : 1)); @@ -321,6 +324,9 @@ public: 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(1 / get_speed() * (output_repeat_ != 0 ? 2 : 1)); @@ -332,6 +338,11 @@ public: 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(); diff --git a/core/producer/scene/scene_producer.cpp b/core/producer/scene/scene_producer.cpp index fd99bb96b..f5f2a625e 100644 --- a/core/producer/scene/scene_producer.cpp +++ b/core/producer/scene/scene_producer.cpp @@ -149,7 +149,7 @@ struct scene_producer::impl auto speed_variable = std::make_shared>(L"1.0", true, 1.0); store_variable(L"scene_speed", speed_variable); speed_ = speed_variable->value(); - + auto frame_variable = std::make_shared>(L"-1", true, -1); store_variable(L"frame", frame_variable); frame_number_ = frame_variable->value(); @@ -256,14 +256,18 @@ struct scene_producer::impl 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; @@ -316,9 +320,9 @@ struct scene_producer::impl { prec_timer timer; timer.tick_millis(0); - + auto field1 = render_progressive_frame(); - + timer.tick(0.5 / format_desc_.fps); auto field2 = render_progressive_frame(); @@ -432,7 +436,7 @@ struct scene_producer::impl return boost::optional(); } - std::future call(const std::vector& params) + std::future call(const std::vector& params) { if (!params.empty() && boost::ends_with(params.at(0), L"()")) return make_ready_future(handle_call(params)); @@ -531,7 +535,7 @@ struct scene_producer::impl { return producer_name_; } - + boost::property_tree::wptree info() const { boost::property_tree::wptree info; @@ -636,7 +640,7 @@ boost::property_tree::wptree scene_producer::info() const return impl_->info(); } -std::future scene_producer::call(const std::vector& params) +std::future scene_producer::call(const std::vector& params) { return impl_->call(params); } diff --git a/core/producer/scene/scene_producer.h b/core/producer/scene/scene_producer.h index ccfad7fe9..aca30f6b6 100644 --- a/core/producer/scene/scene_producer.h +++ b/core/producer/scene/scene_producer.h @@ -60,10 +60,14 @@ struct adjustments struct chroma_key { - binding key; - binding threshold; + binding enable; + binding target_hue; + binding hue_width; + binding min_saturation; + binding min_brightness; binding softness; binding spill; + binding spill_darken; }; struct layer @@ -195,7 +199,7 @@ public: static_cast(duration))); to_affect.set(tweened); - + //CASPAR_LOG(info) << relative_frame << L" " << *start_value << L" " << duration << L" " << tweened; }; diff --git a/core/producer/scene/xml_scene_producer.cpp b/core/producer/scene/xml_scene_producer.cpp index 5eacb6c36..49f1ee4d8 100644 --- a/core/producer/scene/xml_scene_producer.cpp +++ b/core/producer/scene/xml_scene_producer.cpp @@ -170,10 +170,14 @@ spl::shared_ptr create_xml_scene_producer( layer.is_key = scene->create_variable(variable_prefix + L"is_key", false, elem.second.get(L"is_key", L"false")); layer.use_mipmap = scene->create_variable(variable_prefix + L"use_mipmap", false, elem.second.get(L"use_mipmap", L"false")); layer.blend_mode = scene->create_variable(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(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(variable_prefix + L"chroma_key.threshold", false, elem.second.get(L"chroma_key.threshold", L"0.0")); + layer.chroma_key.enable = scene->create_variable(variable_prefix + L"chroma_key.enable", false, elem.second.get(L"chroma_key.enable", L"false")); + layer.chroma_key.target_hue = scene->create_variable(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(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(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(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(variable_prefix + L"chroma_key.softness", false, elem.second.get(L"chroma_key.softness", L"0.0")); - layer.chroma_key.spill = scene->create_variable(variable_prefix + L"chroma_key.spill", false, elem.second.get(L"chroma_key.spill", L"0.0")); + layer.chroma_key.spill = scene->create_variable(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(variable_prefix + L"chroma_key.spill_darken", false, elem.second.get(L"chroma_key.spill_darken", L"2.0")); scene->create_variable(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width; scene->create_variable(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height; @@ -234,7 +238,7 @@ spl::shared_ptr create_xml_scene_producer( 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()) diff --git a/deploy/linux/run.sh b/deploy/linux/run.sh index 185a73608..72a66bf16 100755 --- a/deploy/linux/run.sh +++ b/deploy/linux/run.sh @@ -1,4 +1,10 @@ #!/bin/sh -LD_LIBRARY_PATH=lib bin/casparcg "$@" +RET=5 + +while [ $RET -eq 5 ] +do + LD_LIBRARY_PATH=lib bin/casparcg "$@" + RET=$? +done diff --git a/modules/decklink/consumer/decklink_consumer.cpp b/modules/decklink/consumer/decklink_consumer.cpp index ca847727e..39dcde7f4 100644 --- a/modules/decklink/consumer/decklink_consumer.cpp +++ b/modules/decklink/consumer/decklink_consumer.cpp @@ -647,10 +647,13 @@ public: if(!is_running_) CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running.")); + boost::lock_guard lock(send_completion_mutex_); + if (frame_buffer_.try_push(frame)) + { + send_completion_ = std::packaged_task(); return make_ready_future(true); - - boost::lock_guard lock(send_completion_mutex_); + } send_completion_ = std::packaged_task([frame, this] () mutable -> bool { diff --git a/modules/ffmpeg/consumer/ffmpeg_consumer.cpp b/modules/ffmpeg/consumer/ffmpeg_consumer.cpp index e6f1ded38..188fa4188 100644 --- a/modules/ffmpeg/consumer/ffmpeg_consumer.cpp +++ b/modules/ffmpeg/consumer/ffmpeg_consumer.cpp @@ -247,30 +247,37 @@ public: { 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(); + } } } @@ -702,6 +709,11 @@ private: 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, @@ -1298,6 +1310,15 @@ spl::shared_ptr create_ffmpeg_consumer( 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(path, args, separate_key, mono_streams, compatibility_mode); diff --git a/modules/ffmpeg/producer/ffmpeg_producer.cpp b/modules/ffmpeg/producer/ffmpeg_producer.cpp index b71e36a8f..216559156 100644 --- a/modules/ffmpeg/producer/ffmpeg_producer.cpp +++ b/modules/ffmpeg/producer/ffmpeg_producer.cpp @@ -559,8 +559,17 @@ public: 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()); }, [&] { @@ -599,7 +608,8 @@ public: 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 diff --git a/modules/ffmpeg/producer/video/video_decoder.cpp b/modules/ffmpeg/producer/video/video_decoder.cpp index 9322baad4..8f8e60abf 100644 --- a/modules/ffmpeg/producer/video/video_decoder.cpp +++ b/modules/ffmpeg/producer/video/video_decoder.cpp @@ -148,6 +148,11 @@ public: return packets_.size() >= 8; } + bool empty() const + { + return packets_.empty(); + } + uint32_t nb_frames() const { return std::max(nb_frames_, static_cast(file_frame_number_)); @@ -163,6 +168,7 @@ video_decoder::video_decoder(const spl::shared_ptr& context) : void video_decoder::push(const std::shared_ptr& packet){impl_->push(packet);} std::shared_ptr 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();} diff --git a/modules/ffmpeg/producer/video/video_decoder.h b/modules/ffmpeg/producer/video/video_decoder.h index d954dc05b..5947b8ea4 100644 --- a/modules/ffmpeg/producer/video/video_decoder.h +++ b/modules/ffmpeg/producer/video/video_decoder.h @@ -42,11 +42,12 @@ class video_decoder : boost::noncopyable { public: explicit video_decoder(const spl::shared_ptr& context); - + bool ready() const; + bool empty() const; void push(const std::shared_ptr& packet); std::shared_ptr poll(); - + int width() const; int height() const; @@ -61,4 +62,4 @@ private: spl::shared_ptr impl_; }; -}} \ No newline at end of file +}} diff --git a/modules/html/producer/html_producer.cpp b/modules/html/producer/html_producer.cpp index 78ca18fc7..241c2f41f 100644 --- a/modules/html/producer/html_producer.cpp +++ b/modules/html/producer/html_producer.cpp @@ -73,7 +73,7 @@ #pragma comment (lib, "libcef_dll_wrapper.lib") namespace caspar { namespace html { - + class html_client : public CefClient , public CefRenderHandler @@ -459,7 +459,7 @@ public: 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); @@ -599,17 +599,18 @@ spl::shared_ptr create_producer( const core::frame_producer_dependencies& dependencies, const std::vector& 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( diff --git a/modules/image/producer/image_producer.cpp b/modules/image/producer/image_producer.cpp index 112922ac5..b1857407b 100644 --- a/modules/image/producer/image_producer.cpp +++ b/modules/image/producer/image_producer.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -79,12 +80,14 @@ struct image_producer : public core::frame_producer_base core::monitor::subject monitor_subject_; const std::wstring description_; const spl::shared_ptr frame_factory_; + const uint32_t length_; core::draw_frame frame_ = core::draw_frame::empty(); core::constraints constraints_; - image_producer(const spl::shared_ptr& frame_factory, const std::wstring& description, bool thumbnail_mode) + image_producer(const spl::shared_ptr& frame_factory, const std::wstring& description, bool thumbnail_mode, uint32_t length) : description_(description) , frame_factory_(frame_factory) + , length_(length) { load(load_image(description_)); @@ -94,9 +97,10 @@ struct image_producer : public core::frame_producer_base CASPAR_LOG(info) << print() << L" Initialized"; } - image_producer(const spl::shared_ptr& frame_factory, const void* png_data, size_t size) + image_producer(const spl::shared_ptr& 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)); @@ -131,6 +135,11 @@ struct image_producer : public core::frame_producer_base return frame_; } + uint32_t nb_frames() const override + { + return length_; + } + core::constraints& pixel_constraints() override { return constraints_; @@ -178,11 +187,19 @@ public: 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 = { @@ -202,6 +219,7 @@ static const auto g_extensions = { spl::shared_ptr create_producer(const core::frame_producer_dependencies& dependencies, const std::vector& params) { + auto length = get_param(L"LENGTH", params, std::numeric_limits::max()); if (boost::iequals(params.at(0), L"[IMG_SEQUENCE]")) { @@ -258,7 +276,7 @@ spl::shared_ptr create_producer(const core::frame_producer auto png_data = from_base64(std::string(params.at(1).begin(), params.at(1).end())); - return spl::make_shared(dependencies.frame_factory, png_data.data(), png_data.size()); + return spl::make_shared(dependencies.frame_factory, png_data.data(), png_data.size(), length); } std::wstring filename = env::media_folder() + params.at(0); @@ -273,7 +291,7 @@ spl::shared_ptr create_producer(const core::frame_producer if(ext == g_extensions.end()) return core::frame_producer::empty(); - return spl::make_shared(dependencies.frame_factory, *caspar::find_case_insensitive(filename + *ext), false); + return spl::make_shared(dependencies.frame_factory, *caspar::find_case_insensitive(filename + *ext), false, length); } @@ -294,7 +312,8 @@ core::draw_frame create_thumbnail(const core::frame_producer_dependencies& depen spl::shared_ptr producer = spl::make_shared( dependencies.frame_factory, *caspar::find_case_insensitive(filename + *ext), - true); + true, + 1); return producer->receive(); } diff --git a/protocol/amcp/AMCPCommand.h b/protocol/amcp/AMCPCommand.h index 0eb9a2855..7c6d56af7 100644 --- a/protocol/amcp/AMCPCommand.h +++ b/protocol/amcp/AMCPCommand.h @@ -95,6 +95,7 @@ namespace amcp { 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) @@ -134,9 +135,17 @@ namespace amcp { 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; } }; }}} diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index a490da5d7..45382b452 100644 --- a/protocol/amcp/AMCPCommandsImpl.cpp +++ b/protocol/amcp/AMCPCommandsImpl.cpp @@ -233,20 +233,33 @@ std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_pt + L"\r\n"; } -std::wstring ListMedia(const spl::shared_ptr& 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_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& cg_registry) +std::wstring ListTemplates(const spl::shared_ptr& 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())) { @@ -871,16 +884,24 @@ std::wstring data_retrieve_command(command_context& ctx) 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())) { @@ -1272,16 +1293,33 @@ std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 lin 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) @@ -1290,25 +1328,71 @@ 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(chroma.threshold) + L" " + + std::wstring(chroma.enable ? L"1 " : L"0 ") + + boost::lexical_cast(chroma.target_hue) + L" " + + boost::lexical_cast(chroma.hue_width) + L" " + + boost::lexical_cast(chroma.min_saturation) + L" " + + boost::lexical_cast(chroma.min_brightness) + L" " + boost::lexical_cast(chroma.softness) + L" " - + boost::lexical_cast(chroma.spill) + L"\r\n"; + + boost::lexical_cast(chroma.spill) + L" " + + boost::lexical_cast(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(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(ctx.parameters.at(1)); - chroma.softness = boost::lexical_cast(ctx.parameters.at(2)); - chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast(ctx.parameters.at(3)) : 0.0; + + duration = ctx.parameters.size() > 4 ? boost::lexical_cast(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(ctx.parameters.at(1)) * 0.5; + chroma.min_brightness = boost::lexical_cast(ctx.parameters.at(1)); + chroma.min_saturation = boost::lexical_cast(ctx.parameters.at(1)); + chroma.softness = boost::lexical_cast(ctx.parameters.at(2)) - boost::lexical_cast(ctx.parameters.at(1)); + chroma.spill = boost::lexical_cast(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(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(ctx.parameters.at(1)); + chroma.hue_width = boost::lexical_cast(ctx.parameters.at(2)); + chroma.min_saturation = boost::lexical_cast(ctx.parameters.at(3)); + chroma.min_brightness = boost::lexical_cast(ctx.parameters.at(4)); + chroma.softness = boost::lexical_cast(ctx.parameters.at(5)); + chroma.spill = boost::lexical_cast(ctx.parameters.at(6)); + chroma.spill_darken = boost::lexical_cast(ctx.parameters.at(7)); + chroma.show_mask = boost::lexical_cast(ctx.parameters.at(8)); + } + } + transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform { @@ -2083,9 +2167,12 @@ std::wstring channel_grid_command(command_context& ctx) 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" @@ -2096,10 +2183,15 @@ void thumbnail_list_describer(core::help_sink& sink, const core::help_repository 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())) { @@ -2204,15 +2296,16 @@ void cinf_describer(core::help_sink& sink, const core::help_repository& repo) 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); } @@ -2229,18 +2322,26 @@ std::wstring cinf_command(command_context& ctx) 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()); } @@ -2270,19 +2371,27 @@ std::wstring fls_command(command_context& ctx) 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(); @@ -2885,6 +2994,21 @@ std::wstring lock_command(command_context& ctx) 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); @@ -2971,6 +3095,8 @@ void register_commands(amcp_command_repository& repo) 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 diff --git a/protocol/amcp/AMCPProtocolStrategy.cpp b/protocol/amcp/AMCPProtocolStrategy.cpp index 871f25c91..6009b3e93 100644 --- a/protocol/amcp/AMCPProtocolStrategy.cpp +++ b/protocol/amcp/AMCPProtocolStrategy.cpp @@ -19,7 +19,7 @@ * Author: Nicklas P Andersson */ - + #include "../StdAfx.h" #include "AMCPProtocolStrategy.h" @@ -106,7 +106,7 @@ public: 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)) { @@ -115,7 +115,7 @@ public: else result.queue->AddCommand(result.command); } - + if (result.error != error_state::no_error) { std::wstringstream answer; @@ -157,6 +157,22 @@ private: 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()) { @@ -234,6 +250,9 @@ private: 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&) { diff --git a/protocol/amcp/amcp_command_repository.cpp b/protocol/amcp/amcp_command_repository.cpp index 06249814d..2457ec72d 100644 --- a/protocol/amcp/amcp_command_repository.cpp +++ b/protocol/amcp/amcp_command_repository.cpp @@ -225,4 +225,9 @@ void amcp_command_repository::register_channel_command( self.channel_commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params))); } +spl::shared_ptr amcp_command_repository::help_repo() const +{ + return impl_->help_repo; +} + }}} diff --git a/protocol/amcp/amcp_command_repository.h b/protocol/amcp/amcp_command_repository.h index 778c99845..2cd065fbb 100644 --- a/protocol/amcp/amcp_command_repository.h +++ b/protocol/amcp/amcp_command_repository.h @@ -60,6 +60,7 @@ public: 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 help_repo() const; private: struct impl; spl::shared_ptr impl_; diff --git a/shell/casparcg.config b/shell/casparcg.config index d9973b14a..173442485 100644 --- a/shell/casparcg.config +++ b/shell/casparcg.config @@ -41,7 +41,7 @@ false [true|false] false [true|false] - false [true|false] + false [true|false] false [true|false] auto [cpu|gpu|auto] diff --git a/shell/main.cpp b/shell/main.cpp index 7db474e7e..b90bd4001 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -118,9 +118,8 @@ void do_run( 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")) {