]> git.sesse.net Git - casparcg/commitdiff
Merge pull request #519 from krzyc/patch-1
authorHellGore <helge.norberg@gmail.com>
Mon, 30 Jan 2017 15:32:27 +0000 (16:32 +0100)
committerGitHub <noreply@github.com>
Mon, 30 Jan 2017 15:32:27 +0000 (16:32 +0100)
Remove platform name from linux build script

36 files changed:
CHANGELOG
accelerator/ogl/image/blending_glsl.h
accelerator/ogl/image/image_kernel.cpp
accelerator/ogl/image/image_mixer.cpp
accelerator/ogl/image/image_shader.cpp
build-scripts/build-windows.bat
common/filesystem.cpp
core/frame/frame_transform.cpp
core/frame/frame_transform.h
core/producer/framerate/framerate_producer.cpp
core/producer/scene/scene_producer.cpp
core/producer/scene/scene_producer.h
core/producer/scene/xml_scene_producer.cpp
dependencies64/ffmpeg/ffmpeg-build-linux/.gitignore [new file with mode: 0644]
dependencies64/ffmpeg/ffmpeg-build-linux/LICENSE [new file with mode: 0644]
dependencies64/ffmpeg/ffmpeg-build-linux/README.md [new file with mode: 0644]
dependencies64/ffmpeg/ffmpeg-build-linux/build-ubuntu.sh [new file with mode: 0755]
dependencies64/ffmpeg/ffmpeg-build-linux/build.sh [new file with mode: 0755]
dependencies64/ffmpeg/ffmpeg-build-linux/env.source [new file with mode: 0644]
dependencies64/ffmpeg/ffmpeg-build-linux/fetchurl [new file with mode: 0755]
deploy/linux/run.sh
modules/decklink/consumer/decklink_consumer.cpp
modules/ffmpeg/consumer/ffmpeg_consumer.cpp
modules/ffmpeg/producer/ffmpeg_producer.cpp
modules/ffmpeg/producer/input/input.cpp
modules/ffmpeg/producer/input/input.h
modules/ffmpeg/producer/video/video_decoder.cpp
modules/ffmpeg/producer/video/video_decoder.h
modules/html/producer/html_producer.cpp
modules/image/producer/image_producer.cpp
protocol/amcp/AMCPCommand.h
protocol/amcp/AMCPCommandsImpl.cpp
protocol/amcp/AMCPProtocolStrategy.cpp
protocol/amcp/amcp_command_repository.cpp
protocol/amcp/amcp_command_repository.h
shell/main.cpp

index 24901f20673a9d1070c1707d507b6c21ec8fce37..630a12148748762f10ba7f39d376d5be3e8a3b87 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,11 @@ General
     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
@@ -27,6 +32,9 @@ Consumers
     + 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
@@ -34,18 +42,44 @@ Producers
   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
+    + Added support for IN and OUT parameters (Dimitry Ishenko).\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
+  o Fixed bug where glReadPixels() was done from the last drawn to texture\r
+    instead of always from the target texture. This means that for example a\r
+    MIXER KEYER layer without a layer above to key, as well as a separate alpha\r
+    file with MIXER OPACITY 0 now works as expected.\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
index 2dc65f4ccf69b1ecff6704b48019d7084245dbd5..7f3429d7f68dfa4435d56b4cb22519b1b9ed3bfd 100644 (file)
@@ -281,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";
 
index b10accd133e0ecfe1778e6cce55bfcbd77567528..37bb44ff7552bab1d0f240de2cba5f1c49a54f46 100644 (file)
@@ -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<device> ogl_;
        spl::shared_ptr<shader> 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<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);
@@ -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<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);
@@ -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<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;
 
@@ -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<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();
@@ -451,7 +457,7 @@ struct image_kernel::impl
                default:
                        break;
                }
-               
+
                // Cleanup
                GL(glDisable(GL_SCISSOR_TEST));
                GL(glDisable(GL_POLYGON_STIPPLE));
