Fixes for silence in projects, various cleanups.
authorCarl Hetherington <cth@carlh.net>
Wed, 28 Jun 2017 09:09:53 +0000 (10:09 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 29 Jun 2017 10:26:13 +0000 (11:26 +0100)
12 files changed:
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/dcpomatic_time_coalesce.h [new file with mode: 0644]
src/lib/empty.cc [new file with mode: 0644]
src/lib/empty.h [new file with mode: 0644]
src/lib/player.cc
src/lib/player.h
src/lib/playlist.cc
src/lib/wscript
test/dcpomatic_time_test.cc
test/empty_test.cc [new file with mode: 0644]
test/wscript

index 69d86c5..5425798 100644 (file)
@@ -60,6 +60,11 @@ AudioDecoder::emit (AudioStreamPtr stream, shared_ptr<const AudioBuffers> data,
                   we just count samples, as it seems that ContentTimes are unreliable from
                   FFmpegDecoder (not quite continuous; perhaps due to some rounding error).
                */
+               if (_content->delay() > 0) {
+                       /* Insert silence to give the delay */
+                       silence (_content->delay ());
+               }
+               time += ContentTime::from_seconds (_content->delay() / 1000.0);
                _positions[stream] = time.frames_round (stream->frame_rate ());
        }
 
@@ -139,4 +144,20 @@ AudioDecoder::flush ()
                        _positions[i->first] += ro->frames();
                }
        }
+
+       if (_content->delay() < 0) {
+               /* Finish off with the gap caused by the delay */
+               silence (-_content->delay ());
+       }
+}
+
+void
+AudioDecoder::silence (int milliseconds)
+{
+       BOOST_FOREACH (AudioStreamPtr i, _content->streams ()) {
+               int const samples = ContentTime::from_seconds(milliseconds / 1000.0).frames_round(i->frame_rate());
+               shared_ptr<AudioBuffers> silence (new AudioBuffers (i->channels(), samples));
+               silence->make_silent ();
+               Data (i, ContentAudio (silence, _positions[i]));
+       }
 }
index 624b5c9..19d1035 100644 (file)
@@ -56,6 +56,8 @@ public:
        boost::signals2::signal<void (AudioStreamPtr, ContentAudio)> Data;
 
 private:
+       void silence (int milliseconds);
+
        boost::shared_ptr<const AudioContent> _content;
        /** Frame after the last one that was emitted from Data for each AudioStream */
        std::map<AudioStreamPtr, Frame> _positions;
