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