index 19d0191e5fef9a23f5a70e542d24a02806f2d776..5a7f157332d85ca0299f3b5b7b890cb424612098 100644 (file)
@@ -50,7 +50,7 @@
 #include <vector>
 
 namespace caspar { namespace accelerator { namespace ogl {
-               
+
 typedef std::shared_future<std::shared_ptr<texture>> future_texture;
 
 struct item
@@ -91,9 +91,9 @@ public:
                , kernel_(ogl_, blend_modes_wanted, straight_alpha_wanted)
        {
        }
-       
+
        std::future<array<const std::uint8_t>> operator()(std::vector<layer> layers, const core::video_format_desc& format_desc, bool straighten_alpha)
-       {       
+       {
                if(layers.empty())
                { // Bypass GPU with empty frame.
                        static const cache_aligned_vector<uint8_t> buffer(get_max_video_format_size(), 0);
@@ -133,14 +133,16 @@ public:
 
                        kernel_.post_process(target_texture, straighten_alpha);
 
+                       target_texture->attach();
+
                        return ogl_->copy_async(target_texture);
                }));
        }
 
-private:       
-       
-       void draw(spl::shared_ptr<texture>&                     target_texture, 
-                         std::vector<layer>                            layers, 
+private:
+
+       void draw(spl::shared_ptr<texture>&                     target_texture,
+                         std::vector<layer>                            layers,
                          const core::video_format_desc&        format_desc,
                          core::field_mode                                      field_mode)
        {
@@ -154,25 +156,25 @@ private:
        }
 
        void draw(spl::shared_ptr<texture>&                     target_texture,
-                         layer                                                         layer, 
+                         layer                                                         layer,
                          std::shared_ptr<texture>&                     layer_key_texture,
                          const core::video_format_desc&        format_desc,
                          core::field_mode                                      field_mode)
-       {                       
-               // REMOVED: This is done in frame_muxer. 
+       {
+               // REMOVED: This is done in frame_muxer.
                // Fix frames
-               //BOOST_FOREACH(auto& item, layer.items)                
+               //BOOST_FOREACH(auto& item, layer.items)
                //{
                        //if(std::abs(item.transform.fill_scale[1]-1.0) > 1.0/target_texture->height() ||
-                       //   std::abs(item.transform.fill_translation[1]) > 1.0/target_texture->height())               
-                       //      CASPAR_LOG(warning) << L"[image_mixer] Frame should be deinterlaced. Send FILTER DEINTERLACE_BOB when creating producer.";      
+                       //   std::abs(item.transform.fill_translation[1]) > 1.0/target_texture->height())
+                       //      CASPAR_LOG(warning) << L"[image_mixer] Frame should be deinterlaced. Send FILTER DEINTERLACE_BOB when creating producer.";
 
                        //if(item.pix_desc.planes.at(0).height == 480) // NTSC DV
                        //{
                        //      item.transform.fill_translation[1] += 2.0/static_cast<double>(format_desc.height);
                        //      item.transform.fill_scale[1] *= 1.0 - 6.0*1.0/static_cast<double>(format_desc.height);
                        //}
-       
+
                        //// Fix field-order if needed
                        //if(item.field_mode == core::field_mode::lower && format_desc.field_mode == core::field_mode::upper)
                        //      item.transform.fill_translation[1] += 1.0/static_cast<double>(format_desc.height);
@@ -181,49 +183,49 @@ private:
                //}
 
                // Mask out fields
-               for (auto& item : layer.items)                          
+               for (auto& item : layer.items)
                        item.transform.field_mode &= field_mode;
-               
+
                // Remove empty items.
                boost::range::remove_erase_if(layer.items, [&](const item& item)
                {
                        return item.transform.field_mode == core::field_mode::empty;
                });
-               
+
                if(layer.items.empty())
                        return;
 
                std::shared_ptr<texture> local_key_texture;
                std::shared_ptr<texture> local_mix_texture;
-                               
+
                if(layer.blend_mode != core::blend_mode::normal)
                {
                        auto layer_texture = ogl_->create_texture(target_texture->width(), target_texture->height(), 4, false);
 
                        for (auto& item : layer.items)
                                draw(layer_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture, format_desc);
-               
-                       draw(layer_texture, std::move(local_mix_texture), core::blend_mode::normal);                                                    
+
+                       draw(layer_texture, std::move(local_mix_texture), core::blend_mode::normal);
                        draw(target_texture, std::move(layer_texture), layer.blend_mode);
                }
                else // fast path
                {
-                       for (auto& item : layer.items)          
+                       for (auto& item : layer.items)
                                draw(target_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture, format_desc);
-                                       
+
                        draw(target_texture, std::move(local_mix_texture), core::blend_mode::normal);
-               }                                       
+               }
 
                layer_key_texture = std::move(local_key_texture);
        }
 
-       void draw(spl::shared_ptr<texture>& target_texture, 
-                         item item, 
-                     std::shared_ptr<texture>& layer_key_texture, 
-                         std::shared_ptr<texture>& local_key_texture, 
+       void draw(spl::shared_ptr<texture>& target_texture,
+                         item item,
+                     std::shared_ptr<texture>& layer_key_texture,
+                         std::shared_ptr<texture>& local_key_texture,
                          std::shared_ptr<texture>& local_mix_texture,
                          const core::video_format_desc& format_desc)
-       {                       
+       {
                draw_params draw_params;
                draw_params.pix_desc            = std::move(item.pix_desc);
                draw_params.transform           = std::move(item.transform);
@@ -258,17 +260,17 @@ private:
                else
                {
                        draw(target_texture, std::move(local_mix_texture), core::blend_mode::normal);
-                       
+
                        draw_params.background  = target_texture;
                        draw_params.local_key   = std::move(local_key_texture);
                        draw_params.layer_key   = layer_key_texture;
 
                        kernel_.draw(std::move(draw_params));
-               }       
+               }
        }
 
-       void draw(spl::shared_ptr<texture>&      target_texture, 
-                         std::shared_ptr<texture>&& source_buffer, 
+       void draw(spl::shared_ptr<texture>&      target_texture,
+                         std::shared_ptr<texture>&& source_buffer,
                          core::blend_mode                       blend_mode = core::blend_mode::normal)
        {
                if(!source_buffer)
@@ -286,9 +288,9 @@ private:
                kernel_.draw(std::move(draw_params));
        }
 };
-               
+
 struct image_mixer::impl : public core::frame_factory
-{      
+{
        spl::shared_ptr<device>                         ogl_;
        image_renderer                                          renderer_;
        std::vector<core::image_transform>      transform_stack_;
@@ -298,11 +300,11 @@ public:
        impl(const spl::shared_ptr<device>& ogl, bool blend_modes_wanted, bool straight_alpha_wanted, int channel_id)
                : ogl_(ogl)
                , renderer_(ogl, blend_modes_wanted, straight_alpha_wanted)
-               , transform_stack_(1)   
+               , transform_stack_(1)
        {
                CASPAR_LOG(info) << L"Initialized OpenGL Accelerated GPU Image Mixer for channel " << channel_id;
        }
-               
+
        void push(const core::frame_transform& transform)
        {
                auto previous_layer_depth = transform_stack_.back().layer_depth;
@@ -326,9 +328,9 @@ public:
                }
 
        }
-               
+
        void visit(const core::const_frame& frame)
-       {                       
+       {
                if(frame.pixel_format_desc().format == core::pixel_format::invalid)
                        return;
 
@@ -342,11 +344,11 @@ public:
                item.pix_desc   = frame.pixel_format_desc();
                item.transform  = transform_stack_.back();
                item.geometry   = frame.geometry();
-               
+
                // NOTE: Once we have copied the arrays they are no longer valid for reading!!! Check for alternative solution e.g. transfer with AMD_pinned_memory.
                for(int n = 0; n < static_cast<int>(item.pix_desc.planes.size()); ++n)
                        item.textures.push_back(ogl_->copy_async(frame.image_data(n), item.pix_desc.planes[n].width, item.pix_desc.planes[n].height, item.pix_desc.planes[n].stride, item.transform.use_mipmap));
-               
+
                layer_stack_.back()->items.push_back(item);
        }
 
@@ -355,17 +357,17 @@ public:
                transform_stack_.pop_back();
                layer_stack_.resize(transform_stack_.back().layer_depth);
        }
-       
+
        std::future<array<const std::uint8_t>> render(const core::video_format_desc& format_desc, bool straighten_alpha)
        {
                return renderer_(std::move(layers_), format_desc, straighten_alpha);
        }
-       
+
        core::mutable_frame create_frame(const void* tag, const core::pixel_format_desc& desc, const core::audio_channel_layout& channel_layout) override
        {
                std::vector<array<std::uint8_t>> buffers;
-               for (auto& plane : desc.planes)         
-                       buffers.push_back(ogl_->create_array(plane.size));              
+               for (auto& plane : desc.planes)
+                       buffers.push_back(ogl_->create_array(plane.size));
 
                return core::mutable_frame(std::move(buffers), core::mutable_audio_buffer(), tag, desc, channel_layout);
        }
index dfc47a0b2ef6675e79eb1e68129210ebc5032c9c..c4a5ecdc3519fe3d76fb22b699c2faf761ca9b60 100644 (file)
@@ -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<shader> 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<shader> 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);
        }
index 8830d34a9ebfcac38a4be0951bf6f91c10323d80..a1417e442daac6c06cf8f25a873dd7c8baa67cb8 100644 (file)
@@ -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
index d93eb1327f21a691158296c48e6b3028185b70d5..877ac7ca68f6c8310d2488d1c83ad16bf2549d7f 100644 (file)
@@ -33,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)
        {
index 4ba1899202eea6d4daca30d18753c03145785d6f..393214b69aa1326a3c6f7f6d6f27fc30ee770fca 100644 (file)
@@ -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<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 {
index e21e17fb2bd60d276b32ef534381b62d61455ab8..724d35cd1f9b92396f9d01be98227392b718594a 100644 (file)
 #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
@@ -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,7 +155,7 @@ class tweened_transform
        int duration_;
        int time_;
        tweener tweener_;
-public:        
+public:
        tweened_transform()
                : duration_(0)
                , time_(0)
@@ -170,21 +176,20 @@ public:
        {
                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 {
 
index b6141cc0b1762fd87ca1967b63fb6b1a67fb0004..5125ebae3baeb7391775a0fdb6b0de5327dd8d50 100644 (file)
@@ -313,6 +313,9 @@ public:
 
        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));
 
@@ -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<double>(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();
index fd99bb96b299f327fc221c3480f5c5dd37551f39..f5f2a625edf0b0257f329578f703799009d78183 100644 (file)
@@ -149,7 +149,7 @@ struct scene_producer::impl
                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();
@@ -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<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));
@@ -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<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);
 }
index ccfad7fe9c8c2a742272543428754123fa276a64..aca30f6b60fb720b191d178d585e36d19e95815c 100644 (file)
@@ -60,10 +60,14 @@ struct adjustments
 
 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
@@ -195,7 +199,7 @@ public:
                                                        static_cast<double>(duration)));
 
                                        to_affect.set(tweened);
-                                       
+
                                        //CASPAR_LOG(info) << relative_frame << L" " << *start_value << L" " << duration << L" " << tweened;
                                };
 
