Hand-apply 80562fe5dce5fd625da583ca6f7c2833f9db8754 from master (remove default scale...
authorCarl Hetherington <cth@carlh.net>
Tue, 11 Nov 2014 00:04:02 +0000 (00:04 +0000)
committerCarl Hetherington <cth@carlh.net>
Tue, 11 Nov 2014 00:04:02 +0000 (00:04 +0000)
14 files changed:
ChangeLog
src/lib/config.cc
src/lib/config.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/ratio.cc
src/lib/ratio.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_examiner.h
src/wx/config_dialog.cc
test/wscript

index 47aea47..729ce56 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2014-11-10  Carl Hetherington  <cth@carlh.net>
+
+       * Guess initial scale from the size of video
+       content images, taking pixel aspect ratio into
+       account where possible.
+
 2014-11-07  c.hetherington  <cth@carlh.net>
 
        * Add a hint if there is 3D content in a proposed 2D DCP.
index 79ab4e9..5f48262 100644 (file)
@@ -67,7 +67,6 @@ Config::Config ()
        , _cinema_sound_processor (CinemaSoundProcessor::from_id (N_("dolby_cp750")))
        , _allow_any_dcp_frame_rate (false)
        , _default_still_length (10)
-       , _default_scale (VideoContentScale (Ratio::from_id ("185")))
        , _default_container (Ratio::from_id ("185"))
        , _default_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"))
        , _default_j2k_bandwidth (100000000)
@@ -148,11 +147,6 @@ Config::read ()
 
        _language = f.optional_string_child ("Language");
 
-       c = f.optional_string_child ("DefaultScale");
-       if (c) {
-               _default_scale = VideoContentScale::from_id (c.get ());
-       }
-
        c = f.optional_string_child ("DefaultContainer");
        if (c) {
                _default_container = Ratio::from_id (c.get ());
@@ -331,7 +325,6 @@ Config::write () const
        if (_language) {
                root->add_child("Language")->add_child_text (_language.get());
        }
-       root->add_child("DefaultScale")->add_child_text (_default_scale.id ());
        if (_default_container) {
                root->add_child("DefaultContainer")->add_child_text (_default_container->id ());
        }
index 55a172d..28b1dbd 100644 (file)
@@ -135,10 +135,6 @@ public:
                return _default_still_length;
        }
 
-       VideoContentScale default_scale () const {
-               return _default_scale;
-       }
-
        Ratio const * default_container () const {
                return _default_container;
        }
@@ -314,11 +310,6 @@ public:
                changed ();
        }
 
-       void set_default_scale (VideoContentScale s) {
-               _default_scale = s;
-               changed ();
-       }
-
        void set_default_container (Ratio const * c) {
                _default_container = c;
                changed ();
@@ -482,7 +473,6 @@ private:
        ISDCFMetadata _default_isdcf_metadata;
        boost::optional<std::string> _language;
        int _default_still_length;
-       VideoContentScale _default_scale;
        Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
        std::string _dcp_issuer;
index 48d85da..46e93b1 100644 (file)
@@ -180,6 +180,13 @@ FFmpegExaminer::video_length () const
        return ContentTime (max (ContentTime::Type (1), length.get ()));
 }
 
+optional<float>
+FFmpegExaminer::sample_aspect_ratio () const
+{
+       AVRational sar = av_guess_sample_aspect_ratio (_format_context, _format_context->streams[_video_stream], 0);
+       return float (sar.num) / sar.den;
+}
+
 string
 FFmpegExaminer::audio_stream_name (AVStream* s) const
 {
index 8c31eb2..59c3299 100644 (file)
@@ -32,6 +32,7 @@ public:
        float video_frame_rate () const;
        dcp::Size video_size () const;
        ContentTime video_length () const;
+       boost::optional<float> sample_aspect_ratio () const;
 
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
                return _subtitle_streams;
index fc36415..bc83ae8 100644 (file)
@@ -73,3 +73,19 @@ Ratio::from_ratio (float r)
        return *j;
 }
    
