Whitespace.
[dcpomatic.git] / src / lib / player.cc
index 11369b682b97879e692b3b415b70978440f8841c..b011fffdc351702c6b6c0dbe14ec2bf0bdca29ba 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2017 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -48,6 +48,7 @@
 #include "dcp_decoder.h"
 #include "image_decoder.h"
 #include "compose.hpp"
+#include "shuffler.h"
 #include <dcp/reel.h>
 #include <dcp/reel_sound_asset.h>
 #include <dcp/reel_subtitle_asset.h>
@@ -87,6 +88,7 @@ Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist
        , _fast (false)
        , _play_referenced (false)
        , _audio_merger (_film->audio_frame_rate())
+       , _shuffler (0)
 {
        _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
@@ -98,11 +100,20 @@ Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist
        seek (DCPTime (), true);
 }
 
+Player::~Player ()
+{
+       delete _shuffler;
+}
+
 void
 Player::setup_pieces ()
 {
        _pieces.clear ();
 
+       delete _shuffler;
+       _shuffler = new Shuffler();
+       _shuffler->Video.connect(bind(&Player::video, this, _1, _2));
+
        BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
 
                if (!i->paths_valid ()) {
@@ -137,7 +148,11 @@ Player::setup_pieces ()
                _pieces.push_back (piece);
 
                if (decoder->video) {
-                       decoder->video->Data.connect (bind (&Player::video, this, weak_ptr<Piece> (piece), _1));
+                       if (i->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT || i->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
+                               decoder->video->Data.connect (bind (&Shuffler::video, _shuffler, weak_ptr<Piece>(piece), _1));
+                       } else {
+                               decoder->video->Data.connect (bind (&Player::video, this, weak_ptr<Piece>(piece), _1));
+                       }
                }
 
                if (decoder->audio) {
@@ -151,6 +166,7 @@ Player::setup_pieces ()
                }
        }
 
+       _stream_states.clear ();
        BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
                if (i->content->audio) {
                        BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams()) {
@@ -163,6 +179,7 @@ Player::setup_pieces ()
        _silent = Empty (_film->content(), _film->length(), bind(&Content::audio, _1));
 
        _last_video_time = DCPTime ();
+       _last_video_eyes = EYES_BOTH;
        _last_audio_time = DCPTime ();
        _have_valid_pieces = true;
 }
@@ -185,10 +202,10 @@ Player::playlist_content_changed (weak_ptr<Content> w, int property, bool freque
                property == DCPContentProperty::NEEDS_ASSETS ||
                property == DCPContentProperty::NEEDS_KDM ||
                property == SubtitleContentProperty::COLOUR ||
-               property == SubtitleContentProperty::OUTLINE ||
-               property == SubtitleContentProperty::SHADOW ||
+               property == SubtitleContentProperty::EFFECT ||
                property == SubtitleContentProperty::EFFECT_COLOUR ||
                property == FFmpegContentProperty::SUBTITLE_STREAM ||
+               property == FFmpegContentProperty::FILTERS ||
                property == VideoContentProperty::COLOUR_CONVERSION
                ) {
 
@@ -308,7 +325,7 @@ Player::transform_image_subtitles (list<ImageSubtitle> subs) const
 }
 
 shared_ptr<PlayerVideo>
-Player::black_player_video_frame () const
+Player::black_player_video_frame (Eyes eyes) const
 {
        return shared_ptr<PlayerVideo> (
                new PlayerVideo (
@@ -317,7 +334,7 @@ Player::black_player_video_frame () const
                        optional<double> (),
                        _video_container_size,
                        _video_container_size,
-                       EYES_BOTH,
+                       eyes,
                        PART_WHOLE,
                        PresetColourConversion::all().front().conversion
                )
@@ -345,8 +362,8 @@ DCPTime
 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
 {
        /* See comment in dcp_to_content_video */
-       DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
-       return max (DCPTime (), d + piece->content->position ());
+       DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime(piece->content->trim_start(), piece->frc);
+       return d + piece->content->position();
 }
 
 Frame
@@ -515,7 +532,7 @@ Player::pass ()
 
        if (_playlist->length() == DCPTime()) {
                /* Special case of an empty Film; just give one black frame */
-               emit_video (black_player_video_frame(), DCPTime());
+               emit_video (black_player_video_frame(EYES_BOTH), DCPTime());
                return true;
        }
 
@@ -525,8 +542,15 @@ Player::pass ()
        optional<DCPTime> earliest_time;
 
        BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
-               if (!i->done) {
-                       DCPTime const t = content_time_to_dcp (i, i->decoder->position());
+               if (i->done) {
+                       continue;
+               }
+
+               DCPTime const t = content_time_to_dcp (i, i->decoder->position());
+               if (t > i->content->end()) {
+                       i->done = true;
+               } else {
+
                        /* Given two choices at the same time, pick the one with a subtitle so we see it before
                           the video.
                        */
@@ -565,12 +589,20 @@ Player::pass ()
                earliest_content->done = earliest_content->decoder->pass ();
                break;
        case BLACK:
-               emit_video (black_player_video_frame(), _black.position());
+               emit_video (black_player_video_frame(EYES_BOTH), _black.position());
                _black.set_position (_black.position() + one_video_frame());
                break;
        case SILENT:
        {
                DCPTimePeriod period (_silent.period_at_position());
+               if (_last_audio_time) {
+                       /* Sometimes the thing that happened last finishes fractionally before
+                          this silence.  Bodge the start time of the silence to fix it.  I'm
+                          not sure if this is the right solution --- maybe the last thing should
+                          be padded `forward' rather than this thing padding `back'.
+                       */
+                       period.from = min(period.from, *_last_audio_time);
+               }
                if (period.duration() > one_video_frame()) {
                        period.to = period.from + one_video_frame();
                }
@@ -585,12 +617,18 @@ Player::pass ()
 
        /* Emit any audio that is ready */
 
+       /* Work out the time before which the audio is definitely all here.  This is the earliest last_push_end of one
+          of our streams, or the position of the _silent.
+       */
        DCPTime pull_to = _film->length ();
        for (map<AudioStreamPtr, StreamState>::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) {
                if (!i->second.piece->done && i->second.last_push_end < pull_to) {
                        pull_to = i->second.last_push_end;
                }
        }
+       if (!_silent.done() && _silent.position() < pull_to) {
+               pull_to = _silent.position();
+       }
 
        list<pair<shared_ptr<AudioBuffers>, DCPTime> > audio = _audio_merger.pull (pull_to);
        for (list<pair<shared_ptr<AudioBuffers>, DCPTime> >::iterator i = audio.begin(); i != audio.end(); ++i) {
@@ -609,6 +647,9 @@ Player::pass ()
                emit_audio (i->first, i->second);
        }
 
+       if (done) {
+               _shuffler->flush ();
+       }
        return done;
 }
 
@@ -617,7 +658,9 @@ Player::subtitles_for_frame (DCPTime time) const
 {
        list<PositionImage> subtitles;
 
-       BOOST_FOREACH (PlayerSubtitles i, _active_subtitles.get_burnt (time, _always_burn_subtitles)) {
+       int const vfr = _film->video_frame_rate();
+
+       BOOST_FOREACH (PlayerSubtitles i, _active_subtitles.get_burnt (DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_subtitles)) {
 
                /* Image subtitles */
                list<PositionImage> c = transform_image_subtitles (i.image);
@@ -625,7 +668,7 @@ Player::subtitles_for_frame (DCPTime time) const
 
                /* Text subtitles (rendered to an image) */
                if (!i.text.empty ()) {
-                       list<PositionImage> s = render_subtitles (i.text, i.fonts, _video_container_size, time);
+                       list<PositionImage> s = render_subtitles (i.text, i.fonts, _video_container_size, time, vfr);
                        copy (s.begin(), s.end(), back_inserter (subtitles));
                }
        }
@@ -637,41 +680,64 @@ Player::subtitles_for_frame (DCPTime time) const
        return merge (subtitles);
 }
 
-bool
+void
 Player::video (weak_ptr<Piece> wp, ContentVideo video)
 {
        shared_ptr<Piece> piece = wp.lock ();
        if (!piece) {
-               return false;
+               return;
        }
 
        FrameRateChange frc(piece->content->active_video_frame_rate(), _film->video_frame_rate());
        if (frc.skip && (video.frame % 2) == 1) {
-               return false;
+               return;
        }
 
        /* Time of the first frame we will emit */
        DCPTime const time = content_video_to_dcp (piece, video.frame);
 
-       /* Discard if it's outside the content's period or if it's before the last accurate seek */
-       if (
-               time < piece->content->position() ||
-               time >= piece->content->end() ||
-               (_last_video_time && time < *_last_video_time)) {
-               return false;
+       /* Discard if it's before the content's period or the last accurate seek.  We can't discard
+          if it's after the content's period here as in that case we still need to fill any gap between
+          `now' and the end of the content's period.
+       */
+       if (time < piece->content->position() || (_last_video_time && time < *_last_video_time)) {
+               return;
        }
 
-       /* Fill gaps that we discover now that we have some video which needs to be emitted */
+       /* Fill gaps that we discover now that we have some video which needs to be emitted.
+          This is where we need to fill to.
+       */
+       DCPTime fill_to = min (time, piece->content->end());
 
        if (_last_video_time) {
-               /* XXX: this may not work for 3D */
                DCPTime fill_from = max (*_last_video_time, piece->content->position());
-               for (DCPTime j = fill_from; j < time; j += one_video_frame()) {
-                       LastVideoMap::const_iterator k = _last_video.find (wp);
-                       if (k != _last_video.end ()) {
-                               emit_video (k->second, j);
-                       } else {
-                               emit_video (black_player_video_frame(), j);
+               LastVideoMap::const_iterator last = _last_video.find (wp);
+               if (_film->three_d()) {
+                       DCPTime j = fill_from;
+                       Eyes eyes = _last_video_eyes.get_value_or(EYES_LEFT);
+                       if (eyes == EYES_BOTH) {
+                               eyes = EYES_LEFT;
+                       }
+                       while (j < fill_to || eyes != video.eyes) {
+                               if (last != _last_video.end()) {
+                                       shared_ptr<PlayerVideo> copy = last->second->shallow_copy();
+                                       copy->set_eyes (eyes);
+                                       emit_video (copy, j);
+                               } else {
+                                       emit_video (black_player_video_frame(eyes), j);
+                               }
+                               if (eyes == EYES_RIGHT) {
+                                       j += one_video_frame();
+                               }
+                               eyes = increment_eyes (eyes);
+                       }
+               } else {
+                       for (DCPTime j = fill_from; j < fill_to; j += one_video_frame()) {
+                               if (last != _last_video.end()) {
+                                       emit_video (last->second, j);
+                               } else {
+                                       emit_video (black_player_video_frame(EYES_BOTH), j);
+                               }
                        }
                }
        }
@@ -693,21 +759,21 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
 
        DCPTime t = time;
        for (int i = 0; i < frc.repeat; ++i) {
-               emit_video (_last_video[wp], t);
+               if (t < piece->content->end()) {
+                       emit_video (_last_video[wp], t);
+               }
                t += one_video_frame ();
        }
-
-       return true;
 }
 
-Frame
+void
 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
 {
        DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
 
        shared_ptr<Piece> piece = wp.lock ();
        if (!piece) {
-               return 0;
+               return;
        }
 
        shared_ptr<AudioContent> content = piece->content->audio;
@@ -723,17 +789,17 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
                pair<shared_ptr<AudioBuffers>, DCPTime> cut = discard_audio (content_audio.audio, time, piece->content->position());
                if (!cut.first) {
                        /* This audio is entirely discarded */
-                       return 0;
+                       return;
                }
                content_audio.audio = cut.first;
                time = cut.second;
        } else if (time > piece->content->end()) {
                /* Discard it all */
-               return 0;
+               return;
        } else if (end > piece->content->end()) {
                Frame const remaining_frames = DCPTime(piece->content->end() - time).frames_round(_film->audio_frame_rate());
                if (remaining_frames == 0) {
-                       return 0;
+                       return;
                }
                shared_ptr<AudioBuffers> cut (new AudioBuffers (content_audio.audio->channels(), remaining_frames));
                cut->copy_from (content_audio.audio.get(), remaining_frames, 0, 0);
@@ -765,7 +831,6 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
        _audio_merger.push (content_audio.audio, time);
        DCPOMATIC_ASSERT (_stream_states.find (stream) != _stream_states.end ());
        _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate());
-       return content_audio.audio->frames();
 }
 
 void
@@ -862,6 +927,10 @@ Player::seek (DCPTime time, bool accurate)
                setup_pieces ();
        }
 
+       if (_shuffler) {
+               _shuffler->clear ();
+       }
+
        if (_audio_processor) {
                _audio_processor->flush ();
        }
@@ -886,9 +955,11 @@ Player::seek (DCPTime time, bool accurate)
 
        if (accurate) {
                _last_video_time = time;
+               _last_video_eyes = EYES_LEFT;
                _last_audio_time = time;
        } else {
                _last_video_time = optional<DCPTime>();
+               _last_video_eyes = optional<Eyes>();
                _last_audio_time = optional<DCPTime>();
        }
 
@@ -912,11 +983,14 @@ Player::emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
                _last_video_time = time + one_video_frame();
                _active_subtitles.clear_before (time);
        }
+       _last_video_eyes = increment_eyes (pv->eyes());
 }
 
 void
 Player::emit_audio (shared_ptr<AudioBuffers> data, DCPTime time)
 {
+       /* This audio must follow on from the previous */
+       DCPOMATIC_ASSERT (!_last_audio_time || time == *_last_audio_time);
        Audio (data, time);
        _last_audio_time = time + DCPTime::from_frames (data->frames(), _film->audio_frame_rate());
 }