index 5eacb6c368f92359bdfe35703f16d27b6e57ae4f..49f1ee4d83648aaf1e0800dcbc649f36e24da5dc 100644 (file)
@@ -170,10 +170,14 @@ spl::shared_ptr<core::frame_producer> create_xml_scene_producer(
                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;
@@ -234,7 +238,7 @@ spl::shared_ptr<core::frame_producer> 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/dependencies64/ffmpeg/ffmpeg-build-linux/.gitignore b/dependencies64/ffmpeg/ffmpeg-build-linux/.gitignore
new file mode 100644 (file)
index 0000000..237ede1
--- /dev/null
@@ -0,0 +1,2 @@
+build/
+target/
diff --git a/dependencies64/ffmpeg/ffmpeg-build-linux/LICENSE b/dependencies64/ffmpeg/ffmpeg-build-linux/LICENSE
new file mode 100644 (file)
index 0000000..5b69cfd
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright (c) 2010(s), zimbatm and contributors.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/dependencies64/ffmpeg/ffmpeg-build-linux/README.md b/dependencies64/ffmpeg/ffmpeg-build-linux/README.md
new file mode 100644 (file)
index 0000000..b2835ba
--- /dev/null
@@ -0,0 +1,67 @@
+FFmpeg static build
+===================
+
+Three scripts to make a static build of ffmpeg with all the latest codecs (webm + h264).
+
+Just follow the instructions below. Once you have the build dependencies,
+just run ./build.sh, wait and you should get the ffmpeg binary in target/bin
+
+Build dependencies
+------------------
+
+    # Debian & Ubuntu
+    $ apt-get install build-essential curl tar
+
+       # OS X
+       # install XCode, it can be found at http://developer.apple.com/
+       # (apple login needed)
+       # <FIXME???>
+
+Build & "install"
+-----------------
+
+    $ ./build.sh or build-ubuntu.sh
+    # ... wait ...
+    # binaries can be found in ./target/bin/
+
+NOTE: If you're going to use the h264 presets, make sure to copy them along the binaries. For ease, you can put them in your home folder like this:
+
+    $ mkdir ~/.ffmpeg
+    $ cp ./target/share/ffmpeg/*.ffpreset ~/.ffmpeg
+
+Debug
+-----
+
+On the top-level of the project, run:
+
+       $ . env.source
+       
+You can then enter the source folders and make the compilation yourself
+
+       $ cd build/ffmpeg-*
+       $ ./configure --prefix=$TARGET_DIR #...
+       # ...
+
+Remaining links
+---------------
+
+I'm not sure it's a good idea to statically link those, but it probably
+means the executable won't work across distributions or even across releases.
+
+    # On Ubuntu 10.04:
+    $ ldd ./target/bin/ffmpeg 
+       not a dynamic executable
+
+    # on OSX 10.6.4:
+    $ otool -L ffmpeg 
+       ffmpeg:
+               /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0)
+
+TODO
+----
+
+ * Add some tests to check that video output is correctly generated
+   this would help upgrading the package without too much work
+ * OSX's xvidcore does not detect yasm correctly
+ * remove remaining libs
diff --git a/dependencies64/ffmpeg/ffmpeg-build-linux/build-ubuntu.sh b/dependencies64/ffmpeg/ffmpeg-build-linux/build-ubuntu.sh
new file mode 100755 (executable)
index 0000000..83e9ed5
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+sudo apt-get install build-essential curl tar pkg-config
+
+./build.sh
diff --git a/dependencies64/ffmpeg/ffmpeg-build-linux/build.sh b/dependencies64/ffmpeg/ffmpeg-build-linux/build.sh
new file mode 100755 (executable)
index 0000000..cf40d27
--- /dev/null
@@ -0,0 +1,450 @@
+#!/bin/sh
+
+set -e
+set -u
+
+jflag=
+jval=2
+
+while getopts 'j:' OPTION
+do
+  case $OPTION in
+  j)   jflag=1
+               jval="$OPTARG"
+               ;;
+  ?)   printf "Usage: %s: [-j concurrency_level] (hint: your cores + 20%%)\n" $(basename $0) >&2
+               exit 2
+               ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+if [ "$jflag" ]
+then
+  if [ "$jval" ]
+  then
+    printf "Option -j specified (%d)\n" $jval
+  fi
+fi
+
+cd `dirname $0`
+ENV_ROOT=`pwd`
+. ./env.source
+
+rm -rf "$BUILD_DIR" "$TARGET_DIR"
+mkdir -p "$BUILD_DIR" "$TARGET_DIR"
+
+# NOTE: this is a fetchurl parameter, nothing to do with the current script
+#export TARGET_DIR_DIR="$BUILD_DIR"
+
+echo "#### FFmpeg static build, by STVS SA ####"
+cd $BUILD_DIR
+../fetchurl "http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz"
+../fetchurl "http://zlib.net/zlib-1.2.8.tar.gz"
+../fetchurl "http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz"
+#../fetchurl "http://downloads.sf.net/project/libpng/libpng15/older-releases/1.5.14/libpng-1.5.14.tar.gz"
+../fetchurl "http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.gz"
+../fetchurl "http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.gz"
+../fetchurl "http://downloads.xiph.org/releases/theora/libtheora-1.1.1.tar.bz2"
+../fetchurl "http://storage.googleapis.com/downloads.webmproject.org/releases/webm/libvpx-1.4.0.tar.bz2"
+#../fetchurl "http://downloads.sourceforge.net/project/faac/faac-src/faac-1.28/faac-1.28.tar.bz2"
+../fetchurl "ftp://ftp.videolan.org/pub/x264/snapshots/last_x264.tar.bz2"
+../fetchurl "http://downloads.xvid.org/downloads/xvidcore-1.3.3.tar.gz"
+../fetchurl "http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz"
+../fetchurl "http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz"
+../fetchurl "http://www.freedesktop.org/software/fontconfig/release/fontconfig-2.11.94.tar.bz2"
+../fetchurl "http://www.ffmpeg.org/releases/ffmpeg-2.7.tar.bz2"
+../fetchurl "http://downloads.sourceforge.net/project/expat/expat/2.1.0/expat-2.1.0.tar.gz"
+../fetchurl "ftp://ftp.gnutls.org/gcrypt/gnutls/v3.3/gnutls-3.3.15.tar.xz"
+../fetchurl "https://ftp.gnu.org/gnu/nettle/nettle-2.7.1.tar.gz"
+../fetchurl "https://gmplib.org/download/gmp/gmp-6.0.0a.tar.xz"
+../fetchurl "https://github.com/libass/libass/archive/0.12.2.tar.gz"
+../fetchurl "http://fribidi.org/download/fribidi-0.19.6.tar.bz2"
+../fetchurl "ftp://ftp.videolan.org/pub/videolan/libbluray/0.8.1/libbluray-0.8.1.tar.bz2"
+../fetchurl "http://tukaani.org/xz/xz-5.2.1.tar.xz"
+../fetchurl "http://pkgs.fedoraproject.org/lookaside/pkgs/libgme/game-music-emu-0.6.0.tar.bz2/b98fafb737bc889dc65e7a8b94bd1bf5/game-music-emu-0.6.0.tar.bz2"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/libilbc-20141214-git-ef04ebe.tar.xz"
+../fetchurl "http://downloads.sourceforge.net/project/modplug-xmms/libmodplug/0.8.8.5/libmodplug-0.8.8.5.tar.gz?r=&ts=1440421934&use_mirror=vorboss"
+../fetchurl "http://downloads.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-0.1.3.tar.gz"
+../fetchurl "http://rtmpdump.mplayerhq.hu/download/rtmpdump-2.3.tgz"
+../fetchurl "https://www.openssl.org/source/old/1.0.2/openssl-1.0.2a.tar.gz"
+../fetchurl "http://downloads.sourceforge.net/project/soxr/soxr-0.1.1-Source.tar.xz"
+../fetchurl "https://github.com/georgmartius/vid.stab/archive/release-0.98.tar.gz"
+../fetchurl "http://download.savannah.gnu.org/releases/freetype/freetype-2.5.5.tar.bz2"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/libgsm-1.0.13-4.tar.xz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/openjpeg-1.5.2.tar.xz"
+../fetchurl "http://pkgs.fedoraproject.org/lookaside/pkgs/orc/orc-0.4.18.tar.gz/1a2552e8d127526c48d644fe6437b377/orc-0.4.18.tar.gz"
+../fetchurl "http://ftp.cs.stanford.edu/pub/exim/pcre/pcre-8.36.tar.bz2"
+../fetchurl "http://78.108.103.11/MIRROR/ftp/png/src/history/libpng12/libpng-1.2.53.tar.bz2"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/schroedinger-1.0.11.tar.xz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/speex-1.2rc2.tar.xz"
+../fetchurl "http://ftp.gnu.org/gnu/libtasn1/libtasn1-4.5.tar.gz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/twolame-0.3.13.tar.xz"
+../fetchurl "http://www.freedesktop.org/software/vaapi/releases/libva/libva-1.5.1.tar.bz2"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/vo-aacenc-0.1.3.tar.xz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/vo-amrwbenc-0.1.2.tar.xz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/wavpack-4.75.0.tar.xz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/libwebp-0.4.3.tar.xz"
+../fetchurl "http://ffmpeg.zeranoe.com/builds/source/external_libraries/x265-1.7.tar.xz"
+../fetchurl "ftp://xmlsoft.org/libxml2/libxml2-2.9.2.tar.gz"
+
+echo "*** Building libxml2 ***"
+cd $BUILD_DIR/libxml2*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install || echo "Make install failed, continuing"
+
+echo "*** Building x265 ***"
+cd $BUILD_DIR/x265*/source
+cmake .
+make -j $jval
+make DESTDIR=$TARGET_DIR install
+
+echo "*** Building libwebp ***"
+cd $BUILD_DIR/libwebp*
+./configure --prefix=$TARGET_DIR --enable-shared --enable-mmx
+make -j $jval
+make install
+
+echo "*** Building wavpack ***"
+cd $BUILD_DIR/wavpack*
+./configure --prefix=$TARGET_DIR --enable-shared --enable-mmx
+make -j $jval
+make install
+
+echo "*** Building vo-amrwbenc ***"
+cd $BUILD_DIR/vo-amrwbenc*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building vo-aacenc ***"
+cd $BUILD_DIR/vo-aacenc*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libva ***"
+cd $BUILD_DIR/libva*
+./configure --prefix=$TARGET_DIR --enable-shared --enable-drm=no --enable-glx=no --enable-egl=no --enable-wayland=no
+make -j $jval
+make install
+
+echo "*** Building twolame ***"
+cd $BUILD_DIR/twolame*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libtasn1 ***"
+cd $BUILD_DIR/libtasn1*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building speex ***"
+cd $BUILD_DIR/speex*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building orc ***"
+cd $BUILD_DIR/orc*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building schroedinger ***"
+cd $BUILD_DIR/schroedinger*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libpng ***"
+cd $BUILD_DIR/libpng*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building pcre ***"
+cd $BUILD_DIR/pcre*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building openjpeg ***"
+cd $BUILD_DIR/openjpeg*
+./bootstrap.sh
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building freetype ***"
+cd $BUILD_DIR/freetype*
+./configure --prefix=$TARGET_DIR --enable-shared --with-harfbuzz=no
+make -j $jval
+make install
+
+echo "*** Building libgsm ***"
+cd $BUILD_DIR/libgsm*
+sed -i "s#INSTALL_ROOT =#INSTALL_ROOT  = $TARGET_DIR#g" $BUILD_DIR/libgsm*/Makefile
+sed -i 's#NeedFunctionPrototypes=1#NeedFunctionPrototypes=1 -fPIC#g' $BUILD_DIR/libgsm*/Makefile
+sed -i 's#(GSM_INSTALL_ROOT)/inc#(GSM_INSTALL_ROOT)/include#g' $BUILD_DIR/libgsm*/Makefile
+make -j $jval
+make -j lib/libgsm.so
+make install prefix=$TARGET_DIR
+cp lib/libgsm.so* $TARGET_DIR/lib/
+
+echo "*** Building xavs ***"
+cd $BUILD_DIR/
+svn checkout http://svn.code.sf.net/p/xavs/code/trunk xavs-code
+cd xavs-code
+./configure --prefix=$TARGET_DIR --enable-shared --disable-asm
+make -j $jval
+make install
+
+echo "*** Building vid.stab ***"
+cd $BUILD_DIR/vid.stab*
+cmake .
+make -j $jval
+make DESTDIR=$TARGET_DIR install
+
+echo "*** Building soxr ***"
+cd $BUILD_DIR/soxr*
+cmake -DWITH_OPENMP=off .
+make -j $jval
+make DESTDIR=$TARGET_DIR install
+
+echo "*** Building openssl ***"
+cd $BUILD_DIR/openssl*
+./config --prefix=$TARGET_DIR shared
+make
+make install
+
+echo "*** Building rtmpdump ***"
+cd $BUILD_DIR/rtmpdump*
+sed -i 's#LIB_OPENSSL=-lssl -lcrypto#LIB_OPENSSL=-lssl -lcrypto -ldl#g' $BUILD_DIR/rtmpdump*/Makefile
+make INC=-I$TARGET_DIR/include LDFLAGS=-L$TARGET_DIR/lib  -j $jval SYS=posix
+make install prefix=$TARGET_DIR
+
+echo "*** Building opencore-amr ***"
+cd $BUILD_DIR/opencore-amr*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libmodplug ***"
+cd $BUILD_DIR/libmodplug*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libilbc ***"
+cd $BUILD_DIR/libilbc*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building game-music-emu ***"
+cd $BUILD_DIR/game-music-emu*
+cmake .
+make -j $jval
+make DESTDIR=$TARGET_DIR install
+
+echo "*** Building xz ***"
+cd $BUILD_DIR/xz*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libbluray ***"
+cd $BUILD_DIR/libbluray*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+sed -i 's#Libs.private: -ldl -lxml2   -lfreetype#Libs.private: -ldl -lxml2   -lfreetype -llzma#g' $TARGET_DIR/lib/pkgconfig/libbluray.pc
+
+echo "*** Building fribidi ***"
+cd $BUILD_DIR/fribidi*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libass ***"
+cd $BUILD_DIR/libass*
+./autogen.sh
+./configure --prefix=$TARGET_DIR --enable-shared --disable-harfbuzz --disable-enca
+make -j $jval
+make install
+
+echo "*** Building expat ***"
+cd $BUILD_DIR/expat*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building fontconfig ***"
+cd $BUILD_DIR/fontconfig*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building gmp ***"
+cd $BUILD_DIR/gmp*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building nettle ***"
+cd $BUILD_DIR/nettle*
+sed -i 's#testsuite##g' Makefile.in
+sed -i 's#examples##g' Makefile.in
+./configure --prefix=$TARGET_DIR --enable-shared --enable-mini-gmp
+make -j $jval
+make install
+
+echo "*** Building gnutls ***"
+cd $BUILD_DIR/gnutls*
+./configure --prefix=$TARGET_DIR --enable-shared --disable-doc --without-p11-kit --disable-libdane --disable-cxx
+make -j $jval
+make install
+
+echo "*** Building yasm ***"
+cd $BUILD_DIR/yasm*
+./configure --prefix=$TARGET_DIR
+make -j $jval
+make install
+
+echo "*** Building zlib ***"
+cd $BUILD_DIR/zlib*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building bzip2 ***"
+cd $BUILD_DIR/bzip2*
+make -f Makefile-libbz2_so
+make install PREFIX=$TARGET_DIR
+
+#echo "*** Building libpng ***"
+#cd $BUILD_DIR/libpng*
+#./configure --prefix=$TARGET_DIR --enable-static --disable-shared
+#make -j $jval
+#make install
+
+# Ogg before vorbis
+echo "*** Building libogg ***"
+cd $BUILD_DIR/libogg*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+# Vorbis before theora
+echo "*** Building libvorbis ***"
+cd $BUILD_DIR/libvorbis*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building libtheora ***"
+cd $BUILD_DIR/libtheora*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building livpx ***"
+cd $BUILD_DIR/libvpx*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+#echo "*** Building faac ***"
+#cd $BUILD_DIR/faac*
+#./configure --prefix=$TARGET_DIR --enable-static --disable-shared
+# FIXME: gcc incompatibility, does not work with log()
+
+#sed -i -e "s|^char \*strcasestr.*|//\0|" common/mp4v2/mpeg4ip.h
+#make -j $jval
+#make install
+
+echo "*** Building x264 ***"
+cd $BUILD_DIR/x264*
+./configure --prefix=$TARGET_DIR --enable-shared --disable-opencl
+make -j $jval
+make install
+
+echo "*** Building xvidcore ***"
+cd "$BUILD_DIR/xvidcore/build/generic"
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+#rm $TARGET_DIR/lib/libxvidcore.so.*
+
+echo "*** Building lame ***"
+cd $BUILD_DIR/lame*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+echo "*** Building opus ***"
+cd $BUILD_DIR/opus*
+./configure --prefix=$TARGET_DIR --enable-shared
+make -j $jval
+make install
+
+rm -f $TARGET_DIR/lib/*.a
+rm -f $TARGET_DIR/usr/local/lib/*.a
+
+# FFMpeg
+echo "*** Building FFmpeg ***"
+cd $BUILD_DIR/ffmpeg*
+CFLAGS="-I$TARGET_DIR/include -I$TARGET_DIR/usr/local/include" LDFLAGS="-L$TARGET_DIR/lib -L$TARGET_DIR/usr/local/lib -lm" PKG_CONFIG_PATH=$TARGET_DIR/lib/pkgconfig:$TARGET_DIR/usr/local/lib/pkgconfig ./configure \
+       --prefix=${OUTPUT_DIR:-$TARGET_DIR} \
+       --extra-cflags="-I$TARGET_DIR/include -fexceptions -fnon-call-exceptions -fPIC" \
+       --extra-ldflags="-L$TARGET_DIR/lib -lm -llzma" \
+       --disable-doc \
+       --disable-ffplay \
+       --disable-ffserver \
+       --disable-stripping \
+       --enable-shared \
+       --enable-avisynth \
+       --enable-bzlib \
+       --enable-fontconfig \
+       --enable-frei0r \
+       --enable-gnutls \
+       --enable-gpl \
+       --enable-iconv \
+       --enable-libass \
+       --enable-libbluray \
+       --enable-libfreetype \
+       --enable-libgme \
+       --enable-libgsm \
+       --enable-libilbc \
+       --enable-libmodplug \
+       --enable-libmp3lame \
+       --enable-libopencore-amrnb \
+       --enable-libopencore-amrwb \
+       --enable-libopenjpeg \
+       --enable-libopus \
+       --enable-librtmp \
+       --enable-libschroedinger \
+       --enable-libsoxr \
+       --enable-libspeex \
+       --enable-libtheora \
+       --enable-libtwolame \
+       --enable-libvidstab \
+       --enable-libvo-aacenc \
+       --enable-libvo-amrwbenc \
+       --enable-libvorbis \
+       --enable-libvpx \
+       --enable-libwavpack \
+       --enable-libwebp \
+       --enable-libx264 \
+       --enable-libx265 \
+       --enable-libxavs \
+       --enable-libxvid \
+       --enable-pthreads \
+       --enable-version3 \
+       --enable-libv4l2 \
+       --enable-zlib
+make -j $jval && make install
diff --git a/dependencies64/ffmpeg/ffmpeg-build-linux/env.source b/dependencies64/ffmpeg/ffmpeg-build-linux/env.source
new file mode 100644 (file)
index 0000000..b08d880
--- /dev/null
@@ -0,0 +1,28 @@
+# Source this shell script to get the same environment as the build script
+
+if [ -z "$ENV_ROOT" ]; then
+  if [ -f "./env.source" ]; then
+    ENV_ROOT=`pwd`
+    export ENV_ROOT
+  fi
+fi
+
+if [ -z "$ENV_ROOT" ]; then
+  echo "Missing ENV_ROOT variable" >&2
+elif [ "${ENV_ROOT#/}" = "$ENV_ROOT" ]; then
+  echo "ENV_ROOT must be an absolute path" >&2
+else
+
+  BUILD_DIR="${BUILD_DIR:-$ENV_ROOT/build}"
+  TARGET_DIR="${TARGET_DIR:-$ENV_ROOT/target}"
+
+  export LDFLAGS="-L${TARGET_DIR}/lib"
+  # FIXME: detect OS somehow
+  export DYLD_LIBRARY_PATH="${TARGET_DIR}/lib"
+  export PKG_CONFIG_PATH="$TARGET_DIR/lib/pkgconfig:$TARGET_DIR/lib64/pkgconfig:$TARGET_DIR/usr/local/lib/pkgconfig"
+  #export CFLAGS="-I${TARGET_DIR}/include $LDFLAGS -static-libgcc -Wl,-Bstatic -lc"
+  export CFLAGS="-I${TARGET_DIR}/include $LDFLAGS"
+  export PATH="${TARGET_DIR}/bin:${PATH}"
+  # Force PATH cache clearing
+  hash -r
+fi
diff --git a/dependencies64/ffmpeg/ffmpeg-build-linux/fetchurl b/dependencies64/ffmpeg/ffmpeg-build-linux/fetchurl
new file mode 100755 (executable)
index 0000000..da6d8e3
--- /dev/null
@@ -0,0 +1,123 @@
+#!/bin/sh
+#
+# Small utility to fetch and unpack archives on the web (with cache)
+#
+# Depends on : curl, tar
+#
+
+set -e
+set +u
+
+# ENV vars, inherited from external
+CACHE=${CACHE:-1}
+UNPACK=${UNPACK:-1}
+VERBOSE=${VERBOSE:-0}
+
+EXTRACT_DIR=${EXTRACT_DIR:-`pwd`}
+if [ -n "$HOME" ]; then
+  CACHE_DIR=${CACHE_DIR:-$HOME/.cache/fetchurl}
+else
+  CACHE_DIR=${CACHE_DIR:-}
+fi
+TMP_DIR=${TMP_DIR:-/tmp}
+
+URL=$1
+
+set -u
+
+stderr () {
+  echo $@ 1>&2
+}
+
+sh () {
+  echo $ $@
+  if [ "$VERBOSE" -ne 0 ]; then
+    $@
+  else
+    $@ >/dev/null 2>&1
+  fi
+}
+
+expand_path() {
+       here=`pwd`
+       cd $1
+       echo `pwd -P`
+       cd "$here"
+}
+
+usage() {
+  echo "Usage: fetchurl url"
+  echo "CACHE=${CACHE}"
+  echo "UNPACK=${UNPACK}"
+  echo "VERBOSE=${VERBOSE}"
+
+  echo "EXTRACT_DIR=${EXTRACT_DIR}"
+  echo "CACHE_DIR=${CACHE_DIR}"
+  echo "TMP_DIR=${TMP_DIR}"
+
+  echo "URL=${URL}"
+  exit 1
+}
+
+if [ -z "$URL" ]; then
+  stderr "ERROR: missing url"
+  usage
+fi
+
+if [ -z "$CACHE_DIR" ] && [ "$CACHE" -ne 0 ]; then
+  stderr "ERROR: missing cache dir"
+  usage
+fi
+
+filename=`basename "$URL" | sed 's/\?.*//'`
+tmp_file="$TMP_DIR/$filename"
+cache_file="$CACHE_DIR/$filename"
+
+mkdir -p "$CACHE_DIR"
+
+# Fetch
+if [ "$CACHE" -eq 0 ] || [ ! -f "$cache_file" ]; then
+  rm -rf "$tmp_file"
+  sh curl -L -o "$tmp_file" "$URL"
+  sh mv "$tmp_file" "$cache_file"
+fi
+
+# TODO: checksums
+
+# Unpack
+if [ "$UNPACK" -ne 0 ]; then
+
+  if [ "$filename" != "${filename%.tar.gz}" ]; then
+    extname=.tar.gz
+  elif [ "$filename" != "${filename%.tgz}" ]; then
+    extname=.tgz
+  elif [ "$filename" != "${filename%.tar.bz2}" ]; then
+    extname=.tar.bz2
+  elif [ "$filename" != "${filename%.tar.xz}" ]; then
+    extname=.tar.xz
+  else
+    stderr extension of $filename is not supported
+    exit 1
+  fi
+
+  target_dir=`expand_path "$EXTRACT_DIR"`
+  mkdir -p "$target_dir"
+  sh cd "$target_dir"
+  
+  case "$extname" in
+    .tar.gz|.tgz)    
+      sh tar xzvf "$cache_file"
+    ;;
+    .tar.bz2)
+      sh tar xjvf "$cache_file"
+    ;;
+    .tar.xz)
+      sh tar xJvf "$cache_file"
+    ;;
+    *)
+      stderr BUG, this should not happen
+      exit 1
+    ;;
+  esac
+fi
+
index 185a73608730ea122f01f63f58f212ae2febe54c..72a66bf16f29dd7f0c7f793d0a02bd5415f996e6 100755 (executable)
@@ -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
 