+Ratio const *
+Ratio::nearest_from_ratio (float r)
+{
+       Ratio const * nearest = 0;
+       float distance = FLT_MAX;
+       
+       for (vector<Ratio const *>::iterator i = _ratios.begin (); i != _ratios.end(); ++i) {
+               float const d = fabs ((*i)->ratio() - r);
+               if (d < distance) {
+                       distance = d;
+                       nearest = *i;
+               }
+       }
+
+       return nearest;
+}
index 69e3726..f1a180d 100644 (file)
@@ -53,6 +53,7 @@ public:
        static void setup_ratios ();
        static Ratio const * from_id (std::string i);
        static Ratio const * from_ratio (float r);
+       static Ratio const * nearest_from_ratio (float r);
        static std::vector<Ratio const *> all () {
                return _ratios;
        }
index 3470e62..8e07174 100644 (file)
@@ -66,7 +66,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
        , _video_length (0)
        , _video_frame_rate (0)
        , _video_frame_type (VIDEO_FRAME_TYPE_2D)
-       , _scale (Config::instance()->default_scale ())
+       , _scale (VideoContentScale (Ratio::from_id ("178")))
 {
        set_default_colour_conversion (false);
 }
@@ -76,7 +76,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len
        , _video_length (len)
        , _video_frame_rate (0)
        , _video_frame_type (VIDEO_FRAME_TYPE_2D)
-       , _scale (Config::instance()->default_scale ())
+       , _scale (VideoContentScale (Ratio::from_id ("178")))
 {
        set_default_colour_conversion (false);
 }
@@ -86,7 +86,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
        , _video_length (0)
        , _video_frame_rate (0)
        , _video_frame_type (VIDEO_FRAME_TYPE_2D)
-       , _scale (Config::instance()->default_scale ())
+       , _scale (VideoContentScale (Ratio::from_id ("178")))
 {
        set_default_colour_conversion (false);
 }
@@ -106,6 +106,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, i
        }
        
        _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType"));
+       _sample_aspect_ratio = node->optional_number_child<float> ("SampleAspectRatio");
        _crop.left = node->number_child<int> ("LeftCrop");
        _crop.right = node->number_child<int> ("RightCrop");
        _crop.top = node->number_child<int> ("TopCrop");
@@ -190,6 +191,9 @@ VideoContent::as_xml (xmlpp::Node* node) const
        node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height));
        node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
        node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type)));
+       if (_sample_aspect_ratio) {
+               node->add_child("SampleAspectRatio")->add_child_text (raw_convert<string> (_sample_aspect_ratio.get ()));
+       }
        _crop.as_xml (node);
        _scale.as_xml (node->add_child("Scale"));
        if (_colour_conversion) {
@@ -219,12 +223,19 @@ VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
        dcp::Size const vs = d->video_size ();
        float const vfr = d->video_frame_rate ();
        ContentTime vl = d->video_length ();
+       optional<float> const ar = d->sample_aspect_ratio ();
 
        {
                boost::mutex::scoped_lock lm (_mutex);
                _video_size = vs;
                _video_frame_rate = vfr;
                _video_length = vl;
+               _sample_aspect_ratio = ar;
+
+               /* Guess correct scale from size and sample aspect ratio */
+               _scale = VideoContentScale (
+                       Ratio::nearest_from_ratio (float (_video_size.width) * ar.get_value_or (1) / _video_size.height)
+                       );
        }
 
        shared_ptr<const Film> film = _film.lock ();
@@ -233,6 +244,7 @@ VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
        
        signal_changed (VideoContentProperty::VIDEO_SIZE);
        signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
+       signal_changed (VideoContentProperty::VIDEO_SCALE);
        signal_changed (ContentProperty::LENGTH);
 }
 
@@ -252,6 +264,10 @@ VideoContent::information () const
                video_size().height,
                setprecision (3), video_size().ratio ()
                );
+
+       if (sample_aspect_ratio ()) {
+               s << String::compose (_(" sample aspect ratio %1:1"), sample_aspect_ratio().get ());
+       }
        
        return s.str ();
 }
@@ -365,13 +381,19 @@ VideoContent::set_video_frame_type (VideoFrameType t)
 string
 VideoContent::technical_summary () const
 {
-       return String::compose (
+       string s = String::compose (
                "video: length %1, size %2x%3, rate %4",
                video_length_after_3d_combine().seconds(),
                video_size().width,
                video_size().height,
                video_frame_rate()
                );
+
+       if (sample_aspect_ratio ()) {
+               s += String::compose (_(", sample aspect ratio %1"), (sample_aspect_ratio().get ()));
+       }
+
+       return s;
 }
 
 dcp::Size
index 64112c1..30dcac2 100644 (file)
@@ -137,6 +137,11 @@ public:
                return _colour_conversion;
        }
 