diff --git a/src/lib/dcpomatic_time_coalesce.h b/src/lib/dcpomatic_time_coalesce.h
new file mode 100644 (file)
index 0000000..e103e80
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "dcpomatic_time.h"
+#include <iostream>
+
+/** @param periods Set of periods in ascending order of from time */
+template <class T>
+std::list<TimePeriod<T> > coalesce (std::list<TimePeriod<T> > periods)
+{
+       bool did_something;
+       std::list<TimePeriod<T> > coalesced;
+       do {
+               coalesced.clear ();
+               did_something = false;
+               for (typename std::list<TimePeriod<T> >::const_iterator i = periods.begin(); i != periods.end(); ++i) {
+                       typename std::list<TimePeriod<T> >::const_iterator j = i;
+                       ++j;
+                       if (j != periods.end() && (i->overlap(*j) || i->to == j->from)) {
+                               coalesced.push_back (TimePeriod<T> (i->from, j->to));
+                               did_something = true;
+                               ++i;
+                       } else {
+                               coalesced.push_back (*i);
+                       }
+               }
+               periods = coalesced;
+       } while (did_something);
+
+       return periods;
+}
diff --git a/src/lib/empty.cc b/src/lib/empty.cc
new file mode 100644 (file)
index 0000000..2233b43
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "empty.h"
+#include "playlist.h"
+#include "content.h"
+#include "content_part.h"
+#include "dcp_content.h"
+#include "dcpomatic_time_coalesce.h"
+#include <boost/foreach.hpp>
+#include <iostream>
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::function;
+
+Empty::Empty (shared_ptr<const Playlist> playlist, function<shared_ptr<ContentPart> (Content *)> part)
+{
+       list<DCPTimePeriod> full;
+       BOOST_FOREACH (shared_ptr<Content> i, playlist->content()) {
+               if (part (i.get())) {
+                       full.push_back (DCPTimePeriod (i->position(), i->end()));
+               }
+       }
+
+       _periods = subtract (DCPTimePeriod(DCPTime(), playlist->length()), coalesce(full));
+}
+
+void
+Empty::set_position (DCPTime position)
+{
+       _position = position;
+
+       BOOST_FOREACH (DCPTimePeriod i, _periods) {
+               if (i.contains(_position)) {
+                       return;
+               }
+       }
+
+       BOOST_FOREACH (DCPTimePeriod i, _periods) {
+               if (i.from > _position) {
+                       _position = i.from;
+                       return;
+               }
+       }
+}
+
+DCPTimePeriod
+Empty::period_at_position () const
+{
+       BOOST_FOREACH (DCPTimePeriod i, _periods) {
+               if (i.contains(_position)) {
+                       return DCPTimePeriod (_position, i.to);
+               }
+       }
+
+       DCPOMATIC_ASSERT (false);
+}
+
+bool
+Empty::done () const
+{
+       BOOST_FOREACH (DCPTimePeriod i, _periods) {
+               if (i.contains(_position)) {
+                       return false;
+               }
+       }
+
+       return true;
+}
diff --git a/src/lib/empty.h b/src/lib/empty.h
new file mode 100644 (file)
index 0000000..3c676de
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "playlist.h"
+#include "dcpomatic_time.h"
+#include "content_part.h"
+#include <list>
+
+struct empty_test1;
+
+class Empty
+{
+public:
+       Empty () {}
+       Empty (boost::shared_ptr<const Playlist> playlist, boost::function<boost::shared_ptr<ContentPart> (Content *)> part);
+
+       DCPTime position () const {
+               return _position;
+       }
+
+       DCPTimePeriod period_at_position () const;
+
+       bool done () const;
+
+       void set_position (DCPTime amount);
+
+private:
+       friend struct ::empty_test1;
+
+       std::list<DCPTimePeriod> _periods;
+       DCPTime _position;
+};
index 5234751..be6ff2b 100644 (file)
@@ -156,6 +156,9 @@ Player::setup_pieces ()
                }
        }
 
+       _black = Empty (_playlist, bind(&Content::video, _1));
+       _silent = Empty (_playlist, bind(&Content::audio, _1));
+
        _last_video_time = DCPTime ();
        _last_audio_time = DCPTime ();
        _have_valid_pieces = true;
@@ -356,8 +359,9 @@ DCPTime
 Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
 {
        /* See comment in dcp_to_content_video */
-       DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start(), piece->frc);
-       return max (DCPTime (), d + piece->content->position ());
+       return DCPTime::from_frames (f, _film->audio_frame_rate())
+               - DCPTime (piece->content->trim_start(), piece->frc)
+               + piece->content->position();
 }
 
 ContentTime
@@ -499,34 +503,7 @@ Player::pass ()
                setup_pieces ();
        }
 