index ca847727eb8b6849834fdb989763757482749a1f..39dcde7f40804edf19f80e550f301898d39199aa 100644 (file)
@@ -647,10 +647,13 @@ public:
                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
                {
index 25f2b87b01dd695f08b5a9043e21ba7098f2b48e..188fa418878652b424ef1b324ae24d80e87943ec 100644 (file)
@@ -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();
+                       }
                }
        }
 
@@ -1303,6 +1310,15 @@ spl::shared_ptr<core::frame_consumer> 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<ffmpeg_consumer_proxy>(path, args, separate_key, mono_streams, compatibility_mode);
index ac4ae09e0233eef8e42e455f959c3efb07c3bee9..216559156fda5435fbc2ae174aad78257d55ff23 100644 (file)
@@ -95,8 +95,6 @@ struct ffmpeg_producer : public core::frame_producer_base
        std::unique_ptr<frame_muxer>                                            muxer_;
 
        const boost::rational<int>                                                      framerate_;
-       const uint32_t                                                                          start_;
-       const uint32_t                                                                          length_;
        const bool                                                                                      thumbnail_mode_;
 
        core::draw_frame                                                                        last_frame_;
@@ -112,21 +110,18 @@ public:
                        const std::wstring& url_or_file,
                        const std::wstring& filter,
                        bool loop,
