Untested; allow viewing of subtitles or closed captions in the preview.
authorCarl Hetherington <cth@carlh.net>
Fri, 20 Jul 2018 18:55:22 +0000 (19:55 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 20 Jul 2018 18:55:22 +0000 (19:55 +0100)
src/lib/active_captions.cc
src/lib/active_captions.h
src/lib/ffmpeg_encoder.cc
src/lib/player.cc
src/lib/player.h
src/lib/player_video.cc
src/lib/player_video.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
test/client_server_test.cc
test/player_test.cc

index d41270382c7cf08785c7c32f6b50d1296fa44c1d..bb64f995dbd96cdbda6320509faa45b63c5c11f6 100644 (file)
@@ -33,10 +33,10 @@ using boost::optional;
 
 /** Get the subtitles that should be burnt into a given period.
  *  @param period Period of interest.
- *  @param always_burn_subtitles Always burn subtitles even if their content is not set to burn.
+ *  @param always_burn_captions Always burn captions even if their content is not set to burn.
  */
 list<PlayerCaption>
-ActiveCaptions::get_burnt (DCPTimePeriod period, bool always_burn_subtitles) const
+ActiveCaptions::get_burnt (DCPTimePeriod period, bool always_burn_captions) const
 {
        list<PlayerCaption> ps;
 
@@ -47,7 +47,7 @@ ActiveCaptions::get_burnt (DCPTimePeriod period, bool always_burn_subtitles) con
                        continue;
                }
 
-               if (!piece->content->caption->use() || (!always_burn_subtitles && !piece->content->caption->burn())) {
+               if (!piece->content->caption->use() || (!always_burn_captions && !piece->content->caption->burn())) {
                        /* Not burning this piece */
                        continue;
                }
index 718ba393e244624281463d7685edb61b500f1c67..e0e8acf8ebea9daf33e0a951ea05cc8df9d2c666 100644 (file)
@@ -36,7 +36,7 @@ class Piece;
 class ActiveCaptions : public boost::noncopyable
 {
 public:
-       std::list<PlayerCaption> get_burnt (DCPTimePeriod period, bool always_burn_subtitles) const;
+       std::list<PlayerCaption> get_burnt (DCPTimePeriod period, bool always_burn_captions) const;
        void clear_before (DCPTime time);
        void clear ();
        void add_from (boost::weak_ptr<Piece> piece, PlayerCaption ps, DCPTime from);
index cc259149859c171b0e8f8feecc29155efe1ea7e4..10d439f94f05b0f2954c39ef0799b98cf28e3454 100644 (file)
@@ -72,7 +72,7 @@ FFmpegEncoder::FFmpegEncoder (shared_ptr<const Film> film, weak_ptr<Job> job, bo
                break;
        }
 
-       _player->set_always_burn_subtitles (true);
+       _player->set_always_burn_captions (CAPTION_OPEN);
        _player->set_play_referenced ();
 
        int const ch = film->audio_channels ();
index 7040bd530cf8b520f8e7b41d1dc97e364902279d..719e59acc9ab4e7a062eed99a06a33d772f6aea4 100644 (file)
@@ -89,7 +89,6 @@ Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist
        , _have_valid_pieces (false)
        , _ignore_video (false)
        , _ignore_subtitle (false)
-       , _always_burn_subtitles (false)
        , _fast (false)
        , _play_referenced (false)
        , _audio_merger (_film->audio_frame_rate())
@@ -431,14 +430,14 @@ Player::set_ignore_subtitle ()
        _ignore_subtitle = true;
 }
 
-/** Set whether or not this player should always burn text subtitles into the image,
+/** Set a type of caption that this player should always burn into the image,
  *  regardless of the content settings.
- *  @param burn true to always burn subtitles, false to obey content settings.
+ *  @param type type of captions to burn.
  */
 void
-Player::set_always_burn_subtitles (bool burn)
+Player::set_always_burn_captions (CaptionType type)
 {
-       _always_burn_subtitles = burn;
+       _always_burn_captions = type;
 }
 
 /** Sets up the player to be faster, possibly at the expense of quality */
@@ -657,30 +656,36 @@ Player::pass ()
 }
 
 optional<PositionImage>
-Player::subtitles_for_frame (DCPTime time) const
+Player::captions_for_frame (DCPTime time) const
 {
-       list<PositionImage> subtitles;
+       list<PositionImage> captions;
 
        int const vfr = _film->video_frame_rate();
 
-       BOOST_FOREACH (PlayerCaption i, _active_captions[CAPTION_OPEN].get_burnt (DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_subtitles)) {
-
-               /* Image subtitles */
-               list<PositionImage> c = transform_bitmap_captions (i.image);
-               copy (c.begin(), c.end(), back_inserter (subtitles));
-
-               /* Text subtitles (rendered to an image) */
-               if (!i.text.empty ()) {
-                       list<PositionImage> s = render_text (i.text, i.fonts, _video_container_size, time, vfr);
-                       copy (s.begin(), s.end(), back_inserter (subtitles));
+       for (int i = 0; i < CAPTION_COUNT; ++i) {
+               bool const always = _always_burn_captions && *_always_burn_captions == i;
+               BOOST_FOREACH (
+                       PlayerCaption j,
+                       _active_captions[i].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), always)
+                       ) {
+
+                       /* Image subtitles */
+                       list<PositionImage> c = transform_bitmap_captions (j.image);
+                       copy (c.begin(), c.end(), back_inserter (captions));
+
+                       /* Text subtitles (rendered to an image) */
+                       if (!j.text.empty ()) {
+                               list<PositionImage> s = render_text (j.text, j.fonts, _video_container_size, time, vfr);
+                               copy (s.begin(), s.end(), back_inserter (captions));
+                       }
                }
        }
 
-       if (subtitles.empty ()) {
+       if (captions.empty ()) {
                return optional<PositionImage> ();
        }
 
-       return merge (subtitles);
+       return merge (captions);
 }
 
 void
@@ -928,7 +933,8 @@ Player::subtitle_stop (weak_ptr<Piece> wp, ContentTime to, CaptionType type)
 
        pair<PlayerCaption, DCPTime> from = _active_captions[type].add_to (wp, dcp_to);
 
-       if (piece->content->caption->use() && !_always_burn_subtitles && !piece->content->caption->burn()) {
+       bool const always = _always_burn_captions && *_always_burn_captions == type;
+       if (piece->content->caption->use() && !always && !piece->content->caption->burn()) {
                Caption (from.first, type, DCPTimePeriod (from.second, dcp_to));
        }
 }
@@ -1017,9 +1023,9 @@ Player::do_emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
                }
        }
 