-       bool filled = false;
-
-       if (_last_video_time && !_playlist->video_content_at(*_last_video_time) && *_last_video_time < _playlist->length()) {
-               /* _last_video_time is the time just after the last video we emitted, and there is no video content
-                  at this time so we need to emit some black.
-               */
-               emit_video (black_player_video_frame(), *_last_video_time);
-               filled = true;
-       } else if (_playlist->length() == DCPTime()) {
-               /* Special case of an empty Film; just give one black frame */
-               emit_video (black_player_video_frame(), DCPTime());
-               filled = true;
-       }
-
-       if (_last_audio_time && !_playlist->audio_content_at(*_last_audio_time) && *_last_audio_time < _playlist->length()) {
-               /* _last_audio_time is the time just after the last audio we emitted.  There is no audio here
-                  so we need to emit some silence.
-               */
-               shared_ptr<Content> next = _playlist->next_audio_content(*_last_audio_time);
-               DCPTimePeriod period (*_last_audio_time, next ? next->position() : _playlist->length());
-               if (period.duration() > one_video_frame()) {
-                       period = DCPTimePeriod (*_last_audio_time, *_last_audio_time + one_video_frame());
-               }
-               fill_audio (period);
-               filled = true;
-       }
-
-       /* Now pass() the decoder which is farthest behind where we are */
+       /* Find the decoder or empty which is farthest behind where we are and make it emit some data */
 
        shared_ptr<Piece> earliest;
        DCPTime earliest_content;
@@ -541,8 +518,27 @@ Player::pass ()
                }
        }
 
-       if (!filled && earliest) {
+       bool done = false;
+
+       if (!_black.done() && (!earliest ||_black.position() < earliest_content)) {
+               /* There is some black that must be emitted */
+               emit_video (black_player_video_frame(), _black.position());
+               _black.set_position (_black.position() + one_video_frame());
+       } else if (!_silent.done() && (!earliest || _silent.position() < earliest_content)) {
+               /* There is some silence that must be emitted */
+               DCPTimePeriod period (_silent.period_at_position());
+               if (period.duration() > one_video_frame()) {
+                       period.to = period.from + one_video_frame();
+               }
+               fill_audio (period);
+               _silent.set_position (period.to);
+       } else if (_playlist->length() == DCPTime()) {
+               /* Special case of an empty Film; just give one black frame */
+               emit_video (black_player_video_frame(), DCPTime());
+       } else if (earliest) {
                earliest->done = earliest->decoder->pass ();
+       } else {
+               done = true;
        }
 
        /* Emit any audio that is ready */
@@ -567,17 +563,10 @@ Player::pass ()
                        *i = cut;
                }
 
-               if (_last_audio_time) {
-                       /* Fill in the gap before delayed audio; this doesn't need to take into account
-                          periods with no audio as it should only occur in delayed audio case.
-                       */
-                       fill_audio (DCPTimePeriod (*_last_audio_time, i->second));
-               }
-
                emit_audio (i->first, i->second);
        }
 
-       return !earliest && !filled;
+       return done;
 }
 
 optional<PositionImage>
@@ -663,37 +652,6 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
        emit_video (_last_video[wp], time);
 }
 
-/** Do our common processing on some audio */
-void
-Player::audio_transform (shared_ptr<AudioContent> content, AudioStreamPtr stream, ContentAudio content_audio, DCPTime time)
-{
-       DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
-
-       /* Gain */
-
-       if (content->gain() != 0) {
-               shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
-               gain->apply_gain (content->gain ());
-               content_audio.audio = gain;
-       }
-
-       /* Remap */
-
-       content_audio.audio = remap (content_audio.audio, _film->audio_channels(), stream->mapping());
-
-       /* Process */
-
-       if (_audio_processor) {
-               content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
-       }
-
-       /* Push */
-
-       _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());
-}
-
 void
 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
 {
@@ -708,7 +666,7 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
        DCPOMATIC_ASSERT (content);
 
        /* Compute time in the DCP */
-       DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
+       DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame);
        /* And the end of this block in the DCP */
        DCPTime end = time + DCPTime::from_frames(content_audio.audio->frames(), content->resampled_frame_rate());
 
@@ -734,7 +692,31 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
                content_audio.audio = cut;
        }
 
-       audio_transform (content, stream, content_audio, time);
+       DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
+
+       /* Gain */
+
+       if (content->gain() != 0) {
+               shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
+               gain->apply_gain (content->gain ());
+               content_audio.audio = gain;
+       }
+
+       /* Remap */
+
+       content_audio.audio = remap (content_audio.audio, _film->audio_channels(), stream->mapping());
+
+       /* Process */
+
+       if (_audio_processor) {
+               content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
+       }
+
+       /* Push */
+
+       _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());
 }
 
 void