-                       uint32_t start,
-                       uint32_t length,
+                       uint32_t in,
+                       uint32_t out,
                        bool thumbnail_mode,
                        const std::wstring& custom_channel_order,
                        const ffmpeg_options& vid_params)
                : filename_(url_or_file)
                , frame_factory_(frame_factory)
                , initial_logger_disabler_(temporary_enable_quiet_logging_for_thread(thumbnail_mode))
-               , input_(graph_, url_or_file, loop, start, length, thumbnail_mode, vid_params)
+               , input_(graph_, url_or_file, loop, in, out, thumbnail_mode, vid_params)
                , framerate_(read_framerate(*input_.context(), format_desc.framerate))
-               , start_(start)
-               , length_(length)
                , thumbnail_mode_(thumbnail_mode)
                , last_frame_(core::draw_frame::empty())
-               , frame_number_(0)
        {
                graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
                graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f));
@@ -212,6 +207,12 @@ public:
                        CASPAR_THROW_EXCEPTION(averror_stream_not_found() << msg_info("No streams found"));
 
                muxer_.reset(new frame_muxer(framerate_, std::move(audio_input_pads), frame_factory, format_desc, channel_layout, filter, true));
+
+               if (auto nb_frames = file_nb_frames())
+               {
+                       out = std::min(out, nb_frames);
+                       input_.out(out);
+               }
        }
 
        // frame_producer