+       boost::optional<float> sample_aspect_ratio () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _sample_aspect_ratio;
+       }
+
        ContentTime fade_in () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _fade_in;
@@ -178,6 +183,10 @@ private:
        Crop _crop;
        VideoContentScale _scale;
        boost::optional<ColourConversion> _colour_conversion;
+       /** Sample aspect ratio obtained from the content file's header,
+           if there is one.
+       */
+       boost::optional<float> _sample_aspect_ratio;
        ContentTime _fade_in;
        ContentTime _fade_out;
 };
index 64c66ea..9b27055 100644 (file)
@@ -20,6 +20,7 @@
 #include "video_decoder.h"
 #include "image.h"
 #include "image_proxy.h"
+#include "raw_image_proxy.h"
 #include "content_video.h"
 
 #include "i18n.h"
@@ -27,6 +28,7 @@
 using std::cout;
 using std::list;
 using std::max;
+using std::back_inserter;
 using boost::shared_ptr;
 using boost::optional;
 
@@ -39,7 +41,8 @@ VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
 #endif
        , _same (false)
 {
-
+       _black_image.reset (new Image (PIX_FMT_RGB24, _video_content->video_size(), true));
+       _black_image->make_black ();
 }
 
 list<ContentVideo>
@@ -113,7 +116,8 @@ VideoDecoder::get_video (VideoFrame frame, bool accurate)
                }
        }
 
-       /* Clean up _decoded_video; keep the frame we are returning, but nothing before that */
+       /* Clean up _decoded_video; keep the frame we are returning (which may have two images
+          for 3D), but nothing before that */
        while (!_decoded_video.empty() && _decoded_video.front().frame < dec.front().frame) {
                _decoded_video.pop_front ();
        }
@@ -121,57 +125,156 @@ VideoDecoder::get_video (VideoFrame frame, bool accurate)
        return dec;
 }
 
+/** Fill _decoded_video up to, but not including, the specified frame */
+void
+VideoDecoder::fill_up_to_2d (VideoFrame frame)
+{
+       if (frame == 0) {
+               /* Already OK */
+               return;
+       }
 
-/** Called by subclasses when they have a video frame ready */
+       /* Fill with black... */
+       boost::shared_ptr<const ImageProxy> filler_image (new RawImageProxy (_black_image));
+       Part filler_part = PART_WHOLE;
+
+       /* ...unless there's some video we can fill with */
+       if (!_decoded_video.empty ()) {
+               filler_image = _decoded_video.back().image;
+               filler_part = _decoded_video.back().part;
+       }
+
+       VideoFrame filler_frame = _decoded_video.empty() ? 0 : (_decoded_video.back().frame + 1);
+       while (filler_frame < frame) {
+
+#ifdef DCPOMATIC_DEBUG
+               test_gaps++;
+#endif
+
+               _decoded_video.push_back (
+                       ContentVideo (filler_image, EYES_BOTH, filler_part, filler_frame)
+                       );
+               
+               ++filler_frame;
+       }
+}
+
+/** Fill _decoded_video up to, but not including, the specified frame and eye */
 void