-       optional<PositionImage> subtitles = subtitles_for_frame (time);
-       if (subtitles) {
-               pv->set_subtitle (subtitles.get ());
+       optional<PositionImage> captions = captions_for_frame (time);
+       if (captions) {
+               pv->set_caption (captions.get ());
        }
 
        Video (pv, time);
index 5b6a0b7b4c5ed1276b56725f41c6256b74411f0c..cbadb11f658f5a82f509c04304241fe4a703f604 100644 (file)
@@ -79,7 +79,7 @@ public:
        void set_video_container_size (dcp::Size);
        void set_ignore_video ();
        void set_ignore_subtitle ();
-       void set_always_burn_subtitles (bool burn);
+       void set_always_burn_captions (CaptionType type);
        void set_fast ();
        void set_play_referenced ();
        void set_dcp_decode_reduction (boost::optional<int> reduction);
@@ -134,7 +134,7 @@ private:
        std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> discard_audio (
                boost::shared_ptr<const AudioBuffers> audio, DCPTime time, DCPTime discard_to
                ) const;
-       boost::optional<PositionImage> subtitles_for_frame (DCPTime time) const;
+       boost::optional<PositionImage> captions_for_frame (DCPTime time) const;
        void emit_video (boost::shared_ptr<PlayerVideo> pv, DCPTime time);
        void do_emit_video (boost::shared_ptr<PlayerVideo> pv, DCPTime time);
        void emit_audio (boost::shared_ptr<AudioBuffers> data, DCPTime time);
@@ -154,10 +154,10 @@ private:
        bool _ignore_video;
        /** true if the player should ignore all audio; i.e. never produce any */
        bool _ignore_subtitle;
-       /** true if the player should always burn subtitles into the video regardless
-           of content settings
+       /** Type of captions that the player should always burn into the video regardless
+           of content settings.
        */
-       bool _always_burn_subtitles;
+       boost::optional<CaptionType> _always_burn_captions;
        /** true if we should try to be fast rather than high quality */
        bool _fast;
        /** true if we should `play' (i.e output) referenced DCP data (e.g. for preview) */
index a50b196a200152caf782a108c45eb1fdaddc5fbc..c8fb044aa191d9c8b766fd35a64d5c0c8e07463d 100644 (file)
@@ -92,14 +92,14 @@ PlayerVideo::PlayerVideo (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket
 
                image->read_from_socket (socket);
 
-               _subtitle = PositionImage (image, Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")));
+               _caption = PositionImage (image, Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")));
        }
 }
 
 void
-PlayerVideo::set_subtitle (PositionImage image)
+PlayerVideo::set_caption (PositionImage image)
 {
-       _subtitle = image;
+       _caption = image;
 }
 
 /** Create an image for this frame.
@@ -153,8 +153,8 @@ PlayerVideo::image (dcp::NoteHandler note, function<AVPixelFormat (AVPixelFormat
                total_crop, _inter_size, _out_size, yuv_to_rgb, pixel_format (_in->pixel_format()), aligned, fast
                );
 
-       if (_subtitle) {
-               out->alpha_blend (Image::ensure_aligned (_subtitle->image), _subtitle->position);
+       if (_caption) {
+               out->alpha_blend (Image::ensure_aligned (_caption->image), _caption->position);
        }
 
        if (_fade) {
@@ -181,11 +181,11 @@ PlayerVideo::add_metadata (xmlpp::Node* node) const
        if (_colour_conversion) {
                _colour_conversion.get().as_xml (node);
        }
-       if (_subtitle) {
-               node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle->image->size().width));
-               node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle->image->size().height));
-               node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle->position.x));
-               node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle->position.y));
+       if (_caption) {
+               node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_caption->image->size().width));
+               node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_caption->image->size().height));
+               node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_caption->position.x));
+               node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_caption->position.y));
        }
 }
 
@@ -193,8 +193,8 @@ void
 PlayerVideo::send_binary (shared_ptr<Socket> socket) const
 {
        _in->send_binary (socket);
-       if (_subtitle) {
-               _subtitle->image->write_to_socket (socket);
+       if (_caption) {
+               _caption->image->write_to_socket (socket);
        }
 }
 
@@ -208,7 +208,7 @@ PlayerVideo::has_j2k () const
                return false;
        }
 
-       return _crop == Crop () && _out_size == j2k->size() && !_subtitle && !_fade && !_colour_conversion;
+       return _crop == Crop () && _out_size == j2k->size() && !_caption && !_fade && !_colour_conversion;
 }
 
 Data
@@ -239,13 +239,13 @@ PlayerVideo::same (shared_ptr<const PlayerVideo> other) const
                return false;
        }
 
-       if ((!_subtitle && other->_subtitle) || (_subtitle && !other->_subtitle)) {
-               /* One has a subtitle and the other doesn't */
+       if ((!_caption && other->_caption) || (_caption && !other->_caption)) {
+               /* One has a caption and the other doesn't */
                return false;
        }
 
-       if (_subtitle && other->_subtitle && !_subtitle->same (other->_subtitle.get ())) {
-               /* They both have subtitles but they are different */
+       if (_caption && other->_caption && !_caption->same (other->_caption.get ())) {
+               /* They both have captions but they are different */
                return false;
        }
 
@@ -278,7 +278,7 @@ PlayerVideo::memory_used () const
        return _in->memory_used();
 }
 
-/** @return Shallow copy of this; _in and _subtitle are shared between the original and the copy */
+/** @return Shallow copy of this; _in and _caption are shared between the original and the copy */
 shared_ptr<PlayerVideo>
 PlayerVideo::shallow_copy () const
 {
index f4bf2a47165be5dbc58b35a3f95e752db7e81ab6..96878e0bd4a51198b55ebe49218510cb42e47ed0 100644 (file)
@@ -60,7 +60,7 @@ public:
 
        boost::shared_ptr<PlayerVideo> shallow_copy () const;
 
-       void set_subtitle (PositionImage);
+       void set_caption (PositionImage);
 
        void prepare ();
        boost::shared_ptr<Image> image (dcp::NoteHandler note, boost::function<AVPixelFormat (AVPixelFormat)> pixel_format, bool aligned, bool fast) const;
@@ -109,7 +109,7 @@ private:
        Eyes _eyes;
        Part _part;
        boost::optional<ColourConversion> _colour_conversion;
-       boost::optional<PositionImage> _subtitle;
+       boost::optional<PositionImage> _caption;
        /** Content that we came from.  This is so that reset_metadata() can work */
        boost::weak_ptr<Content> _content;
        /** Video frame that we came from.  Again, this is for reset_metadata() */
index 1e615da6f948be683008215f54819fc8051f84da..56342d44f018bab10f90a824596af4fb431dbffc 100644 (file)
@@ -76,8 +76,7 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        : wxPanel (p)
        , _panel (new wxPanel (this))
        , _outline_content (0)
-       , _left_eye (new wxRadioButton (this, wxID_ANY, _("Left eye"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP))
-       , _right_eye (new wxRadioButton (this, wxID_ANY, _("Right eye")))
+       , _eye (0)
        , _jump_to_selected (0)
        , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
        , _rewind_button (new wxButton (this, wxID_ANY, wxT("|<")))
@@ -110,14 +109,26 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
        if (outline_content) {
                _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
-               view_options->Add (_outline_content, 0, wxRIGHT, DCPOMATIC_SIZER_GAP);
+               view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
        }
-       view_options->Add (_left_eye, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
-       view_options->Add (_right_eye, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+
+       _eye = new wxChoice (this, wxID_ANY);
+       _eye->Append (_("Left"));
+       _eye->Append (_("Right"));
+       _eye->SetSelection (0);
+       view_options->Add (_eye, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
+
        if (jump_to_selected) {
                _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
-               view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+               view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
        }
+
+       _captions = new wxChoice (this, wxID_ANY);
+       _captions->Append (_("Open captions (subtitles)"));
+       _captions->Append (_("Closed captions"));
+       _captions->SetSelection (0);
+       view_options->Add (_captions, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+
        _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
 
        wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
@@ -145,8 +156,8 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        if (_outline_content) {
                _outline_content->Bind  (wxEVT_CHECKBOX, boost::bind (&FilmViewer::refresh_panel,   this));
        }
-       _left_eye->Bind         (wxEVT_RADIOBUTTON,       boost::bind (&FilmViewer::slow_refresh,    this));
-       _right_eye->Bind        (wxEVT_RADIOBUTTON,       boost::bind (&FilmViewer::slow_refresh,    this));
+       _eye->Bind              (wxEVT_CHOICE,            boost::bind (&FilmViewer::slow_refresh,    this));
+       _captions->Bind         (wxEVT_CHOICE,            boost::bind (&FilmViewer::captions_changed, this));
        _slider->Bind           (wxEVT_SCROLL_THUMBTRACK, boost::bind (&FilmViewer::slider_moved,    this, false));
        _slider->Bind           (wxEVT_SCROLL_PAGEUP,     boost::bind (&FilmViewer::slider_moved,    this, true));
        _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,   boost::bind (&FilmViewer::slider_moved,    this, true));
@@ -214,10 +225,8 @@ FilmViewer::set_film (shared_ptr<Film> film)
                return;
        }
 
-       /* Always burn in subtitles, even if content is set not to, otherwise we won't see them
-          in the preview.
-       */
-       _player->set_always_burn_subtitles (true);
+       /* Start off burning in subtitles, as that's the initial setting of the dropdown */
+       _player->set_always_burn_captions (CAPTION_OPEN);
        _player->set_play_referenced ();
 
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
@@ -291,7 +300,7 @@ FilmViewer::get ()
                _player_video = _butler->get_video ();
        } while (
                _film->three_d() &&
-               ((_left_eye->GetValue() && _player_video.first->eyes() == EYES_RIGHT) || (_right_eye->GetValue() && _player_video.first->eyes() == EYES_LEFT))
+               ((_eye->GetSelection() == 0 && _player_video.first->eyes() == EYES_RIGHT) || (_eye->GetSelection() == 1 && _player_video.first->eyes() == EYES_LEFT))
                );
 
        _butler->rethrow ();
@@ -708,8 +717,7 @@ FilmViewer::setup_sensitivity ()
                _jump_to_selected->Enable (c);
        }
 
-       _left_eye->Enable (c && _film->three_d ());
-       _right_eye->Enable (c && _film->three_d ());
+       _eye->Enable (c && _film->three_d ());
 }
 
 void
@@ -722,6 +730,21 @@ FilmViewer::film_changed (Film::Property p)
        }
 }
 
+void
+FilmViewer::captions_changed ()
+{
+       switch (_captions->GetSelection()) {
+       case 0:
+               _player->set_always_burn_captions (CAPTION_OPEN);
+               break;
+       case 1:
+               _player->set_always_burn_captions (CAPTION_CLOSED);
+               break;
+       }
+
+       slow_refresh ();
+}
+
 /** Re-get the current frame slowly by seeking */
 void
 FilmViewer::slow_refresh ()
index 11d06cc4643c329e8626bb156f7afa202276697c..cb372391ad0306a16c4f614555af5b0e4b327891 100644 (file)
@@ -87,6 +87,7 @@ private:
        void slider_released ();
        void play_clicked ();
        void timer ();
+       void captions_changed ();
        void calculate_sizes ();
        void check_play_state ();
        void active_jobs_changed (boost::optional<std::string>);
@@ -120,9 +121,9 @@ private:
        /** The area that we put our image in */
        wxPanel* _panel;
        wxCheckBox* _outline_content;
-       wxRadioButton* _left_eye;
-       wxRadioButton* _right_eye;
+       wxChoice* _eye;
        wxCheckBox* _jump_to_selected;
+       wxChoice* _captions;
        wxSlider* _slider;
        wxButton* _rewind_button;
        wxButton* _back_button;
index d77ed4c15e4462bf0132895b192957e002f86aea..74f0cae59c8dadb932b78dc135f94dbf0ecca94c 100644 (file)
@@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_rgb)
                        )
                );
 
