]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/producer/ffmpeg_producer.cpp
[ffmpeg_producer] Add support for IN and OUT parameters in PLAY, CALL and SEEK commands
[casparcg] / modules / ffmpeg / producer / ffmpeg_producer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../StdAfx.h"
23
24 #include "ffmpeg_producer.h"
25
26 #include "../ffmpeg.h"
27 #include "../ffmpeg_error.h"
28 #include "util/util.h"
29 #include "input/input.h"
30 #include "audio/audio_decoder.h"
31 #include "video/video_decoder.h"
32 #include "muxer/frame_muxer.h"
33 #include "filter/audio_filter.h"
34
35 #include <common/param.h>
36 #include <common/diagnostics/graph.h>
37 #include <common/future.h>
38
39 #include <core/frame/draw_frame.h>
40 #include <core/help/help_repository.h>
41 #include <core/help/help_sink.h>
42 #include <core/producer/media_info/media_info.h>
43 #include <core/producer/framerate/framerate_producer.h>
44 #include <core/frame/frame_factory.h>
45
46 #include <future>
47 #include <queue>
48
49 namespace caspar { namespace ffmpeg {
50 struct seek_out_of_range : virtual user_error {};
51
52 std::wstring get_relative_or_original(
53                 const std::wstring& filename,
54                 const boost::filesystem::path& relative_to)
55 {
56         boost::filesystem::path file(filename);
57         auto result = file.filename().wstring();
58
59         boost::filesystem::path current_path = file;
60
61         while (true)
62         {
63                 current_path = current_path.parent_path();
64
65                 if (boost::filesystem::equivalent(current_path, relative_to))
66                         break;
67
68                 if (current_path.empty())
69                         return filename;
70
71                 result = current_path.filename().wstring() + L"/" + result;
72         }
73
74         return result;
75 }
76
77 struct ffmpeg_producer : public core::frame_producer_base
78 {
79         spl::shared_ptr<core::monitor::subject>                         monitor_subject_;
80         const std::wstring                                                                      filename_;
81         const std::wstring                                                                      path_relative_to_media_         = get_relative_or_original(filename_, env::media_folder());
82
83         const spl::shared_ptr<diagnostics::graph>                       graph_;
84         timer                                                                                           frame_timer_;
85
86         const spl::shared_ptr<core::frame_factory>                      frame_factory_;
87
88         std::shared_ptr<void>                                                           initial_logger_disabler_;
89
90         core::constraints                                                                       constraints_;
91
92         input                                                                                           input_;
93         std::unique_ptr<video_decoder>                                          video_decoder_;
94         std::vector<std::unique_ptr<audio_decoder>>                     audio_decoders_;
95         std::unique_ptr<frame_muxer>                                            muxer_;
96
97         const boost::rational<int>                                                      framerate_;
98         const bool                                                                                      thumbnail_mode_;
99
100         core::draw_frame                                                                        last_frame_;
101
102         std::queue<std::pair<core::draw_frame, uint32_t>>       frame_buffer_;
103
104         int64_t                                                                                         frame_number_                           = 0;
105         uint32_t                                                                                        file_frame_number_                      = 0;
106 public:
107         explicit ffmpeg_producer(
108                         const spl::shared_ptr<core::frame_factory>& frame_factory,
109                         const core::video_format_desc& format_desc,
110                         const std::wstring& url_or_file,
111                         const std::wstring& filter,
112                         bool loop,
113                         uint32_t in,
114                         uint32_t out,
115                         bool thumbnail_mode,
116                         const std::wstring& custom_channel_order,
117                         const ffmpeg_options& vid_params)
118                 : filename_(url_or_file)
119                 , frame_factory_(frame_factory)
120                 , initial_logger_disabler_(temporary_enable_quiet_logging_for_thread(thumbnail_mode))
121                 , input_(graph_, url_or_file, loop, in, out, thumbnail_mode, vid_params)
122                 , framerate_(read_framerate(*input_.context(), format_desc.framerate))
123                 , thumbnail_mode_(thumbnail_mode)
124                 , last_frame_(core::draw_frame::empty())
125         {
126                 graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
127                 graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f));
128                 diagnostics::register_graph(graph_);
129
130                 try
131                 {
132                         video_decoder_.reset(new video_decoder(input_.context()));
133                         if (!thumbnail_mode_)
134                                 CASPAR_LOG(info) << print() << L" " << video_decoder_->print();
135
136                         constraints_.width.set(video_decoder_->width());
137                         constraints_.height.set(video_decoder_->height());
138                 }
139                 catch (averror_stream_not_found&)
140                 {
141                         //CASPAR_LOG(warning) << print() << " No video-stream found. Running without video.";
142                 }
143                 catch (...)
144                 {
145                         if (!thumbnail_mode_)
146                         {
147                                 CASPAR_LOG_CURRENT_EXCEPTION();
148                                 CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video.";
149                         }
150                 }
151
152                 auto channel_layout = core::audio_channel_layout::invalid();
153                 std::vector<audio_input_pad> audio_input_pads;
154
155                 if (!thumbnail_mode_)
156                 {
157                         for (unsigned stream_index = 0; stream_index < input_.context()->nb_streams; ++stream_index)
158                         {
159                                 auto stream = input_.context()->streams[stream_index];
160
161                                 if (stream->codec->codec_type != AVMediaType::AVMEDIA_TYPE_AUDIO)
162                                         continue;
163
164                                 try
165                                 {
166                                         audio_decoders_.push_back(std::unique_ptr<audio_decoder>(new audio_decoder(stream_index, input_.context(), format_desc.audio_sample_rate)));
167                                         audio_input_pads.emplace_back(
168                                                         boost::rational<int>(1, format_desc.audio_sample_rate),
169                                                         format_desc.audio_sample_rate,
170                                                         AVSampleFormat::AV_SAMPLE_FMT_S32,
171                                                         audio_decoders_.back()->ffmpeg_channel_layout());
172                                         CASPAR_LOG(info) << print() << L" " << audio_decoders_.back()->print();
173                                 }
174                                 catch (averror_stream_not_found&)
175                                 {
176                                         //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio.";
177                                 }
178                                 catch (...)
179                                 {
180                                         CASPAR_LOG_CURRENT_EXCEPTION();
181                                         CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";
182                                 }
183                         }
184
185                         if (audio_decoders_.size() == 1)
186                         {
187                                 channel_layout = get_audio_channel_layout(
188                                                 audio_decoders_.at(0)->num_channels(),
189                                                 audio_decoders_.at(0)->ffmpeg_channel_layout(),
190                                                 custom_channel_order);
191                         }
192                         else if (audio_decoders_.size() > 1)
193                         {
194                                 auto num_channels = cpplinq::from(audio_decoders_)
195                                         .select(std::mem_fn(&audio_decoder::num_channels))
196                                         .aggregate(0, std::plus<int>());
197                                 auto ffmpeg_channel_layout = av_get_default_channel_layout(num_channels);
198
199                                 channel_layout = get_audio_channel_layout(
200                                                 num_channels,
201                                                 ffmpeg_channel_layout,
202                                                 custom_channel_order);
203                         }
204                 }
205
206                 if (!video_decoder_ && audio_decoders_.empty())
207                         CASPAR_THROW_EXCEPTION(averror_stream_not_found() << msg_info("No streams found"));
208
209                 muxer_.reset(new frame_muxer(framerate_, std::move(audio_input_pads), frame_factory, format_desc, channel_layout, filter, true));
210
211                 if (auto nb_frames = file_nb_frames())
212                 {
213                         out = std::min(out, nb_frames);
214                         input_.out(out);
215                 }
216         }
217
218         // frame_producer
219
220         core::draw_frame receive_impl() override
221         {
222                 return render_frame().first;
223         }
224
225         core::draw_frame last_frame() override
226         {
227                 return core::draw_frame::still(last_frame_);
228         }
229
230         core::constraints& pixel_constraints() override
231         {
232                 return constraints_;
233         }
234
235         double out_fps() const
236         {
237                 auto out_framerate      = muxer_->out_framerate();
238                 auto fps                        = static_cast<double>(out_framerate.numerator()) / static_cast<double>(out_framerate.denominator());
239
240                 return fps;
241         }
242
243         std::pair<core::draw_frame, uint32_t> render_frame()
244         {
245                 frame_timer_.restart();
246                 auto disable_logging = temporary_enable_quiet_logging_for_thread(thumbnail_mode_);
247
248                 for (int n = 0; n < 16 && frame_buffer_.size() < 2; ++n)
249                         try_decode_frame();
250
251                 graph_->set_value("frame-time", frame_timer_.elapsed() * out_fps() *0.5);
252
253                 if (frame_buffer_.empty())
254                 {
255                         if (input_.eof())
256                         {
257                                 send_osc();
258                                 return std::make_pair(last_frame(), -1);
259                         }
260                         else if (!is_url())
261                         {
262                                 graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow");
263                                 send_osc();
264                                 return std::make_pair(last_frame_, -1);
265                         }
266                         else
267                         {
268                                 send_osc();
269                                 return std::make_pair(last_frame_, -1);
270                         }
271                 }
272
273                 auto frame = frame_buffer_.front();
274                 frame_buffer_.pop();
275
276                 ++frame_number_;
277                 file_frame_number_ = frame.second;
278
279                 graph_->set_text(print());
280
281                 last_frame_ = frame.first;
282
283                 send_osc();
284
285                 return frame;
286         }
287
288         bool is_url() const
289         {
290                 return boost::contains(filename_, L"://");
291         }
292
293         void send_osc()
294         {
295                 double fps = static_cast<double>(framerate_.numerator()) / static_cast<double>(framerate_.denominator());
296
297                 *monitor_subject_       << core::monitor::message("/profiler/time")             % frame_timer_.elapsed() % (1.0/out_fps());
298
299                 *monitor_subject_       << core::monitor::message("/file/time")                 % (file_frame_number()/fps)
300                                                                                                                                                         % (file_nb_frames()/fps)
301                                                         << core::monitor::message("/file/frame")                        % static_cast<int32_t>(file_frame_number())
302                                                                                                                                                         % static_cast<int32_t>(file_nb_frames())
303                                                         << core::monitor::message("/file/fps")                  % fps
304                                                         << core::monitor::message("/file/path")                 % path_relative_to_media_
305                                                         << core::monitor::message("/loop")                              % input_.loop();
306         }
307
308         core::draw_frame render_specific_frame(uint32_t file_position)
309         {
310                 // Some trial and error and undeterministic stuff here
311                 static const int NUM_RETRIES = 32;
312
313                 if (file_position > 0) // Assume frames are requested in sequential order,
314                                            // therefore no seeking should be necessary for the first frame.
315                 {
316                         input_.seek(file_position > 1 ? file_position - 2: file_position).get();
317                         boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
318                 }
319
320                 for (int i = 0; i < NUM_RETRIES; ++i)
321                 {
322                         boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
323
324                         auto frame = render_frame();
325
326                         if (frame.second == std::numeric_limits<uint32_t>::max())
327                         {
328                                 // Retry
329                                 continue;
330                         }
331                         else if (frame.second == file_position + 1 || frame.second == file_position)
332                                 return frame.first;
333                         else if (frame.second > file_position + 1)
334                         {
335                                 CASPAR_LOG(trace) << print() << L" " << frame.second << L" received, wanted " << file_position + 1;
336                                 int64_t adjusted_seek = file_position - (frame.second - file_position + 1);
337
338                                 if (adjusted_seek > 1 && file_position > 0)
339                                 {
340                                         CASPAR_LOG(trace) << print() << L" adjusting to " << adjusted_seek;
341                                         input_.seek(static_cast<uint32_t>(adjusted_seek) - 1).get();
342                                         boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
343                                 }
344                                 else
345                                         return frame.first;
346                         }
347                 }
348
349                 CASPAR_LOG(trace) << print() << " Giving up finding frame at " << file_position;
350                 return core::draw_frame::empty();
351         }
352
353         core::draw_frame create_thumbnail_frame()
354         {
355                 auto total_frames = nb_frames();
356                 auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);
357
358                 if (grid < 1)
359                 {
360                         CASPAR_LOG(error) << L"configuration/thumbnails/video-grid cannot be less than 1";
361                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1"));
362                 }
363
364                 if (grid == 1)
365                 {
366                         return render_specific_frame(total_frames / 2);
367                 }
368
369                 auto num_snapshots = grid * grid;
370
371                 std::vector<core::draw_frame> frames;
372
373                 for (int i = 0; i < num_snapshots; ++i)
374                 {
375                         int x = i % grid;
376                         int y = i / grid;
377                         int desired_frame;
378
379                         if (i == 0)
380                                 desired_frame = 0; // first
381                         else if (i == num_snapshots - 1)
382                                 desired_frame = total_frames - 1; // last
383                         else
384                                 // evenly distributed across the file.
385                                 desired_frame = total_frames * i / (num_snapshots - 1);
386
387                         auto frame = render_specific_frame(desired_frame);
388                         frame.transform().image_transform.fill_scale[0] = 1.0 / static_cast<double>(grid);
389                         frame.transform().image_transform.fill_scale[1] = 1.0 / static_cast<double>(grid);
390                         frame.transform().image_transform.fill_translation[0] = 1.0 / static_cast<double>(grid) * x;
391                         frame.transform().image_transform.fill_translation[1] = 1.0 / static_cast<double>(grid) * y;
392
393                         frames.push_back(frame);
394                 }
395
396                 return core::draw_frame(frames);
397         }
398
399         uint32_t file_frame_number() const
400         {
401                 return video_decoder_ ? video_decoder_->file_frame_number() : 0;
402         }
403
404         uint32_t nb_frames() const override
405         {
406                 if (is_url() || input_.loop())
407                         return std::numeric_limits<uint32_t>::max();
408
409                 auto nb_frames = std::min(input_.out(), file_nb_frames());
410                 if (nb_frames >= input_.in())
411                         nb_frames -= input_.in();
412                 else
413                         nb_frames = 0;
414
415                 return muxer_->calc_nb_frames(nb_frames);
416         }
417
418         uint32_t file_nb_frames() const
419         {
420                 return video_decoder_ ? video_decoder_->nb_frames() : 0;
421         }
422
423         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
424         {
425                 std::wstring result;
426
427                 std::wstring cmd = params.at(0);
428                 std::wstring value;
429                 if (params.size() > 1)
430                         value = params.at(1);
431
432                 if (boost::iequals(cmd, L"loop"))
433                 {
434                         if (!value.empty())
435                                 input_.loop(boost::lexical_cast<bool>(value));
436                         result = boost::lexical_cast<std::wstring>(input_.loop());
437                 }
438                 else if (boost::iequals(cmd, L"in") || boost::iequals(cmd, L"start"))
439                 {
440                         if (!value.empty())
441                                 input_.in(boost::lexical_cast<uint32_t>(value));
442                         result = boost::lexical_cast<std::wstring>(input_.in());
443                 }
444                 else if (boost::iequals(cmd, L"out"))
445                 {
446                         if (!value.empty())
447                                 input_.out(boost::lexical_cast<uint32_t>(value));
448                         result = boost::lexical_cast<std::wstring>(input_.out());
449                 }
450                 else if (boost::iequals(cmd, L"length"))
451                 {
452                         if (!value.empty())
453                                 input_.length(boost::lexical_cast<uint32_t>(value));
454                         result = boost::lexical_cast<std::wstring>(input_.length());
455                 }
456                 else if (boost::iequals(cmd, L"seek") && !value.empty())
457                 {
458                         auto nb_frames = file_nb_frames();
459
460                         int64_t seek;
461                         if (boost::iequals(value, L"rel"))
462                                 seek = file_frame_number();
463                         else if (boost::iequals(value, L"in"))
464                                 seek = input_.in();
465                         else if (boost::iequals(value, L"out"))
466                                 seek = input_.out();
467                         else if (boost::iequals(value, L"end"))
468                                 seek = nb_frames;
469                         else
470                                 seek = boost::lexical_cast<int64_t>(value);
471
472                         if (params.size() > 2)
473                                 seek += boost::lexical_cast<int64_t>(params.at(2));
474
475                         if (seek < 0)
476                                 seek = 0;
477                         else if (seek >= nb_frames)
478                                 seek = nb_frames - 1;
479
480                         input_.seek(static_cast<uint32_t>(seek));
481                 }
482                 else
483                         CASPAR_THROW_EXCEPTION(invalid_argument());
484
485                 return make_ready_future(std::move(result));
486         }
487
488         std::wstring print() const override
489         {
490                 return L"ffmpeg[" + (is_url() ? filename_ : boost::filesystem::path(filename_).filename().wstring()) + L"|"
491                                                   + print_mode() + L"|"
492                                                   + boost::lexical_cast<std::wstring>(file_frame_number_) + L"/" + boost::lexical_cast<std::wstring>(file_nb_frames()) + L"]";
493         }
494
495         std::wstring name() const override
496         {
497                 return L"ffmpeg";
498         }
499
500         boost::property_tree::wptree info() const override
501         {
502                 boost::property_tree::wptree info;
503                 info.add(L"type",                               L"ffmpeg-producer");
504                 info.add(L"filename",                   filename_);
505                 info.add(L"width",                              video_decoder_ ? video_decoder_->width() : 0);
506                 info.add(L"height",                             video_decoder_ ? video_decoder_->height() : 0);
507                 info.add(L"progressive",                video_decoder_ ? video_decoder_->is_progressive() : false);
508                 info.add(L"fps",                                static_cast<double>(framerate_.numerator()) / static_cast<double>(framerate_.denominator()));
509                 info.add(L"loop",                               input_.loop());
510                 info.add(L"frame-number",               frame_number_);
511                 auto nb_frames2 = nb_frames();
512                 info.add(L"nb-frames",                  nb_frames2 == std::numeric_limits<int64_t>::max() ? -1 : nb_frames2);
513                 info.add(L"file-frame-number",  file_frame_number_);
514                 info.add(L"file-nb-frames",             file_nb_frames());
515                 return info;
516         }
517
518         core::monitor::subject& monitor_output()
519         {
520                 return *monitor_subject_;
521         }
522
523         // ffmpeg_producer
524
525         std::wstring print_mode() const
526         {
527                 return video_decoder_ ? ffmpeg::print_mode(
528                                 video_decoder_->width(),
529                                 video_decoder_->height(),
530                                 static_cast<double>(framerate_.numerator()) / static_cast<double>(framerate_.denominator()),
531                                 !video_decoder_->is_progressive()) : L"";
532         }
533
534         bool all_audio_decoders_ready() const
535         {
536                 for (auto& audio_decoder : audio_decoders_)
537                         if (!audio_decoder->ready())
538                                 return false;
539
540                 return true;
541         }
542
543         void try_decode_frame()
544         {
545                 std::shared_ptr<AVPacket> pkt;
546
547                 for (int n = 0; n < 32 && ((video_decoder_ && !video_decoder_->ready()) || !all_audio_decoders_ready()) && input_.try_pop(pkt); ++n)
548                 {
549                         if (video_decoder_)
550                                 video_decoder_->push(pkt);
551
552                         for (auto& audio_decoder : audio_decoders_)
553                                 audio_decoder->push(pkt);
554                 }
555
556                 std::shared_ptr<AVFrame>                                                                        video;
557                 std::vector<std::shared_ptr<core::mutable_audio_buffer>>        audio;
558
559                 tbb::parallel_invoke(
560                 [&]
561                 {
562                         if (!muxer_->video_ready() && video_decoder_)
563                                 video = video_decoder_->poll();
564                 },
565                 [&]
566                 {
567                         if (!muxer_->audio_ready())
568                         {
569                                 for (auto& audio_decoder : audio_decoders_)
570                                 {
571                                         auto audio_for_stream = audio_decoder->poll();
572
573                                         if (audio_for_stream)
574                                                 audio.push_back(audio_for_stream);
575                                 }
576                         }
577                 });
578
579                 muxer_->push(video);
580                 muxer_->push(audio);
581
582                 if (audio_decoders_.empty())
583                 {
584                         if (video == flush_video())
585                                 muxer_->push({ flush_audio() });
586                         else if (!muxer_->audio_ready())
587                                 muxer_->push({ empty_audio() });
588                 }
589
590                 if (!video_decoder_)
591                 {
592                         if (boost::count_if(audio, [](std::shared_ptr<core::mutable_audio_buffer> a) { return a == flush_audio(); }) > 0)
593                                 muxer_->push(flush_video());
594                         else if (!muxer_->video_ready())
595                                 muxer_->push(empty_video());
596                 }
597
598                 uint32_t file_frame_number = 0;
599                 file_frame_number = std::max(file_frame_number, video_decoder_ ? video_decoder_->file_frame_number() : 0);
600
601                 for (auto frame = muxer_->poll(); frame != core::draw_frame::empty(); frame = muxer_->poll())
602                         frame_buffer_.push(std::make_pair(frame, file_frame_number));
603         }
604
605         bool audio_only() const
606         {
607                 return !video_decoder_;
608         }
609
610         boost::rational<int> get_out_framerate() const
611         {
612                 return muxer_->out_framerate();
613         }
614 };
615
616 void describe_producer(core::help_sink& sink, const core::help_repository& repo)
617 {
618         sink.short_description(L"A producer for playing media files supported by FFmpeg.");
619         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]}");
620         sink.para()
621                 ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ")
622                 ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ")
623                 ->text(L"H.264, FLV, WMV and several audio codecs as well as uncompressed audio.");
624         sink.definitions()
625                 ->item(L"clip", L"The file without the file extension to play. It should reside under the media folder.")
626                 ->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}.")
627                 ->item(L"loop", L"Will cause the media file to loop between in and out.")
628                 ->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.")
629                 ->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.")
630                 ->item(L"length", L"Optionally sets the length of the clip. Equivalent to OUT in + length.")
631                 ->item(L"filter", L"If specified, will be used as an FFmpeg video filter.")
632                 ->item(L"channel_layout",
633                                 L"Optionally override the automatically deduced audio channel layout."
634                                 L"Either a named layout as specified in casparcg.config or in the format [type:string]:[channel_order:string] for a custom layout.");
635         sink.para()->text(L"Examples:");
636         sink.example(L">> PLAY 1-10 folder/clip", L"to play all frames in a clip and stop at the last frame.");
637         sink.example(L">> PLAY 1-10 folder/clip LOOP", L"to loop a clip between the first frame and the last frame.");
638         sink.example(L">> PLAY 1-10 folder/clip LOOP IN 10", L"to loop a clip between frame 10 and the last frame.");
639         sink.example(L">> PLAY 1-10 folder/clip LOOP IN 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60.");
640         sink.example(L">> PLAY 1-10 folder/clip IN 10 OUT 60", L"to play frames 10-60 in a clip and stop.");
641         sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video.");
642         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.");
643         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.");
644         sink.example(L">> PLAY 1-10 rtmp://example.com/live/stream", L"to play an RTMP stream.");
645         sink.example(L">> PLAY 1-10 \"dshow://video=Live! Cam Chat HD VF0790\"", L"to use a web camera as video input on Windows.");
646         sink.example(L">> PLAY 1-10 v4l2:///dev/video0", L"to use a web camera as video input on Linux.");
647         sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via ")->code(L"CALL")->text(L":");
648         sink.example(L">> CALL 1-10 LOOP 1");
649         sink.example(L">> CALL 1-10 IN 10");
650         sink.example(L">> CALL 1-10 OUT 60");
651         sink.example(L">> CALL 1-10 LENGTH 50");
652         sink.example(L">> CALL 1-10 SEEK 30");
653         core::describe_framerate_producer(sink);
654 }
655
656 spl::shared_ptr<core::frame_producer> create_producer(
657                 const core::frame_producer_dependencies& dependencies,
658                 const std::vector<std::wstring>& params,
659                 const spl::shared_ptr<core::media_info_repository>& info_repo)
660 {
661         auto file_or_url        = params.at(0);
662
663         if (!boost::contains(file_or_url, L"://"))
664         {
665                 // File
666                 file_or_url = probe_stem(env::media_folder() + L"/" + file_or_url, false);
667         }
668
669         if (file_or_url.empty())
670                 return core::frame_producer::empty();
671
672         constexpr auto uint32_max = std::numeric_limits<uint32_t>::max();
673
674         auto loop                                       = contains_param(L"LOOP",               params);
675
676         auto in                                         = get_param(L"SEEK",                    params, static_cast<uint32_t>(0)); // compatibility
677         in                                                      = get_param(L"IN",                              params, in);
678
679         auto out                                        = get_param(L"LENGTH",                  params, uint32_max);
680         if (out < uint32_max - in)
681                 out += in;
682         else
683                 out = uint32_max;
684         out                                                     = get_param(L"OUT",                             params, out);
685
686         auto filter_str                         = get_param(L"FILTER",                  params, L"");
687         auto custom_channel_order       = get_param(L"CHANNEL_LAYOUT",  params, L"");
688
689         boost::ireplace_all(filter_str, L"DEINTERLACE_BOB",     L"YADIF=1:-1");
690         boost::ireplace_all(filter_str, L"DEINTERLACE_LQ",      L"SEPARATEFIELDS");
691         boost::ireplace_all(filter_str, L"DEINTERLACE",         L"YADIF=0:-1");
692
693         ffmpeg_options vid_params;
694         bool haveFFMPEGStartIndicator = false;
695         for (size_t i = 0; i < params.size() - 1; ++i)
696         {
697                 if (!haveFFMPEGStartIndicator && params[i] == L"--")
698                 {
699                         haveFFMPEGStartIndicator = true;
700                         continue;
701                 }
702                 if (haveFFMPEGStartIndicator)
703                 {
704                         auto name = u8(params.at(i++)).substr(1);
705                         auto value = u8(params.at(i));
706                         vid_params.push_back(std::make_pair(name, value));
707                 }
708         }
709
710         auto producer = spl::make_shared<ffmpeg_producer>(
711                         dependencies.frame_factory,
712                         dependencies.format_desc,
713                         file_or_url,
714                         filter_str,
715                         loop,
716                         in,
717                         out,
718                         false,
719                         custom_channel_order,
720                         vid_params);
721
722         if (producer->audio_only())
723                 return core::create_destroy_proxy(producer);
724
725         auto get_source_framerate       = [=] { return producer->get_out_framerate(); };
726         auto target_framerate           = dependencies.format_desc.framerate;
727
728         return core::create_destroy_proxy(core::create_framerate_producer(
729                         producer,
730                         get_source_framerate,
731                         target_framerate,
732                         dependencies.format_desc.field_mode,
733                         dependencies.format_desc.audio_cadence));
734 }
735
736 core::draw_frame create_thumbnail_frame(
737                 const core::frame_producer_dependencies& dependencies,
738                 const std::wstring& media_file,
739                 const spl::shared_ptr<core::media_info_repository>& info_repo)
740 {
741         auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
742         auto filename = probe_stem(env::media_folder() + L"/" + media_file, true);
743
744         if (filename.empty())
745                 return core::draw_frame::empty();
746
747         auto loop               = false;
748         auto in                 = 0;
749         auto out                = std::numeric_limits<uint32_t>::max();
750         auto filter_str = L"";
751
752         ffmpeg_options vid_params;
753         auto producer = spl::make_shared<ffmpeg_producer>(
754                         dependencies.frame_factory,
755                         dependencies.format_desc,
756                         filename,
757                         filter_str,
758                         loop,
759                         in,
760                         out,
761                         true,
762                         L"",
763                         vid_params);
764
765         return producer->create_thumbnail_frame();
766 }
767 }}