]> git.sesse.net Git - casparcg/blobdiff - modules/ffmpeg/producer/ffmpeg_producer.cpp
Simplified thumbnail creation, making it less intrusive in the frame_producer design.
[casparcg] / modules / ffmpeg / producer / ffmpeg_producer.cpp
index 813c933302d26c32cc76a52a6fa39483d5d0cf9f..fef7848777b3cfd0d07769142293e14113cb8f56 100644 (file)
@@ -24,6 +24,7 @@
 #include "ffmpeg_producer.h"
 
 #include "../ffmpeg_error.h"
+#include "../ffmpeg.h"
 
 #include "muxer/frame_muxer.h"
 #include "input/input.h"
@@ -48,6 +49,8 @@
 #include <core/monitor/monitor.h>
 #include <core/help/help_repository.h>
 #include <core/help/help_sink.h>
+#include <core/producer/media_info/media_info_repository.h>
+#include <core/producer/media_info/media_info.h>
 
 #include <boost/algorithm/string.hpp>
 #include <boost/filesystem.hpp>
@@ -105,6 +108,8 @@ struct ffmpeg_producer : public core::frame_producer_base
 
        const double                                                                    fps_                                    = read_fps(input_.context(), format_desc_.fps);
        const uint32_t                                                                  start_;
+       const bool                                                                              thumbnail_mode_;
+       const boost::optional<core::media_info>                 info_;
                
        std::unique_ptr<video_decoder>                                  video_decoder_;
        std::unique_ptr<audio_decoder>                                  audio_decoder_; 
@@ -124,13 +129,16 @@ public:
                        const std::wstring& filter,
                        bool loop,
                        uint32_t start,
-                       uint32_t length)
+                       uint32_t length,
+                       bool thumbnail_mode,
+                       boost::optional<core::media_info> info)
                : filename_(filename)
                , frame_factory_(frame_factory)         
                , format_desc_(format_desc)
-               , input_(graph_, filename_, loop, start, length)
-               , fps_(read_fps(input_.context(), format_desc_.fps))
+               , input_(graph_, filename_, loop, start, length, thumbnail_mode)
                , start_(start)
+               , thumbnail_mode_(thumbnail_mode)
+               , info_(info)
        {
                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));   
@@ -139,16 +147,19 @@ public:
 
                try
                {
-                       video_decoder_.reset(new video_decoder(input_));
+                       video_decoder_.reset(new video_decoder(input_, thumbnail_mode));
                        video_decoder_->monitor_output().attach_parent(monitor_subject_);
                        constraints_.width.set(video_decoder_->width());
                        constraints_.height.set(video_decoder_->height());
                        
-                       CASPAR_LOG(info) << print() << L" " << video_decoder_->print();
+                       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&)
                {
-                       //CASPAR_LOG(warning) << print() << " No video-stream found. Running without video.";   
+                       CASPAR_LOG(debug) << print() << " No video-stream found. Running without video.";       
                }
                catch(...)
                {
@@ -158,23 +169,26 @@ public:
 
                auto channel_layout = core::audio_channel_layout::invalid();
 
-               try
+               if (!thumbnail_mode)
                {
-                       audio_decoder_ .reset(new audio_decoder(input_, format_desc_, channel_layout_spec));
-                       audio_decoder_->monitor_output().attach_parent(monitor_subject_);
+                       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();
-                       
-                       CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();
-               }
-               catch(averror_stream_not_found&)
-               {
-                       //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio.";   
-               }
-               catch(...)
-               {
-                       CASPAR_LOG_CURRENT_EXCEPTION();
-                       CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";               
+                               channel_layout = audio_decoder_->channel_layout();
+
+                               CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();
+                       }
+                       catch (averror_stream_not_found&)
+                       {
+                               CASPAR_LOG(debug) << print() << " No audio-stream found. Running without audio.";       
+                       }
+                       catch (...)
+                       {
+                               CASPAR_LOG_CURRENT_EXCEPTION();
+                               CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";
+                       }
                }
 
                if (start_ > file_nb_frames())
@@ -184,7 +198,10 @@ public:
                
                decode_next_frame();
 
-               CASPAR_LOG(info) << print() << L" Initialized";
+               if (is_logging_quiet_for_thread())
+                       CASPAR_LOG(debug) << print() << L" Initialized";
+               else
+                       CASPAR_LOG(info) << print() << L" Initialized";
        }
 
        // frame_producer