-VideoDecoder::video (shared_ptr<const ImageProxy> image, VideoFrame frame)
+VideoDecoder::fill_up_to_3d (VideoFrame frame, Eyes eye)
 {
-       /* We may receive the same frame index twice for 3D, and we need to know
-          when that happens.
-       */
-       _same = (!_decoded_video.empty() && frame == _decoded_video.back().frame);
+       if (frame == 0 && eye == EYES_LEFT) {
+               /* Already OK */
+               return;
+       }
+
+       /* Fill with black... */
+       boost::shared_ptr<const ImageProxy> filler_left_image (new RawImageProxy (_black_image));
+       boost::shared_ptr<const ImageProxy> filler_right_image (new RawImageProxy (_black_image));
+       Part filler_left_part = PART_WHOLE;
+       Part filler_right_part = PART_WHOLE;
+
+       /* ...unless there's some video we can fill with */
+       for (list<ContentVideo>::const_reverse_iterator i = _decoded_video.rbegin(); i != _decoded_video.rend(); ++i) {
+               if (i->eyes == EYES_LEFT && !filler_left_image) {
+                       filler_left_image = i->image;
+                       filler_left_part = i->part;
+               } else if (i->eyes == EYES_RIGHT && !filler_right_image) {
+                       filler_right_image = i->image;
+                       filler_right_part = i->part;
+               }
+
+               if (filler_left_image && filler_right_image) {
+                       break;
+               }
+       }
+
+       VideoFrame filler_frame = _decoded_video.empty() ? 0 : _decoded_video.back().frame;
+       Eyes filler_eye = _decoded_video.empty() ? EYES_LEFT : _decoded_video.back().eyes;
+
+       if (_decoded_video.empty ()) {
+               filler_frame = 0;
+               filler_eye = EYES_LEFT;
+       } else if (_decoded_video.back().eyes == EYES_LEFT) {
+               filler_frame = _decoded_video.back().frame;
+               filler_eye = EYES_RIGHT;
+       } else if (_decoded_video.back().eyes == EYES_RIGHT) {
+               filler_frame = _decoded_video.back().frame + 1;
+               filler_eye = EYES_LEFT;
+       }
 
-       /* Fill in gaps */
-       /* XXX: 3D */
+       while (filler_frame != frame || filler_eye != eye) {
 
-       while (!_decoded_video.empty () && (_decoded_video.back().frame + 1) < frame) {
 #ifdef DCPOMATIC_DEBUG
                test_gaps++;
 #endif
+
                _decoded_video.push_back (
                        ContentVideo (
-                               _decoded_video.back().image,
-                               _decoded_video.back().eyes,
-                               _decoded_video.back().part,
-                               _decoded_video.back().frame + 1
+                               filler_eye == EYES_LEFT ? filler_left_image : filler_right_image,
+                               filler_eye,
+                               filler_eye == EYES_LEFT ? filler_left_part : filler_right_part,
+                               filler_frame
                                )
                        );
+
+               if (filler_eye == EYES_LEFT) {
+                       filler_eye = EYES_RIGHT;
+               } else {
+                       filler_eye = EYES_LEFT;
+                       ++filler_frame;
+               }
        }
+}
        
+/** Called by subclasses when they have a video frame ready */
+void
+VideoDecoder::video (shared_ptr<const ImageProxy> image, VideoFrame frame)
+{
+       /* We may receive the same frame index twice for 3D, and we need to know
+          when that happens.
+       */
+       _same = (!_decoded_video.empty() && frame == _decoded_video.back().frame);
+
+       /* Work out what we are going to push into _decoded_video next */
+       list<ContentVideo> to_push;
        switch (_video_content->video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
-               _decoded_video.push_back (ContentVideo (image, EYES_BOTH, PART_WHOLE, frame));
+               to_push.push_back (ContentVideo (image, EYES_BOTH, PART_WHOLE, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_ALTERNATE:
-               _decoded_video.push_back (ContentVideo (image, _same ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, frame));
+               to_push.push_back (ContentVideo (image, _same ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
-               _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_LEFT_HALF, frame));
-               _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_RIGHT_HALF, frame));
+               to_push.push_back (ContentVideo (image, EYES_LEFT, PART_LEFT_HALF, frame));
+               to_push.push_back (ContentVideo (image, EYES_RIGHT, PART_RIGHT_HALF, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
-               _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_TOP_HALF, frame));
-               _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_BOTTOM_HALF, frame));
+               to_push.push_back (ContentVideo (image, EYES_LEFT, PART_TOP_HALF, frame));
+               to_push.push_back (ContentVideo (image, EYES_RIGHT, PART_BOTTOM_HALF, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_LEFT:
-               _decoded_video.push_back (ContentVideo (image, EYES_LEFT, PART_WHOLE, frame));
+               to_push.push_back (ContentVideo (image, EYES_LEFT, PART_WHOLE, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_RIGHT:
-               _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, PART_WHOLE, frame));
+               to_push.push_back (ContentVideo (image, EYES_RIGHT, PART_WHOLE, frame));
                break;
        default:
                assert (false);
        }
+
+       /* Now VideoDecoder is required never to have gaps in the frames that it presents
+          via get_video().  Hence we need to fill in any gap between the last thing in _decoded_video
+          and the things we are about to push.
+       */
+
+       if (_video_content->video_frame_type() == VIDEO_FRAME_TYPE_2D) {
+               fill_up_to_2d (to_push.front().frame);
+       } else {
+               fill_up_to_3d (to_push.front().frame, to_push.front().eyes);
+       }
+
+       copy (to_push.begin(), to_push.end(), back_inserter (_decoded_video));
 }
 
 void
index f5c3cd7..9e56546 100644 (file)
@@ -33,6 +33,7 @@
 
 class VideoContent;
 class ImageProxy;
+class Image;
 
 /** @class VideoDecoder
  *  @brief Parent for classes which decode video.
@@ -53,14 +54,19 @@ public:
 #endif
 
 protected:
+       friend struct video_decoder_fill_test1;
+       friend struct video_decoder_fill_test2;
 
        void seek (ContentTime time, bool accurate);
        void video (boost::shared_ptr<const ImageProxy>, VideoFrame frame);
        std::list<ContentVideo> decoded_video (VideoFrame frame);
+       void fill_up_to_2d (VideoFrame);
+       void fill_up_to_3d (VideoFrame, Eyes);
 
        boost::shared_ptr<const VideoContent> _video_content;
        std::list<ContentVideo> _decoded_video;
        bool _same;
+       boost::shared_ptr<Image> _black_image;
 };
 
 #endif
index 87e9a04..de79665 100644 (file)
@@ -35,4 +35,7 @@ public:
        virtual float video_frame_rate () const = 0;
        virtual dcp::Size video_size () const = 0;
        virtual ContentTime video_length () const = 0;
+       virtual boost::optional<float> sample_aspect_ratio () const {
+               return boost::optional<float> ();
+       }
 };
index 8d8f44b..c844d82 100644 (file)
@@ -277,10 +277,6 @@ public:
                _isdcf_metadata_button = new wxButton (panel, wxID_ANY, _("Edit..."));
                table->Add (_isdcf_metadata_button);
 
-               add_label_to_sizer (table, panel, _("Default scale to"), true);
-               _scale = new wxChoice (panel, wxID_ANY);
-               table->Add (_scale);
-               
                add_label_to_sizer (table, panel, _("Default container"), true);
                _container = new wxChoice (panel, wxID_ANY);
                table->Add (_container);
@@ -322,14 +318,6 @@ public:
                
                _isdcf_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this, parent));
                
-               vector<VideoContentScale> scales = VideoContentScale::all ();
-               for (size_t i = 0; i < scales.size(); ++i) {
-                       _scale->Append (std_to_wx (scales[i].name ()));
-                       if (scales[i] == config->default_scale ()) {
-                               _scale->SetSelection (i);
-                       }
-               }
-
                vector<Ratio const *> ratios = Ratio::all ();
                for (size_t i = 0; i < ratios.size(); ++i) {
                        _container->Append (std_to_wx (ratios[i]->nickname ()));
@@ -338,7 +326,6 @@ public:
                        }
                }
                
-               _scale->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::scale_changed, this));
                _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
                
                vector<DCPContentType const *> const ct = DCPContentType::all ();
@@ -394,12 +381,6 @@ private:
                Config::instance()->set_default_still_length (_still_length->GetValue ());
        }
 
-       void scale_changed ()
-       {
-               vector<VideoContentScale> scale = VideoContentScale::all ();
-               Config::instance()->set_default_scale (scale[_scale->GetSelection()]);
-       }
-       
        void container_changed ()
        {
                vector<Ratio const *> ratio = Ratio::all ();
@@ -426,7 +407,6 @@ private:
 #else
        wxDirPickerCtrl* _directory;
 #endif
-       wxChoice* _scale;
        wxChoice* _container;
        wxChoice* _dcp_content_type;
        wxTextCtrl* _issuer;
index 4571641..1593aac 100644 (file)
@@ -58,6 +58,7 @@ def build(bld):
                  threed_test.cc
                  upmixer_a_test.cc
                  util_test.cc
+                 video_decoder_fill_test.cc
                  xml_subtitle_test.cc
                  """