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