@@ -313,12 +314,12 @@ public:
                                           // therefore no seeking should be necessary for the first frame.
                {
                        input_.seek(file_position > 1 ? file_position - 2: file_position).get();
-            boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
+                       boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
                }
 
                for (int i = 0; i < NUM_RETRIES; ++i)
                {
-            boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
+                       boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
 
                        auto frame = render_frame();
 
@@ -338,7 +339,7 @@ public:
                                {
                                        CASPAR_LOG(trace) << print() << L" adjusting to " << adjusted_seek;
                                        input_.seek(static_cast<uint32_t>(adjusted_seek) - 1).get();
-                    boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
+                                       boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
                                }
                                else
                                        return frame.first;
@@ -405,71 +406,78 @@ public:
                if (is_url() || input_.loop())
                        return std::numeric_limits<uint32_t>::max();
 
-               uint32_t nb_frames = file_nb_frames();
-
-               nb_frames = std::min(length_, nb_frames - start_);
-               nb_frames = muxer_->calc_nb_frames(nb_frames);
+               auto nb_frames = std::min(input_.out(), file_nb_frames());
+               if (nb_frames >= input_.in())
+                       nb_frames -= input_.in();
+               else
+                       nb_frames = 0;
 
-               return nb_frames;
+               return muxer_->calc_nb_frames(nb_frames);
        }
 
        uint32_t file_nb_frames() const
        {
-               uint32_t file_nb_frames = 0;
-               file_nb_frames = std::max(file_nb_frames, video_decoder_ ? video_decoder_->nb_frames() : 0);
-               return file_nb_frames;
+               return video_decoder_ ? video_decoder_->nb_frames() : 0;
        }
 
        std::future<std::wstring> call(const std::vector<std::wstring>& params) override
        {
-               static const boost::wregex loop_exp(LR"(LOOP\s*(?<VALUE>\d?)?)", boost::regex::icase);
-               static const boost::wregex seek_exp(LR"(SEEK\s+(?<VALUE>(\+|-)?\d+)(\s+(?<WHENCE>REL|END))?)", boost::regex::icase);
-               static const boost::wregex length_exp(LR"(LENGTH\s+(?<VALUE>\d+)?)", boost::regex::icase);
-               static const boost::wregex start_exp(LR"(START\s+(?<VALUE>\d+)?)", boost::regex::icase);
-
-               auto param = boost::algorithm::join(params, L" ");
-
                std::wstring result;
 
-               boost::wsmatch what;
-               if(boost::regex_match(param, what, loop_exp))
+               std::wstring cmd = params.at(0);
+               std::wstring value;
+               if (params.size() > 1)
+                       value = params.at(1);
+
+               if (boost::iequals(cmd, L"loop"))
                {
-                       auto value = what["VALUE"].str();
                        if (!value.empty())
                                input_.loop(boost::lexical_cast<bool>(value));
                        result = boost::lexical_cast<std::wstring>(input_.loop());
                }
-               else if(boost::regex_match(param, what, seek_exp))
+               else if (boost::iequals(cmd, L"in") || boost::iequals(cmd, L"start"))
                {
-                       auto value = boost::lexical_cast<int64_t>(what["VALUE"].str());
-                       auto whence = what["WHENCE"].str();
-                       auto total = file_nb_frames();
-
-                       if(boost::iequals(whence, L"REL"))
-                               value = file_frame_number() + value;
-                       else if(boost::iequals(whence, L"END"))
-                               value = total - value;
-
-                       if(value < 0)
-                               value = 0;
-                       else if(value >= total)
-                               value = total - 1;
-
-                       input_.seek(static_cast<uint32_t>(value));
+                       if (!value.empty())
+                               input_.in(boost::lexical_cast<uint32_t>(value));
+                       result = boost::lexical_cast<std::wstring>(input_.in());
+               }
+               else if (boost::iequals(cmd, L"out"))
+               {
+                       if (!value.empty())
+                               input_.out(boost::lexical_cast<uint32_t>(value));
+                       result = boost::lexical_cast<std::wstring>(input_.out());
                }
-               else if(boost::regex_match(param, what, length_exp))
+               else if (boost::iequals(cmd, L"length"))
                {
-                       auto value = what["VALUE"].str();
-                       if(!value.empty())
+                       if (!value.empty())
                                input_.length(boost::lexical_cast<uint32_t>(value));
                        result = boost::lexical_cast<std::wstring>(input_.length());
                }
-               else if(boost::regex_match(param, what, start_exp))
+               else if (boost::iequals(cmd, L"seek") && !value.empty())
                {
-                       auto value = what["VALUE"].str();
-                       if(!value.empty())
-                               input_.start(boost::lexical_cast<uint32_t>(value));
-                       result = boost::lexical_cast<std::wstring>(input_.start());
+                       auto nb_frames = file_nb_frames();
+
+                       int64_t seek;
+                       if (boost::iequals(value, L"rel"))
+                               seek = file_frame_number();
+                       else if (boost::iequals(value, L"in"))
+                               seek = input_.in();
+                       else if (boost::iequals(value, L"out"))
+                               seek = input_.out();
+                       else if (boost::iequals(value, L"end"))
+                               seek = nb_frames;
+                       else
+                               seek = boost::lexical_cast<int64_t>(value);
+
+                       if (params.size() > 2)
+                               seek += boost::lexical_cast<int64_t>(params.at(2));
+
+                       if (seek < 0)
+                               seek = 0;
+                       else if (seek >= nb_frames)
+                               seek = nb_frames - 1;
+
+                       input_.seek(static_cast<uint32_t>(seek));
                }
                else
                        CASPAR_THROW_EXCEPTION(invalid_argument());
@@ -551,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());
                },
                [&]
                {
@@ -591,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
@@ -608,7 +626,7 @@ public:
 void describe_producer(core::help_sink& sink, const core::help_repository& repo)
 {
        sink.short_description(L"A producer for playing media files supported by FFmpeg.");
-       sink.syntax(L"[clip,url:string] {[loop:LOOP]} {SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}");
+       sink.syntax(L"[clip,url:string] {[loop:LOOP]} {IN,SEEK [in:int]} {OUT [out:int] | LENGTH [length:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}");
        sink.para()
                ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ")
                ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ")
@@ -616,19 +634,20 @@ void describe_producer(core::help_sink& sink, const core::help_repository& repo)
        sink.definitions()
                ->item(L"clip", L"The file without the file extension to play. It should reside under the media folder.")
                ->item(L"url", L"If clip contains :// it is instead treated as the URL parameter. The URL can either be any streaming protocol supported by FFmpeg, dshow://video={webcam_name} or v4l2://{video device}.")
-               ->item(L"loop", L"Will cause the media file to loop between start and start + length")
-               ->item(L"start", L"Optionally sets the start frame. 0 by default. If loop is specified this will be the frame where it starts over again.")
-               ->item(L"length", L"Optionally sets the length of the clip. If not specified the clip will be played to the end. If loop is specified the file will jump to start position once this number of frames has been played.")
+               ->item(L"loop", L"Will cause the media file to loop between in and out.")
+               ->item(L"in", L"Optionally sets the first frame. 0 by default. If loop is specified, this will be the frame where it starts over again.")
+               ->item(L"out", L"Optionally sets the last frame. If not specified the clip will be played to the end. If loop is specified, the file will jump to start position once it reaches the last frame.")
+               ->item(L"length", L"Optionally sets the length of the clip. Equivalent to OUT in + length.")
                ->item(L"filter", L"If specified, will be used as an FFmpeg video filter.")
                ->item(L"channel_layout",
-                               L"Optionally override the automatically deduced audio channel layout. "
+                               L"Optionally override the automatically deduced audio channel layout."
                                L"Either a named layout as specified in casparcg.config or in the format [type:string]:[channel_order:string] for a custom layout.");
        sink.para()->text(L"Examples:");
        sink.example(L">> PLAY 1-10 folder/clip", L"to play all frames in a clip and stop at the last frame.");
        sink.example(L">> PLAY 1-10 folder/clip LOOP", L"to loop a clip between the first frame and the last frame.");
-       sink.example(L">> PLAY 1-10 folder/clip LOOP SEEK 10", L"to loop a clip between frame 10 and the last frame.");
-       sink.example(L">> PLAY 1-10 folder/clip LOOP SEEK 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60.");
-       sink.example(L">> PLAY 1-10 folder/clip SEEK 10 LENGTH 50", L"to play frames 10-60 in a clip and stop.");
+       sink.example(L">> PLAY 1-10 folder/clip LOOP IN 10", L"to loop a clip between frame 10 and the last frame.");
+       sink.example(L">> PLAY 1-10 folder/clip LOOP IN 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60.");
+       sink.example(L">> PLAY 1-10 folder/clip IN 10 OUT 60", L"to play frames 10-60 in a clip and stop.");
        sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video.");
        sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT film", L"given the defaults in casparcg.config this will specifies that the clip has 6 audio channels of the type 5.1 and that they are in the order FL FC FR BL BR LFE regardless of what ffmpeg says.");
        sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT \"5.1:LFE FL FC FR BL BR\"", L"specifies that the clip has 6 audio channels of the type 5.1 and that they are in the specified order regardless of what ffmpeg says.");
@@ -637,7 +656,8 @@ void describe_producer(core::help_sink& sink, const core::help_repository& repo)
        sink.example(L">> PLAY 1-10 v4l2:///dev/video0", L"to use a web camera as video input on Linux.");
        sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via ")->code(L"CALL")->text(L":");
        sink.example(L">> CALL 1-10 LOOP 1");
-       sink.example(L">> CALL 1-10 START 10");
+       sink.example(L">> CALL 1-10 IN 10");
+       sink.example(L">> CALL 1-10 OUT 60");
        sink.example(L">> CALL 1-10 LENGTH 50");
        sink.example(L">> CALL 1-10 SEEK 30");
        core::describe_framerate_producer(sink);
@@ -659,9 +679,20 @@ spl::shared_ptr<core::frame_producer> create_producer(
        if (file_or_url.empty())
                return core::frame_producer::empty();
 
+       constexpr auto uint32_max = std::numeric_limits<uint32_t>::max();
+
        auto loop                                       = contains_param(L"LOOP",               params);
-       auto start                                      = get_param(L"SEEK",                    params, static_cast<uint32_t>(0));
-       auto length                                     = get_param(L"LENGTH",                  params, std::numeric_limits<uint32_t>::max());
+
+       auto in                                         = get_param(L"SEEK",                    params, static_cast<uint32_t>(0)); // compatibility
+       in                                                      = get_param(L"IN",                              params, in);
+
+       auto out                                        = get_param(L"LENGTH",                  params, uint32_max);
+       if (out < uint32_max - in)
+               out += in;
+       else
+               out = uint32_max;
+       out                                                     = get_param(L"OUT",                             params, out);
+
        auto filter_str                         = get_param(L"FILTER",                  params, L"");
        auto custom_channel_order       = get_param(L"CHANNEL_LAYOUT",  params, L"");
 
@@ -692,8 +723,8 @@ spl::shared_ptr<core::frame_producer> create_producer(
                        file_or_url,
                        filter_str,
                        loop,
-                       start,
-                       length,
+                       in,
+                       out,
                        false,
                        custom_channel_order,
                        vid_params);
@@ -724,8 +755,8 @@ core::draw_frame create_thumbnail_frame(
                return core::draw_frame::empty();
 
        auto loop               = false;
-       auto start              = 0;
-       auto length             = std::numeric_limits<uint32_t>::max();
+       auto in                 = 0;
+       auto out                = std::numeric_limits<uint32_t>::max();
        auto filter_str = L"";
 
        ffmpeg_options vid_params;
@@ -735,8 +766,8 @@ core::draw_frame create_thumbnail_frame(
                        filename,
                        filter_str,
                        loop,
-                       start,
-                       length,
+                       in,
+                       out,
                        true,
                        L"",
                        vid_params);
index ac772b8a9cdb50077234ea59b03c9ef25ae735b0..db902fd19903ede7b75ff7bf3b34e0091339b311 100644 (file)
 #include <tbb/atomic.h>
 #include <tbb/recursive_mutex.h>
 
-#include <boost/range/algorithm.hpp>
-#include <boost/thread/condition_variable.hpp>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/thread.hpp>
-
 #if defined(_MSC_VER)
 #pragma warning (push)
 #pragma warning (disable : 4244)
@@ -66,7 +61,7 @@ static const size_t MIN_BUFFER_COUNT    = 50;
 static const size_t MAX_BUFFER_SIZE     = 64 * 1000000;
 
 namespace caspar { namespace ffmpeg {
-struct input::implementation : boost::noncopyable
+struct input::impl : boost::noncopyable
 {
        const spl::shared_ptr<diagnostics::graph>                                       graph_;
 
@@ -74,18 +69,18 @@ struct input::implementation : boost::noncopyable
        const int                                                                                                       default_stream_index_   = av_find_default_stream_index(format_context_.get());
 
        const std::wstring                                                                                      filename_;
-       tbb::atomic<uint32_t>                                                                           start_;
-       tbb::atomic<uint32_t>                                                                           length_;
+       tbb::atomic<uint32_t>                                                                           in_;
+       tbb::atomic<uint32_t>                                                                           out_;
        const bool                                                                                                      thumbnail_mode_;
        tbb::atomic<bool>                                                                                       loop_;
-       uint32_t                                                                                                        frame_number_                   = 0;
+       uint32_t                                                                                                        file_frame_number_              = 0;
 
        tbb::concurrent_bounded_queue<std::shared_ptr<AVPacket>>        buffer_;
        tbb::atomic<size_t>                                                                                     buffer_size_;
 
        executor                                                                                                        executor_;
 
-       explicit implementation(const spl::shared_ptr<diagnostics::graph> graph, const std::wstring& url_or_file, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params)
+       explicit impl(const spl::shared_ptr<diagnostics::graph> graph, const std::wstring& url_or_file, bool loop, uint32_t in, uint32_t out, bool thumbnail_mode, const ffmpeg_options& vid_params)
                : graph_(graph)
                , format_context_(open_input(url_or_file, vid_params))
                , filename_(url_or_file)
@@ -98,13 +93,13 @@ struct input::implementation : boost::noncopyable
                                enable_quiet_logging_for_thread();
                        });
 
-               start_                  = start;
-               length_                 = length;
+               in_                             = in;
+               out_                    = out;
                loop_                   = loop;
                buffer_size_    = 0;
 
-               if(start_ > 0)
-                       queued_seek(start_);
+               if(in_ > 0)
+                       queued_seek(in_);
 
                graph_->set_color("seek", diagnostics::color(1.0f, 0.5f, 0.0f));
                graph_->set_color("buffer-count", diagnostics::color(0.7f, 0.4f, 0.4f));
@@ -187,11 +182,11 @@ struct input::implementation : boost::noncopyable
 
                                if(is_eof(ret))
                                {
-                                       frame_number_   = 0;
+                                       file_frame_number_ = 0;
 
                                        if(loop_)
                                        {
-                                               queued_seek(start_);
+                                               queued_seek(in_);
                                                graph_->set_tag(diagnostics::tag_severity::INFO, "seek");
                                                CASPAR_LOG(trace) << print() << " Looping.";
                                        }
@@ -213,7 +208,7 @@ struct input::implementation : boost::noncopyable
                                        THROW_ON_ERROR(ret, "av_read_frame", print());
 
                                        if(packet->stream_index == default_stream_index_)
-                                               ++frame_number_;
+                                               ++file_frame_number_;
 
                                        THROW_ON_ERROR2(av_dup_packet(packet.get()), print());
 
@@ -354,7 +349,6 @@ struct input::implementation : boost::noncopyable
 
                auto stream = format_context_->streams[default_stream_index_];
 
-
                auto fps = read_fps(*format_context_, 0.0);
 
                THROW_ON_ERROR2(avformat_seek_file(
@@ -365,6 +359,8 @@ struct input::implementation : boost::noncopyable
                        std::numeric_limits<int64_t>::max(),
                        0), print());
 
+               file_frame_number_ = target;
+
                auto flush_packet       = create_packet();
                flush_packet->data      = nullptr;
                flush_packet->size      = 0;
@@ -380,7 +376,7 @@ struct input::implementation : boost::noncopyable
                if(ret == AVERROR_EOF)
                        CASPAR_LOG(trace) << print() << " Received EOF. ";
 
-               return ret == AVERROR_EOF || ret == AVERROR(EIO) || frame_number_ >= length_; // av_read_frame doesn't always correctly return AVERROR_EOF;
+               return ret == AVERROR_EOF || ret == AVERROR(EIO) || file_frame_number_ >= out_; // av_read_frame doesn't always correctly return AVERROR_EOF;
        }
 
        int num_audio_streams() const
@@ -389,15 +385,17 @@ struct input::implementation : boost::noncopyable
        }
 };
 
-input::input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& url_or_file, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params)
-       : impl_(new implementation(graph, url_or_file, loop, start, length, thumbnail_mode, vid_params)){}
+input::input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& url_or_file, bool loop, uint32_t in, uint32_t out, bool thumbnail_mode, const ffmpeg_options& vid_params)
+       : impl_(new impl(graph, url_or_file, loop, in, out, thumbnail_mode, vid_params)){}
 bool input::eof() const {return !impl_->executor_.is_running();}
 bool input::try_pop(std::shared_ptr<AVPacket>& packet){return impl_->try_pop(packet);}
 spl::shared_ptr<AVFormatContext> input::context(){return impl_->format_context_;}