@@ -857,6 +839,9 @@ Player::seek (DCPTime time, bool accurate)
                _last_audio_time = optional<DCPTime>();
        }
 
+       _black.set_position (time);
+       _silent.set_position (time);
+
        _last_video.clear ();
 }
 
index b4d0022..0ceff01 100644 (file)
@@ -32,6 +32,7 @@
 #include "content_subtitle.h"
 #include "audio_stream.h"
 #include "audio_merger.h"
+#include "empty.h"
 #include <boost/shared_ptr.hpp>
 #include <boost/enable_shared_from_this.hpp>
 #include <list>
@@ -108,7 +109,6 @@ private:
        void subtitle_stop (boost::weak_ptr<Piece>, ContentTime);
        DCPTime one_video_frame () const;
        void fill_audio (DCPTimePeriod period);
-       void audio_transform (boost::shared_ptr<AudioContent> content, AudioStreamPtr stream, ContentAudio content_audio, DCPTime time);
        std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> discard_audio (
                boost::shared_ptr<const AudioBuffers> audio, DCPTime time, DCPTime discard_to
                ) const;
@@ -165,6 +165,9 @@ private:
        };
        std::map<AudioStreamPtr, StreamState> _stream_states;
 
+       Empty _black;
+       Empty _silent;
+
        ActiveSubtitles _active_subtitles;
        boost::shared_ptr<AudioProcessor> _audio_processor;
 
index 3609f9e..12832cf 100644 (file)
@@ -563,11 +563,7 @@ Playlist::audio_content_at (DCPTime time) const
                if (!i->audio) {
                        continue;
                }
-               DCPTime end = i->end ();
-               if (i->audio->delay() < 0) {
-                       end += DCPTime::from_seconds (i->audio->delay() / 1000.0);
-               }
-               if (i->position() <= time && time < end) {
+               if (i->position() <= time && time < i->end()) {
                        return true;
                }
        }