@@ -247,6 +264,10 @@ public:
        uint32_t file_nb_frames() const
        {
                uint32_t file_nb_frames = 0;
+
+               if (info_)
+                       file_nb_frames = static_cast<uint32_t>(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;
@@ -404,10 +425,25 @@ public:
        {
                for(int n = 0; n < 32 && muxer_->empty(); ++n)
                {
-                       if(!muxer_->video_ready())
-                               muxer_->push_video(video_decoder_ ? (*video_decoder_)() : create_frame());
-                       if(!muxer_->audio_ready())
-                               muxer_->push_audio(audio_decoder_ ? (*audio_decoder_)() : create_frame());
+                       std::shared_ptr<AVFrame> video;
+                       std::shared_ptr<AVFrame> 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());
@@ -446,9 +482,12 @@ void describe_producer(core::help_sink& sink, const core::help_repository& repo)
        sink.example(L">> CALL 1-10 LENGTH 50");
 }
 
-spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
-{              
-       auto filename = probe_stem(env::media_folder() + L"/" + params.at(0));
+spl::shared_ptr<core::frame_producer> create_producer(
+               const core::frame_producer_dependencies& dependencies,
+               const std::vector<std::wstring>& params,
+               const spl::shared_ptr<core::media_info_repository>& info_repo)
+{
+       auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), false);
 
        if(filename.empty())
                return core::frame_producer::empty();
@@ -458,6 +497,8 @@ spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer
        auto length                     = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::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<ffmpeg_producer>(
                        dependencies.frame_factory,
@@ -467,7 +508,85 @@ spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer
                        filter_str,
                        loop,
                        start,
-                       length)));
+                       length,
+                       thumbnail_mode,
+                       info)));
+}
+
+core::draw_frame create_thumbnail_frame(
+       const core::frame_producer_dependencies& dependencies,
+       const std::wstring& media_file,
+       const spl::shared_ptr<core::media_info_repository>& info_repo)
+{
+       auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
+       auto filename = probe_stem(env::media_folder() + L"/" + media_file, true);
+
+       if (filename.empty())
+               return core::draw_frame::empty();
+
+       auto render_specific_frame = [&](std::int64_t frame_num)
+       {
+               spl::shared_ptr<core::frame_producer> producer = spl::make_shared<ffmpeg_producer>(
+                               dependencies.frame_factory,
+                               dependencies.format_desc,
+                               L"",
+                               filename,
+                               L"",
+                               false,
+                               static_cast<uint32_t>(frame_num),
+                               std::numeric_limits<uint32_t>::max(),
+                               true,
+                               info_repo->get(filename));
+               return producer->receive();
+       };
+
+       auto info = info_repo->get(filename);
+
+       if (!info)
+               return core::draw_frame::empty();
+
+       auto total_frames = info->duration;
+       auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);
+
+       if (grid < 1)
+       {
+               CASPAR_LOG(error) << L"configuration/thumbnails/video-grid cannot be less than 1";
+               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1"));
+       }
+
+       if (grid == 1)
+       {
+               return render_specific_frame(total_frames / 2);
+       }
+
+       auto num_snapshots = grid * grid;
+
+       std::vector<core::draw_frame> frames;
+
+       for (int i = 0; i < num_snapshots; ++i)
+       {
+               int x = i % grid;
+               int y = i / grid;
+               std::int64_t desired_frame;
+
+               if (i == 0)
+                       desired_frame = 0; // first
+               else if (i == num_snapshots - 1)
+                       desired_frame = total_frames - 30; // last
+               else
+                       // evenly distributed across the file.
+                       desired_frame = total_frames * i / (num_snapshots - 1);
+
+               auto frame = render_specific_frame(desired_frame);
+               frame.transform().image_transform.fill_scale[0] = 1.0 / static_cast<double>(grid);
+               frame.transform().image_transform.fill_scale[1] = 1.0 / static_cast<double>(grid);
+               frame.transform().image_transform.fill_translation[0] = 1.0 / static_cast<double>(grid) * x;
+               frame.transform().image_transform.fill_translation[1] = 1.0 / static_cast<double>(grid) * y;
+
+               frames.push_back(frame);
+       }
+
+       return core::draw_frame(frames);
 }
 
 }}