-void input::start(uint32_t value){impl_->start_ = value;}
-uint32_t input::start() const{return impl_->start_;}
-void input::length(uint32_t value){impl_->length_ = value;}
-uint32_t input::length() const{return impl_->length_;}
+void input::in(uint32_t value){impl_->in_ = value;}
+uint32_t input::in() const{return impl_->in_;}
+void input::out(uint32_t value){impl_->out_ = value;}
+uint32_t input::out() const{return impl_->out_;}
+void input::length(uint32_t value){impl_->out_ = impl_->in_ + value;}
+uint32_t input::length() const{return impl_->out_ - impl_->in_;}
 void input::loop(bool value){impl_->loop_ = value;}
 bool input::loop() const{return impl_->loop_;}
 int input::num_audio_streams() const { return impl_->num_audio_streams(); }
index e1c70631237ec0be47bab47af2fe7cbd591ba118..4116b77dbbe50714eec9203ffa08d6f16b55df45 100644 (file)
@@ -49,13 +49,15 @@ namespace ffmpeg {
 class input : boost::noncopyable
 {
 public:
-       explicit input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& url_or_file, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params);
+       explicit input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& url_or_file, bool loop, uint32_t in, uint32_t out, bool thumbnail_mode, const ffmpeg_options& vid_params);
 
        bool                                                            try_pop(std::shared_ptr<AVPacket>& packet);
        bool                                                            eof() const;
 
