X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fffmpeg%2Fproducer%2Fffmpeg_producer.cpp;h=a77994896eabfd92baf5fa8acaba7da712e6987b;hb=61974506572c47b0650c4b5bfa65cd18621d5fec;hp=aed7a41dc3d9748ea8bcc16d31e3c4b2c9be3257;hpb=ceccf878fb19bbed09269b7a087f907d3ab5726e;p=casparcg diff --git a/modules/ffmpeg/producer/ffmpeg_producer.cpp b/modules/ffmpeg/producer/ffmpeg_producer.cpp index aed7a41dc..a77994896 100644 --- a/modules/ffmpeg/producer/ffmpeg_producer.cpp +++ b/modules/ffmpeg/producer/ffmpeg_producer.cpp @@ -23,49 +23,29 @@ #include "ffmpeg_producer.h" -#include "../ffmpeg_error.h" #include "../ffmpeg.h" - -#include "muxer/frame_muxer.h" -#include "input/input.h" +#include "../ffmpeg_error.h" #include "util/util.h" +#include "input/input.h" #include "audio/audio_decoder.h" #include "video/video_decoder.h" +#include "muxer/frame_muxer.h" -#include -#include #include #include #include -#include -#include -#include -#include -#include -#include #include -#include -#include #include #include -#include #include +#include +#include -#include -#include -#include -#include -#include - -#include - -#include -#include +#include #include namespace caspar { namespace ffmpeg { - struct seek_out_of_range : virtual user_error {}; std::wstring get_relative_or_original( @@ -95,94 +75,104 @@ std::wstring get_relative_or_original( struct ffmpeg_producer : public core::frame_producer_base { - spl::shared_ptr monitor_subject_; - const std::wstring filename_; - const std::wstring path_relative_to_media_ = get_relative_or_original(filename_, env::media_folder()); - - const spl::shared_ptr graph_; - - const spl::shared_ptr frame_factory_; - const core::video_format_desc format_desc_; - - input input_; - - const double fps_ = read_fps(input_.context(), format_desc_.fps); - const uint32_t start_; - const bool thumbnail_mode_; - const boost::optional info_; - - std::unique_ptr video_decoder_; - std::unique_ptr audio_decoder_; - std::unique_ptr muxer_; - core::constraints constraints_; - - core::draw_frame last_frame_ = core::draw_frame::empty(); - - boost::optional seek_target_; - + spl::shared_ptr monitor_subject_; + const std::wstring filename_; + const std::wstring path_relative_to_media_ = get_relative_or_original(filename_, env::media_folder()); + + FFMPEG_Resource resource_type_; + + const spl::shared_ptr graph_; + timer frame_timer_; + + const spl::shared_ptr frame_factory_; + + std::shared_ptr initial_logger_disabler_; + + core::constraints constraints_; + + input input_; + std::unique_ptr video_decoder_; + std::unique_ptr audio_decoder_; + std::unique_ptr muxer_; + + const boost::rational framerate_; + const uint32_t start_; + const uint32_t length_; + const bool thumbnail_mode_; + + core::draw_frame last_frame_; + + std::queue> frame_buffer_; + + int64_t frame_number_ = 0; + uint32_t file_frame_number_ = 0; public: explicit ffmpeg_producer( - const spl::shared_ptr& frame_factory, + const spl::shared_ptr& frame_factory, const core::video_format_desc& format_desc, - const std::wstring& channel_layout_spec, const std::wstring& filename, + FFMPEG_Resource resource_type, const std::wstring& filter, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, - boost::optional info) + const std::wstring& custom_channel_order, + const ffmpeg_options& vid_params) : filename_(filename) - , frame_factory_(frame_factory) - , format_desc_(format_desc) - , input_(graph_, filename_, loop, start, length, thumbnail_mode) + , resource_type_(resource_type) + , frame_factory_(frame_factory) + , initial_logger_disabler_(temporary_enable_quiet_logging_for_thread(thumbnail_mode)) + , input_(graph_, filename_, resource_type, loop, start, length, thumbnail_mode, vid_params) + , framerate_(read_framerate(*input_.context(), format_desc.framerate)) , start_(start) + , length_(length) , thumbnail_mode_(thumbnail_mode) - , info_(info) + , last_frame_(core::draw_frame::empty()) + , frame_number_(0) { graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f)); - graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f)); + graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f)); diagnostics::register_graph(graph_); - try { - video_decoder_.reset(new video_decoder(input_, thumbnail_mode)); - video_decoder_->monitor_output().attach_parent(monitor_subject_); + video_decoder_.reset(new video_decoder(input_.context())); + if (!thumbnail_mode_) + CASPAR_LOG(info) << print() << L" " << video_decoder_->print(); + constraints_.width.set(video_decoder_->width()); constraints_.height.set(video_decoder_->height()); - - if (is_logging_quiet_for_thread()) - CASPAR_LOG(debug) << print() << L" " << video_decoder_->print(); - else - CASPAR_LOG(info) << print() << L" " << video_decoder_->print(); } - catch(averror_stream_not_found&) + catch (averror_stream_not_found&) { - CASPAR_LOG(debug) << print() << " No video-stream found. Running without video."; + //CASPAR_LOG(warning) << print() << " No video-stream found. Running without video."; } - catch(...) + catch (...) { - CASPAR_LOG_CURRENT_EXCEPTION(); - CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video."; + if (!thumbnail_mode_) + { + CASPAR_LOG_CURRENT_EXCEPTION(); + CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video."; + } } auto channel_layout = core::audio_channel_layout::invalid(); - if (!thumbnail_mode) + if (!thumbnail_mode_) { try { - audio_decoder_.reset(new audio_decoder(input_, format_desc_, channel_layout_spec)); - audio_decoder_->monitor_output().attach_parent(monitor_subject_); - - channel_layout = audio_decoder_->channel_layout(); - + audio_decoder_.reset(new audio_decoder(input_.context(), format_desc.audio_sample_rate)); + channel_layout = get_audio_channel_layout( + audio_decoder_->num_channels(), + audio_decoder_->ffmpeg_channel_layout(), + custom_channel_order); CASPAR_LOG(info) << print() << L" " << audio_decoder_->print(); } catch (averror_stream_not_found&) { - CASPAR_LOG(debug) << print() << " No audio-stream found. Running without audio."; + //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio."; } catch (...) { @@ -191,76 +181,144 @@ public: } } - if (start_ > file_nb_frames()) - CASPAR_THROW_EXCEPTION(seek_out_of_range() << msg_info("SEEK out of range")); + if (!video_decoder_ && !audio_decoder_) + CASPAR_THROW_EXCEPTION(averror_stream_not_found() << msg_info("No streams found")); - muxer_.reset(new frame_muxer(fps_, frame_factory, format_desc_, channel_layout, filter)); - - decode_next_frame(); - - if (is_logging_quiet_for_thread()) - CASPAR_LOG(debug) << print() << L" Initialized"; - else - CASPAR_LOG(info) << print() << L" Initialized"; + muxer_.reset(new frame_muxer(framerate_, frame_factory, format_desc, channel_layout, filter, true)); } // frame_producer - + core::draw_frame receive_impl() override - { - auto frame = core::draw_frame::late(); - - caspar::timer frame_timer; - - end_seek(); - - decode_next_frame(); - - if(!muxer_->empty()) + { + return render_frame().first; + } + + core::draw_frame last_frame() override + { + return core::draw_frame::still(last_frame_); + } + + core::constraints& pixel_constraints() override + { + return constraints_; + } + + double out_fps() const + { + auto out_framerate = muxer_->out_framerate(); + auto fps = static_cast(out_framerate.numerator()) / static_cast(out_framerate.denominator()); + + return fps; + } + + std::pair render_frame() + { + frame_timer_.restart(); + auto disable_logging = temporary_enable_quiet_logging_for_thread(thumbnail_mode_); + + for (int n = 0; n < 16 && frame_buffer_.size() < 2; ++n) + try_decode_frame(); + + graph_->set_value("frame-time", frame_timer_.elapsed() * out_fps() *0.5); + + if (frame_buffer_.empty()) { - last_frame_ = frame = std::move(muxer_->front()); - muxer_->pop(); + if (input_.eof()) + { + send_osc(); + return std::make_pair(last_frame(), -1); + } + else if (resource_type_ == FFMPEG_Resource::FFMPEG_FILE) + { + graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow"); + send_osc(); + return std::make_pair(core::draw_frame::late(), -1); + } + else + { + send_osc(); + return std::make_pair(last_frame(), -1); + } } - else if (!input_.eof()) - graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow"); - - graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5); - *monitor_subject_ - << core::monitor::message("/profiler/time") % frame_timer.elapsed() % (1.0/format_desc_.fps); - *monitor_subject_ - << core::monitor::message("/file/frame") % static_cast(file_frame_number()) - % static_cast(file_nb_frames()) - << core::monitor::message("/file/fps") % fps_ - << core::monitor::message("/file/path") % path_relative_to_media_ - << core::monitor::message("/loop") % input_.loop(); - + + auto frame = frame_buffer_.front(); + frame_buffer_.pop(); + + ++frame_number_; + file_frame_number_ = frame.second; + + graph_->set_text(print()); + + last_frame_ = frame.first; + + send_osc(); + return frame; } + void send_osc() + { + double fps = static_cast(framerate_.numerator()) / static_cast(framerate_.denominator()); + + *monitor_subject_ << core::monitor::message("/profiler/time") % frame_timer_.elapsed() % (1.0/out_fps()); + + *monitor_subject_ << core::monitor::message("/file/time") % (file_frame_number()/fps) + % (file_nb_frames()/fps) + << core::monitor::message("/file/frame") % static_cast(file_frame_number()) + % static_cast(file_nb_frames()) + << core::monitor::message("/file/fps") % fps + << core::monitor::message("/file/path") % path_relative_to_media_ + << core::monitor::message("/loop") % input_.loop(); + } + core::draw_frame render_specific_frame(uint32_t file_position) { - muxer_->clear(); - input_.seek(file_position); + // Some trial and error and undeterministic stuff here + static const int NUM_RETRIES = 32; - decode_next_frame(); + if (file_position > 0) // Assume frames are requested in sequential order, + // therefore no seeking should be necessary for the first frame. + { + input_.seek(file_position > 1 ? file_position - 2: file_position).get(); + boost::this_thread::sleep_for(boost::chrono::milliseconds(40)); + } - if (muxer_->empty()) + for (int i = 0; i < NUM_RETRIES; ++i) { - CASPAR_LOG(trace) << print() << " Giving up finding frame at " << file_position; + boost::this_thread::sleep_for(boost::chrono::milliseconds(40)); - return core::draw_frame::empty(); - } + auto frame = render_frame(); - auto frame = muxer_->front(); - muxer_->pop(); + if (frame.second == std::numeric_limits::max()) + { + // Retry + continue; + } + else if (frame.second == file_position + 1 || frame.second == file_position) + return frame.first; + else if (frame.second > file_position + 1) + { + CASPAR_LOG(trace) << print() << L" " << frame.second << L" received, wanted " << file_position + 1; + int64_t adjusted_seek = file_position - (frame.second - file_position + 1); + + if (adjusted_seek > 1 && file_position > 0) + { + CASPAR_LOG(trace) << print() << L" adjusting to " << adjusted_seek; + input_.seek(static_cast(adjusted_seek) - 1).get(); + boost::this_thread::sleep_for(boost::chrono::milliseconds(40)); + } + else + return frame.first; + } + } - return frame; + CASPAR_LOG(trace) << print() << " Giving up finding frame at " << file_position; + return core::draw_frame::empty(); } - core::draw_frame create_thumbnail_frame() override + core::draw_frame create_thumbnail_frame() { - auto quiet_logging = temporary_enable_quiet_logging_for_thread(true); - auto total_frames = nb_frames(); auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2); @@ -305,47 +363,31 @@ public: return core::draw_frame(frames); } - core::draw_frame last_frame() override - { - end_seek(); - return core::draw_frame::still(last_frame_); - } - - core::constraints& pixel_constraints() override + uint32_t file_frame_number() const { - return constraints_; + return video_decoder_ ? video_decoder_->file_frame_number() : 0; } uint32_t nb_frames() const override { - if(input_.loop()) + if (resource_type_ == FFMPEG_Resource::FFMPEG_DEVICE || resource_type_ == FFMPEG_Resource::FFMPEG_STREAM || input_.loop()) return std::numeric_limits::max(); uint32_t nb_frames = file_nb_frames(); - nb_frames = std::min(input_.length(), nb_frames); + nb_frames = std::min(length_, nb_frames - start_); nb_frames = muxer_->calc_nb_frames(nb_frames); - - return nb_frames > start_ ? nb_frames - start_ : 0; + + return nb_frames; } uint32_t file_nb_frames() const { uint32_t file_nb_frames = 0; - - if (info_) - file_nb_frames = static_cast(info_->duration); - file_nb_frames = std::max(file_nb_frames, video_decoder_ ? video_decoder_->nb_frames() : 0); - file_nb_frames = std::max(file_nb_frames, audio_decoder_ ? audio_decoder_->nb_frames() : 0); return file_nb_frames; } - uint32_t file_frame_number() const - { - return video_decoder_ ? video_decoder_->file_frame_number() : 0; - } - std::future call(const std::vector& params) override { static const boost::wregex loop_exp(LR"(LOOP\s*(?\d?)?)", boost::regex::icase); @@ -354,47 +396,47 @@ public: static const boost::wregex start_exp(LR"(START\\s+(?\\d+)?)", boost::regex::icase); auto param = boost::algorithm::join(params, L" "); - + std::wstring result; - + boost::wsmatch what; if(boost::regex_match(param, what, loop_exp)) { auto value = what["VALUE"].str(); - if(!value.empty()) + if (!value.empty()) input_.loop(boost::lexical_cast(value)); - result = boost::lexical_cast(loop()); + result = boost::lexical_cast(input_.loop()); } else if(boost::regex_match(param, what, seek_exp)) { auto value = what["VALUE"].str(); - seek(boost::lexical_cast(value)); + input_.seek(boost::lexical_cast(value)); } else if(boost::regex_match(param, what, length_exp)) { auto value = what["VALUE"].str(); if(!value.empty()) - length(boost::lexical_cast(value)); - result = boost::lexical_cast(length()); + input_.length(boost::lexical_cast(value)); + result = boost::lexical_cast(input_.length()); } else if(boost::regex_match(param, what, start_exp)) { auto value = what["VALUE"].str(); if(!value.empty()) - start(boost::lexical_cast(value)); - result = boost::lexical_cast(start()); + input_.start(boost::lexical_cast(value)); + result = boost::lexical_cast(input_.start()); } else CASPAR_THROW_EXCEPTION(invalid_argument()); return make_ready_future(std::move(result)); } - + std::wstring print() const override { - return L"ffmpeg[" + boost::filesystem::path(filename_).filename().wstring() + L"|" - + print_mode() + L"|" - + boost::lexical_cast(file_frame_number()) + L"/" + boost::lexical_cast(file_nb_frames()) + L"]"; + return L"ffmpeg[" + boost::filesystem::path(filename_).filename().wstring() + L"|" + + print_mode() + L"|" + + boost::lexical_cast(file_frame_number_) + L"/" + boost::lexical_cast(file_nb_frames()) + L"]"; } std::wstring name() const override @@ -405,123 +447,106 @@ public: boost::property_tree::wptree info() const override { boost::property_tree::wptree info; - info.add(L"type", L"ffmpeg"); + info.add(L"type", L"ffmpeg-producer"); info.add(L"filename", filename_); info.add(L"width", video_decoder_ ? video_decoder_->width() : 0); info.add(L"height", video_decoder_ ? video_decoder_->height() : 0); - info.add(L"progressive", video_decoder_ ? video_decoder_->is_progressive() : 0); - info.add(L"fps", fps_); + info.add(L"progressive", video_decoder_ ? video_decoder_->is_progressive() : false); + info.add(L"fps", static_cast(framerate_.numerator()) / static_cast(framerate_.denominator())); info.add(L"loop", input_.loop()); - info.add(L"frame-number", frame_number()); + info.add(L"frame-number", frame_number_); auto nb_frames2 = nb_frames(); info.add(L"nb-frames", nb_frames2 == std::numeric_limits::max() ? -1 : nb_frames2); - info.add(L"file-frame-number", file_frame_number()); + info.add(L"file-frame-number", file_frame_number_); info.add(L"file-nb-frames", file_nb_frames()); return info; } - + core::monitor::subject& monitor_output() { return *monitor_subject_; } // ffmpeg_producer - - void end_seek() - { - for(int n = 0; n < 8 && (last_frame_ == core::draw_frame::empty() || (seek_target_ && file_frame_number() != *seek_target_+2)); ++n) - { - decode_next_frame(); - if(!muxer_->empty()) - { - last_frame_ = muxer_->front(); - seek_target_.reset(); - } - } - } - void loop(bool value) + std::wstring print_mode() const { - input_.loop(value); + return video_decoder_ ? ffmpeg::print_mode( + video_decoder_->width(), + video_decoder_->height(), + static_cast(framerate_.numerator()) / static_cast(framerate_.denominator()), + !video_decoder_->is_progressive()) : L""; } - bool loop() const + void try_decode_frame() { - return input_.loop(); - } + std::shared_ptr pkt; - void length(uint32_t value) - { - input_.length(value); - } + for (int n = 0; n < 32 && ((video_decoder_ && !video_decoder_->ready()) || (audio_decoder_ && !audio_decoder_->ready())) && input_.try_pop(pkt); ++n) + { + if (video_decoder_) + video_decoder_->push(pkt); + if (audio_decoder_) + audio_decoder_->push(pkt); + } - uint32_t length() - { - return input_.length(); - } - - void start(uint32_t value) - { - input_.start(value); - } + std::shared_ptr video; + std::shared_ptr audio; - uint32_t start() - { - return input_.start(); - } + tbb::parallel_invoke( + [&] + { + if (!muxer_->video_ready() && video_decoder_) + video = video_decoder_->poll(); + }, + [&] + { + if (!muxer_->audio_ready() && audio_decoder_) + audio = audio_decoder_->poll(); + }); + + muxer_->push(video); + muxer_->push(audio); + + if (!audio_decoder_) + { + if(video == flush_video()) + muxer_->push(flush_audio()); + else if(!muxer_->audio_ready()) + muxer_->push(empty_audio()); + } - void seek(uint32_t target) - { - if (target > file_nb_frames()) - CASPAR_THROW_EXCEPTION(seek_out_of_range() << msg_info("SEEK out of range")); + if (!video_decoder_) + { + if(audio == flush_audio()) + muxer_->push(flush_video()); + else if(!muxer_->video_ready()) + muxer_->push(empty_video()); + } - seek_target_ = target; + uint32_t file_frame_number = 0; + file_frame_number = std::max(file_frame_number, video_decoder_ ? video_decoder_->file_frame_number() : 0); + //file_frame_number = std::max(file_frame_number, audio_decoder_ ? audio_decoder_->file_frame_number() : 0); - input_.seek(*seek_target_); - muxer_->clear(); + for (auto frame = muxer_->poll(); frame != core::draw_frame::empty(); frame = muxer_->poll()) + frame_buffer_.push(std::make_pair(frame, file_frame_number)); } - std::wstring print_mode() const + bool audio_only() const { - return ffmpeg::print_mode(video_decoder_ ? video_decoder_->width() : 0, - video_decoder_ ? video_decoder_->height() : 0, - fps_, - video_decoder_ ? !video_decoder_->is_progressive() : false); + return !video_decoder_; } - - void decode_next_frame() - { - for(int n = 0; n < 32 && muxer_->empty(); ++n) - { - std::shared_ptr video; - std::shared_ptr audio; - bool needs_video = !muxer_->video_ready(); - bool needs_audio = !muxer_->audio_ready(); - - tbb::parallel_invoke( - [&] - { - if (needs_video) - video = video_decoder_ ? (*video_decoder_)() : create_frame(); - }, - [&] - { - if (needs_audio) - audio = audio_decoder_ ? (*audio_decoder_)() : create_frame(); - }); - - muxer_->push_video(video); - muxer_->push_audio(audio); - } - graph_->set_text(print()); + boost::rational get_out_framerate() const + { + return muxer_->out_framerate(); } }; void describe_producer(core::help_sink& sink, const core::help_repository& repo) { sink.short_description(L"A producer for playing media files supported by FFmpeg."); - sink.syntax(L"[clip:string] {[loop:LOOP]} {START,SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}"); + sink.syntax(L"[clip:string] {[loop:LOOP]} {SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}"); sink.para() ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ") ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ") @@ -538,16 +563,18 @@ void describe_producer(core::help_sink& sink, const core::help_repository& repo) sink.para()->text(L"Examples:"); sink.example(L">> PLAY 1-10 folder/clip", L"to play all frames in a clip and stop at the last frame."); sink.example(L">> PLAY 1-10 folder/clip LOOP", L"to loop a clip between the first frame and the last frame."); - sink.example(L">> PLAY 1-10 folder/clip LOOP START 10", L"to loop a clip between frame 10 and the last frame."); - sink.example(L">> PLAY 1-10 folder/clip LOOP START 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60."); - sink.example(L">> PLAY 1-10 folder/clip START 10 LENGTH 50", L"to play frames 10-60 in a clip and stop."); + sink.example(L">> PLAY 1-10 folder/clip LOOP SEEK 10", L"to loop a clip between frame 10 and the last frame."); + sink.example(L">> PLAY 1-10 folder/clip LOOP SEEK 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60."); + sink.example(L">> PLAY 1-10 folder/clip SEEK 10 LENGTH 50", L"to play frames 10-60 in a clip and stop."); sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video."); sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT film", L"given the defaults in casparcg.config this will specifies that the clip has 6 audio channels of the type 5.1 and that they are in the order FL FC FR BL BR LFE regardless of what ffmpeg says."); sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT \"5.1:LFE FL FC FR BL BR\"", L"specifies that the clip has 6 audio channels of the type 5.1 and that they are in the specified order regardless of what ffmpeg says."); - sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via CALL:"); + sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via ")->code(L"CALL")->text(L":"); sink.example(L">> CALL 1-10 LOOP 1"); sink.example(L">> CALL 1-10 START 10"); sink.example(L">> CALL 1-10 LENGTH 50"); + sink.example(L">> CALL 1-10 SEEK 30"); + core::describe_framerate_producer(sink); } spl::shared_ptr create_producer( @@ -555,62 +582,120 @@ spl::shared_ptr create_producer( const std::vector& params, const spl::shared_ptr& info_repo) { - auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), false); + // Infer the resource type from the resource_name + auto resource_type = FFMPEG_Resource::FFMPEG_FILE; + auto tokens = protocol_split(params.at(0)); + auto filename = params.at(0); + + if (!tokens[0].empty()) + { + if (tokens[0] == L"dshow") + { + // Camera + resource_type = FFMPEG_Resource::FFMPEG_DEVICE; + filename = tokens[1]; + } + else + { + // Stream + resource_type = FFMPEG_Resource::FFMPEG_STREAM; + filename = params.at(0); + } + } + else + { + // File + resource_type = FFMPEG_Resource::FFMPEG_FILE; + filename = probe_stem(env::media_folder() + L"/" + params.at(0), false); + } - if(filename.empty()) + if (filename.empty()) return core::frame_producer::empty(); - - bool loop = contains_param(L"LOOP", params); - auto start = get_param(L"START", params, get_param(L"SEEK", params, static_cast(0))); - auto length = get_param(L"LENGTH", params, std::numeric_limits::max()); - auto filter_str = get_param(L"FILTER", params, L""); - auto channel_layout = get_param(L"CHANNEL_LAYOUT", params, L""); - bool thumbnail_mode = false; - auto info = info_repo->get(filename); - - return create_destroy_proxy(spl::make_shared_ptr(std::make_shared( + + auto loop = contains_param(L"LOOP", params); + auto start = get_param(L"SEEK", params, static_cast(0)); + auto length = get_param(L"LENGTH", params, std::numeric_limits::max()); + auto filter_str = get_param(L"FILTER", params, L""); + auto custom_channel_order = get_param(L"CHANNEL_LAYOUT", params, L""); + + boost::ireplace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1"); + boost::ireplace_all(filter_str, L"DEINTERLACE_LQ", L"SEPARATEFIELDS"); + boost::ireplace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1"); + + ffmpeg_options vid_params; + bool haveFFMPEGStartIndicator = false; + for (size_t i = 0; i < params.size() - 1; ++i) + { + if (!haveFFMPEGStartIndicator && params[i] == L"--") + { + haveFFMPEGStartIndicator = true; + continue; + } + if (haveFFMPEGStartIndicator) + { + auto name = u8(params.at(i++)).substr(1); + auto value = u8(params.at(i)); + vid_params.push_back(std::make_pair(name, value)); + } + } + + auto producer = spl::make_shared( dependencies.frame_factory, dependencies.format_desc, - channel_layout, filename, + resource_type, filter_str, loop, start, length, - thumbnail_mode, - info))); + false, + custom_channel_order, + vid_params); + + if (producer->audio_only()) + return core::create_destroy_proxy(producer); + + auto get_source_framerate = [=] { return producer->get_out_framerate(); }; + auto target_framerate = dependencies.format_desc.framerate; + + return core::create_destroy_proxy(core::create_framerate_producer( + producer, + get_source_framerate, + target_framerate, + dependencies.format_desc.field_mode, + dependencies.format_desc.audio_cadence)); } -spl::shared_ptr create_thumbnail_producer( +core::draw_frame create_thumbnail_frame( const core::frame_producer_dependencies& dependencies, - const std::vector& params, + const std::wstring& media_file, const spl::shared_ptr& info_repo) { auto quiet_logging = temporary_enable_quiet_logging_for_thread(true); - auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), true); + auto filename = probe_stem(env::media_folder() + L"/" + media_file, true); - if(filename.empty()) - return core::frame_producer::empty(); - - bool loop = false; - auto start = 0; - auto length = std::numeric_limits::max(); - auto filter_str = L""; - auto channel_layout = L""; - bool thumbnail_mode = true; - auto info = info_repo->get(filename); - - return spl::make_shared_ptr(std::make_shared( + if (filename.empty()) + return core::draw_frame::empty(); + + auto loop = false; + auto start = 0; + auto length = std::numeric_limits::max(); + auto filter_str = L""; + + ffmpeg_options vid_params; + auto producer = spl::make_shared( dependencies.frame_factory, dependencies.format_desc, - channel_layout, filename, + FFMPEG_Resource::FFMPEG_FILE, filter_str, loop, start, length, - thumbnail_mode, - info)); -} + true, + L"", + vid_params); + return producer->create_thumbnail_frame(); +} }}