index cbb0173..39ebc5a 100644 (file)
@@ -67,6 +67,7 @@ sources = """
           dkdm_wrapper.cc
           dolby_cp750.cc
           emailer.cc
+          empty.cc
           encoder.cc
           encode_server.cc
           encode_server_finder.cc
index f49d29a..38b4d31 100644 (file)
  */
 
 #include "lib/dcpomatic_time.h"
+#include "lib/dcpomatic_time_coalesce.h"
 #include <boost/test/unit_test.hpp>
 #include <list>
+#include <iostream>
 
 using std::list;
+using std::cout;
 
 BOOST_AUTO_TEST_CASE (dcpomatic_time_test)
 {
@@ -214,3 +217,86 @@ BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test7)
        ++i;
        BOOST_REQUIRE (i == r.end ());
 }
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test8)
+{
+       DCPTimePeriod A (DCPTime(0), DCPTime(32000));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime(8000), DCPTime(20000)));
+       B.push_back (DCPTimePeriod (DCPTime(28000), DCPTime(32000)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (*i == DCPTimePeriod(DCPTime(0), DCPTime(8000)));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (*i == DCPTimePeriod(DCPTime(20000), DCPTime(28000)));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test1)
+{
+       DCPTimePeriod A (DCPTime(14), DCPTime(29));
+       DCPTimePeriod B (DCPTime(45), DCPTime(91));
+       list<DCPTimePeriod> p;
+       p.push_back (A);
+       p.push_back (B);
+       list<DCPTimePeriod> q = coalesce (p);
+       BOOST_REQUIRE_EQUAL (q.size(), 2);
+       BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(29)));
+       BOOST_CHECK (q.back () == DCPTimePeriod(DCPTime(45), DCPTime(91)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test2)
+{
+       DCPTimePeriod A (DCPTime(14), DCPTime(29));
+       DCPTimePeriod B (DCPTime(26), DCPTime(91));
+       list<DCPTimePeriod> p;
+       p.push_back (A);
+       p.push_back (B);
+       list<DCPTimePeriod> q = coalesce (p);
+       BOOST_REQUIRE_EQUAL (q.size(), 1);
+       BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(91)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test3)
+{
+       DCPTimePeriod A (DCPTime(14), DCPTime(29));
+       DCPTimePeriod B (DCPTime(29), DCPTime(91));
+       list<DCPTimePeriod> p;
+       p.push_back (A);
+       p.push_back (B);
+       list<DCPTimePeriod> q = coalesce (p);
+       BOOST_REQUIRE_EQUAL (q.size(), 1);
+       BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(91)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test4)
+{
+       DCPTimePeriod A (DCPTime(14), DCPTime(29));
+       DCPTimePeriod B (DCPTime(20), DCPTime(91));
+       DCPTimePeriod C (DCPTime(35), DCPTime(106));
+       list<DCPTimePeriod> p;
+       p.push_back (A);
+       p.push_back (B);
+       p.push_back (C);
+       list<DCPTimePeriod> q = coalesce (p);
+       BOOST_REQUIRE_EQUAL (q.size(), 1);
+       BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(106)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test5)
+{
+       DCPTimePeriod A (DCPTime(14), DCPTime(29));
+       DCPTimePeriod B (DCPTime(20), DCPTime(91));
+       DCPTimePeriod C (DCPTime(100), DCPTime(106));
+       list<DCPTimePeriod> p;
+       p.push_back (A);
+       p.push_back (B);
+       p.push_back (C);
+       list<DCPTimePeriod> q = coalesce (p);
+       BOOST_REQUIRE_EQUAL (q.size(), 2);
+       BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(91)));
+       BOOST_CHECK (q.back()  == DCPTimePeriod(DCPTime(100), DCPTime(106)));
+}
diff --git a/test/empty_test.cc b/test/empty_test.cc
new file mode 100644 (file)
index 0000000..b7dce1a
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+    Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "lib/film.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "lib/video_content.h"
+#include "lib/image_content.h"
+#include "lib/empty.h"
+#include "test.h"
+#include <boost/test/unit_test.hpp>
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (empty_test1)
+{
+       shared_ptr<Film> film = new_test_film ("empty_test1");
+       film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
+       film->set_name ("empty_test1");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_sequence (false);
+       shared_ptr<ImageContent> contentA (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
+       shared_ptr<ImageContent> contentB (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
+
+       film->examine_and_add_content (contentA);
+       film->examine_and_add_content (contentB);
+       wait_for_jobs ();
+
+       contentA->video->set_scale (VideoContentScale (Ratio::from_id ("185")));
+       contentA->video->set_length (3);
+       contentA->set_position (DCPTime::from_frames (2, film->video_frame_rate ()));
+       contentB->video->set_scale (VideoContentScale (Ratio::from_id ("185")));
+       contentB->video->set_length (1);
+       contentB->set_position (DCPTime::from_frames (7, film->video_frame_rate ()));
+
+       Empty black (film->playlist(), bind(&Content::video, _1));
+       BOOST_REQUIRE_EQUAL (black._periods.size(), 2);
+       BOOST_CHECK (black._periods.front().from == DCPTime());
+       BOOST_CHECK (black._periods.front().to == DCPTime::from_frames(2, film->video_frame_rate()));
+       BOOST_CHECK (black._periods.back().from == DCPTime::from_frames(5, film->video_frame_rate()));
+       BOOST_CHECK (black._periods.back().to == DCPTime::from_frames(7, film->video_frame_rate()));
+}
index c90f9bd..3718534 100644 (file)
@@ -54,6 +54,7 @@ def build(bld):
                  dcpomatic_time_test.cc
                  dcp_subtitle_test.cc
                  digest_test.cc
+                 empty_test.cc
                  ffmpeg_audio_only_test.cc
                  ffmpeg_audio_test.cc
                  ffmpeg_dcp_test.cc