-       void                                                            start(uint32_t value);
-       uint32_t                                                        start() const;
+       void                                                            in(uint32_t value);
+       uint32_t                                                        in() const;
+       void                                                            out(uint32_t value);
+       uint32_t                                                        out() const;
        void                                                            length(uint32_t value);
        uint32_t                                                        length() const;
        void                                                            loop(bool value);
@@ -67,8 +69,8 @@ public:
 
        spl::shared_ptr<AVFormatContext>        context();
 private:
-       struct implementation;
-       std::shared_ptr<implementation> impl_;
+       struct impl;
+       std::shared_ptr<impl> impl_;
 };
 
 
index 9322baad4ba1db6a67669e8df3c184ace5eaef85..8f8e60abfa86c46b701542c7dad8c6f3a9db6f47 100644 (file)
@@ -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<uint32_t>(file_frame_number_));
@@ -163,6 +168,7 @@ video_decoder::video_decoder(const spl::shared_ptr<AVFormatContext>& context) :
 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();}
index d954dc05b05610a1d8b24d26b8cd03b7a75e573a..5947b8ea4b1ef88ea4fc67758d207bb3512a3aad 100644 (file)
@@ -42,11 +42,12 @@ class video_decoder : boost::noncopyable
 {
 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;
 
@@ -61,4 +62,4 @@ private:
        spl::shared_ptr<implementation> impl_;
 };
 
-}}
\ No newline at end of file
+}}
index 78ca18fc7be036b8bfd073925316ea79aa108ef1..241c2f41f158a613a2ac30de96072414338770d4 100644 (file)
@@ -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<core::frame_producer> create_producer(
                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>(
index 112922ac5f68fe450de2feec264398800f94066f..b1857407b9b726f47ea7e6544224468277ba546d 100644 (file)
@@ -40,6 +40,7 @@
 #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>
@@ -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<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_));
 
@@ -94,9 +97,10 @@ struct image_producer : public core::frame_producer_base
                        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));
 
@@ -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<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]"))
        {
@@ -258,7 +276,7 @@ spl::shared_ptr<core::frame_producer> 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<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);
@@ -273,7 +291,7 @@ spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer
        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);
 }
 
 
@@ -294,7 +312,8 @@ core::draw_frame create_thumbnail(const core::frame_producer_dependencies& depen
        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();
 }
index 0eb9a2855bff2297c9d4629aa4e09e1b6c858744..7c6d56af797a3468746c928a1515177e95d23c2b 100644 (file)
@@ -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;
                }
        };
 }}}
index a490da5d714ffb71db4de3eff5a9ac36d1c51e6d..45382b452119b6360bee81a18ec1707e99020ae6 100644 (file)
@@ -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_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()))
                {
@@ -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<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
        {
@@ -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
index 871f25c918d17d7d9133925f28d8e0d43c825028..6009b3e930c73199656270718f288ac327ed0ee2 100644 (file)
@@ -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&)
                {
index 06249814def23a49a9bbaed5bc2229f9c7d68b3f..2457ec72d5b0f847897fe5b204ef65b53f02e27c 100644 (file)
@@ -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<core::help_repository> amcp_command_repository::help_repo() const
+{
+       return impl_->help_repo;
+}
+
 }}}
index 778c99845dcf1e20d420255d94f316d30fc3a364..2cd065fbba5a09be25eb796a2cc49537d68ce6d3 100644 (file)
@@ -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<core::help_repository> help_repo() const;
 private:
        struct impl;
        spl::shared_ptr<impl> impl_;
index 7db474e7e21f990dcbc663cd2b3788f5f9b069cd..b90bd40018219abba98dc17097a27fa220b08756 100644 (file)
@@ -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"))
                {