-       pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
+       pvf->set_caption (PositionImage (sub_image, Position<int> (50, 60)));
 
        shared_ptr<DCPVideo> frame (
                new DCPVideo (
@@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE (client_server_test_yuv)
                        )
                );
 
-       pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
+       pvf->set_caption (PositionImage (sub_image, Position<int> (50, 60)));
 
        shared_ptr<DCPVideo> frame (
                new DCPVideo (
index 4ff79b436d0fd2436d123381fc32d560adfdf436..510083236f294cc1e56a0ec4dc5de7715d6b0959 100644 (file)
@@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test)
 
        shared_ptr<Player> player (new Player (film, film->playlist()));
        player->set_fast ();
-       player->set_always_burn_subtitles (true);
+       player->set_always_burn_captions (CAPTION_OPEN);
        player->set_play_referenced ();
 
        shared_ptr<Butler> butler (new Butler (player, film->log(), AudioMapping(), 2));
@@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test2)
 
        shared_ptr<Player> player (new Player (film, film->playlist()));
        player->set_fast ();
-       player->set_always_burn_subtitles (true);
+       player->set_always_burn_captions (CAPTION_OPEN);
        player->set_play_referenced ();
 
        shared_ptr<Butler> butler (new Butler (player, film->log(), AudioMapping(), 2));