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