Mostly-merge master.
authorCarl Hetherington <cth@carlh.net>
Fri, 21 Mar 2014 12:36:17 +0000 (12:36 +0000)
committerCarl Hetherington <cth@carlh.net>
Fri, 21 Mar 2014 12:36:17 +0000 (12:36 +0000)
16 files changed:
1  2 
ChangeLog
cscript
src/lib/ffmpeg.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_decoder.h
src/lib/image.cc
src/lib/image_content.cc
src/lib/types.h
src/lib/util.cc
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/wx/timecode.cc
src/wx/timing_panel.cc
src/wx/video_panel.cc
test/play_test.cc

diff --combined ChangeLog
index 2edfe0e121e482b7af60bd004a583fe869787501,418149a183aef3f12c7979e49399bd3c1be4794b..065a10ae324ca38809de65a83408ab822f3367df
+++ b/ChangeLog
@@@ -1,7 -1,13 +1,17 @@@
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
 +
+ 2014-03-18  Carl Hetherington  <cth@carlh.net>
+       * Version 1.66.3 released.
+ 2014-03-18  Carl Hetherington  <cth@carlh.net>
+       * Fix bad rounding of timecodes.
+       * Tentative support for 3D from alternate frames of the source.
  2014-03-17  Carl Hetherington  <cth@carlh.net>
  
        * Improve behaviour of the position slider at the end of films.
diff --combined cscript
index b8b0580063ee0490652cd2c8e0b0132360bcf349,7e6b769f075492d66e5ebe2e679f3feb6c2d2640..c07c430310a41ad3f430e1bc60ba8d0123fefe50
+++ b/cscript
@@@ -129,8 -129,8 +129,8 @@@ def make_control(debian_version, bits, 
          print >>f,''
  
  def dependencies(target):
-     return (('ffmpeg-cdist', '08827fa4e1d483511e6135c424d2ca9c56a9ed50'),
+     return (('ffmpeg-cdist', 'a0db025'),
 -            ('libdcp', '8af7b48'))
 +            ('libdcp', '1.0'))
  
  def build(target, options):
      cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
diff --combined src/lib/ffmpeg.cc
index 5fc33348923c85d584af16fb980c6f5f50ad2dd1,60eea6ec7756b626cf76fb996f552940196730b9..a98aa98289e6839dedf04ccc557c33b68d1e9c71
@@@ -21,7 -21,6 +21,6 @@@ extern "C" 
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  #include <libswscale/swscale.h>
- #include <libpostproc/postprocess.h>
  }
  #include "ffmpeg.h"
  #include "ffmpeg_content.h"
@@@ -193,10 -192,6 +192,10 @@@ FFmpeg::video_codec_context () cons
  AVCodecContext *
  FFmpeg::audio_codec_context () const
  {
 +      if (!_ffmpeg_content->audio_stream ()) {
 +              return 0;
 +      }
 +      
        return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
  }
  
index 90c00283d940fc79c43f1e0cc3131ae92507e01b,b7551c96af36ed3a85e47671ac6e1237a8d96499..86ce7503c9483c192e78c1655c99391f0ba68d36
@@@ -152,7 -152,7 +152,7 @@@ FFmpegContent::as_xml (xmlpp::Node* nod
        }
  
        if (_first_video) {
 -              node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
 +              node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get().get()));
        }
  }
  
@@@ -163,14 -163,14 +163,14 @@@ FFmpegContent::examine (shared_ptr<Job
  
        Content::examine (job);
  
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
 +      take_from_video_examiner (examiner);
  
 -      VideoContent::Frame video_length = 0;
 -      video_length = examiner->video_length ();
 -      film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
 +      ContentTime video_length = examiner->video_length ();
 +
 +      shared_ptr<const Film> film = _film.lock ();
 +      assert (film);
 +      film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ())));
  
        {
                boost::mutex::scoped_lock lm (_mutex);
                _first_video = examiner->first_video ();
        }
  
 -      take_from_video_examiner (examiner);
 -
        signal_changed (ContentProperty::LENGTH);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
@@@ -231,13 -233,13 +231,13 @@@ FFmpegContent::technical_summary () con
  string
  FFmpegContent::information () const
  {
 -      if (video_length() == 0 || video_frame_rate() == 0) {
 +      if (video_length() == ContentTime (0) || video_frame_rate() == 0) {
                return "";
        }
        
        stringstream s;
        
-       s << String::compose (_("%1 frames; %2 frames per second"), video_length().frames (video_frame_rate()), video_frame_rate()) << "\n";
 -      s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
++      s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
        s << VideoContent::information ();
  
        return s.str ();
@@@ -265,17 -267,19 +265,15 @@@ FFmpegContent::set_audio_stream (shared
        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
  }
  
 -AudioContent::Frame
 +ContentTime
  FFmpegContent::audio_length () const
  {
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               if (!_audio_stream) {
-                       return ContentTime ();
-               }
 -      int const cafr = content_audio_frame_rate ();
 -      int const vfr  = video_frame_rate ();
 -      VideoContent::Frame const vl = video_length_after_3d_combine ();
 -
+       boost::mutex::scoped_lock lm (_mutex);
+       if (!_audio_stream) {
 -              return 0;
++              return ContentTime ();
        }
 -      
 -      return video_frames_to_audio_frames (vl, cafr, vfr);
 +
-       return video_length();
++      return video_length ();
  }
  
  int
@@@ -311,15 -315,16 +309,15 @@@ FFmpegContent::output_audio_frame_rate 
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_frame_rate (content_audio_frame_rate ());
  
 -      FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
 +      FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
  
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
           look different in the DCP compared to the source (slower or faster).
 -         skip/repeat doesn't come into effect here.
        */
  
        if (frc.change_speed) {
 -              t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
 +              t /= frc.speed_up;
        }
  
        return rint (t);
@@@ -367,7 -372,7 +365,7 @@@ FFmpegAudioStream::as_xml (xmlpp::Node
        root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
        root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
        if (first_audio) {
 -              root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get ()));
 +              root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get().get()));
        }
        mapping.as_xml (root->add_child("Mapping"));
  }
@@@ -417,12 -422,14 +415,12 @@@ FFmpegSubtitleStream::as_xml (xmlpp::No
        FFmpegStream::as_xml (root);
  }
  
 -Time
 +DCPTime
  FFmpegContent::full_length () const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-       return DCPTime (video_length(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
 -      
 -      FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
 -      return video_length_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate ();
++      return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
  }
  
  AudioMapping
diff --combined src/lib/ffmpeg_decoder.h
index fbf802bb01004cf9c7d3f27f12e65a0f962af888,d4b4fa1c02984f8690c42bd264434b5266cff52e..15fe5d9a48966cbb42d6ec66e9a676f46646757a
@@@ -29,7 -29,6 +29,6 @@@
  #include <boost/thread/mutex.hpp>
  extern "C" {
  #include <libavcodec/avcodec.h>
- #include <libpostproc/postprocess.h>
  }
  #include "util.h"
  #include "decoder.h"
@@@ -38,7 -37,7 +37,7 @@@
  #include "subtitle_decoder.h"
  #include "ffmpeg.h"
  
 -class Film;
 +class Log;
  class FilterGraph;
  class ffmpeg_pts_offset_test;
  
  class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg
  {
  public:
 -      FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
 +      FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>, bool video, bool audio, bool subtitles);
        ~FFmpegDecoder ();
  
 -      void pass ();
 -      void seek (VideoContent::Frame, bool);
 -      bool done () const;
 +      void seek (ContentTime time, bool);
  
  private:
        friend class ::ffmpeg_pts_offset_test;
  
 -      static double compute_pts_offset (double, double, float);
 -
 +      bool pass ();
        void flush ();
  
        void setup_subtitle ();
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
  
 +      bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
 +      bool seek_final_finished (int, int) const;
 +      int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
 +      void seek_and_flush (ContentTime);
 +
 +      boost::shared_ptr<Log> _log;
        AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
        AVCodec* _subtitle_codec;                ///< may be 0 if there is no subtitle
        
@@@ -85,7 -81,7 +84,7 @@@
  
        bool _decode_video;
        bool _decode_audio;
 +      bool _decode_subtitles;
  
 -      double _pts_offset;
 -      bool _just_sought;
 +      ContentTime _pts_offset;
  };
diff --combined src/lib/image.cc
index c3b1ca77afded0cc70270fb097d40a6b989a2bfa,d083cf3f6c17612caa6e249cf86413116f655667..926aefd3662659583f0116abcd76abb9e16439c1
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@@ -26,12 -26,10 +26,11 @@@ extern "C" 
  #include <libswscale/swscale.h>
  #include <libavutil/pixfmt.h>
  #include <libavutil/pixdesc.h>
- #include <libpostproc/postprocess.h>
  }
  #include "image.h"
  #include "exceptions.h"
  #include "scaler.h"
 +#include "timer.h"
  
  #include "i18n.h"
  
@@@ -40,7 -38,7 +39,7 @@@ using std::min
  using std::cout;
  using std::cerr;
  using boost::shared_ptr;
 -using libdcp::Size;
 +using dcp::Size;
  
  int
  Image::line_factor (int n) const
@@@ -84,7 -82,7 +83,7 @@@ Image::components () cons
  
  /** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */
  shared_ptr<Image>
 -Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
 +Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
  {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
        out->make_black ();
  
        /* Size of the image after any crop */
 -      libdcp::Size const cropped_size = crop.apply (size ());
 +      dcp::Size const cropped_size = crop.apply (size ());
  
        /* Scale context for a scale from cropped_size to inter_size */
        struct SwsContext* scale_context = sws_getContext (
 -              cropped_size.width, cropped_size.height, pixel_format(),
 -              inter_size.width, inter_size.height, out_format,
 -              scaler->ffmpeg_id (), 0, 0, 0
 +                      cropped_size.width, cropped_size.height, pixel_format(),
 +                      inter_size.width, inter_size.height, out_format,
 +                      scaler->ffmpeg_id (), 0, 0, 0
                );
  
        if (!scale_context) {
  }
  
  shared_ptr<Image>
 -Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
 +Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
  {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
  shared_ptr<Image>
  Image::crop (Crop crop, bool aligned) const
  {
 -      libdcp::Size cropped_size = crop.apply (size ());
 +      dcp::Size cropped_size = crop.apply (size ());
        shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
  
        for (int c = 0; c < components(); ++c) {
@@@ -346,18 -344,8 +345,18 @@@ Image::make_black (
  void
  Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
  {
 -      /* Only implemented for RGBA onto RGB24 so far */
 -      assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
 +      int this_bpp = 0;
 +      int other_bpp = 0;
 +
 +      if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
 +              this_bpp = 4;
 +              other_bpp = 4;
 +      } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
 +              this_bpp = 3;
 +              other_bpp = 4;
 +      } else {
 +              assert (false);
 +      }
  
        int start_tx = position.x;
        int start_ox = 0;
        }
  
        for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
 -              uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
 +              uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
                uint8_t* op = other->data()[0] + oy * other->stride()[0];
                for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
                        float const alpha = float (op[3]) / 255;
                        tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
                        tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
                        tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
 -                      tp += 3;
 -                      op += 4;
 +                      tp += this_bpp;
 +                      op += other_bpp;
                }
        }
  }
@@@ -468,8 -456,8 +467,8 @@@ Image::bytes_per_pixel (int c) cons
   *  @param p Pixel format.
   *  @param s Size in pixels.
   */
 -Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned)
 -      : libdcp::Image (s)
 +Image::Image (AVPixelFormat p, dcp::Size s, bool aligned)
 +      : dcp::Image (s)
        , _pixel_format (p)
        , _aligned (aligned)
  {
@@@ -506,7 -494,7 +505,7 @@@ Image::allocate (
  }
  
  Image::Image (Image const & other)
 -      : libdcp::Image (other)
 +      : dcp::Image (other)
        ,  _pixel_format (other._pixel_format)
        , _aligned (other._aligned)
  {
  }
  
  Image::Image (AVFrame* frame)
 -      : libdcp::Image (libdcp::Size (frame->width, frame->height))
 +      : dcp::Image (dcp::Size (frame->width, frame->height))
        , _pixel_format (static_cast<AVPixelFormat> (frame->format))
        , _aligned (true)
  {
  }
  
  Image::Image (shared_ptr<const Image> other, bool aligned)
 -      : libdcp::Image (other)
 +      : dcp::Image (other)
        , _pixel_format (other->_pixel_format)
        , _aligned (aligned)
  {
@@@ -576,7 -564,7 +575,7 @@@ Image::operator= (Image const & other
  void
  Image::swap (Image & other)
  {
 -      libdcp::Image::swap (other);
 +      dcp::Image::swap (other);
        
        std::swap (_pixel_format, other._pixel_format);
  
@@@ -619,7 -607,7 +618,7 @@@ Image::stride () cons
        return _stride;
  }
  
 -libdcp::Size
 +dcp::Size
  Image::size () const
  {
        return _size;
diff --combined src/lib/image_content.cc
index 6a37f30676472c51ec24866179ce5209cc5c2ec8,13f7c52e3d12dec4256479d0d0bdbcec7c795253..d7b37a8353fa5f04b63c58006a2a6c8eade95a1f
@@@ -110,7 -110,7 +110,7 @@@ ImageContent::examine (shared_ptr<Job> 
  }
  
  void
 -ImageContent::set_video_length (VideoContent::Frame len)
 +ImageContent::set_video_length (ContentTime len)
  {
        {
                boost::mutex::scoped_lock lm (_mutex);
        signal_changed (ContentProperty::LENGTH);
  }
  
 -Time
 +DCPTime
  ImageContent::full_length () const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-       return DCPTime (video_length(), FrameRateChange (video_frame_rate(), film->video_frame_rate()));
 -      
 -      FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
 -      return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate();
++      return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate(), film->video_frame_rate()));
  }
  
  string
@@@ -133,7 -135,7 +133,7 @@@ ImageContent::identifier () cons
  {
        stringstream s;
        s << VideoContent::identifier ();
 -      s << "_" << video_length();
 +      s << "_" << video_length().get();
        return s.str ();
  }
  
diff --combined src/lib/types.h
index dafea92f83948faba000c4391be42b169aaecf7a,8a16818bddae84b86ecc9d55cf5b5ed600fe547f..4e1f59ca87fb122baea9acad6c63b71f5e83061d
@@@ -23,8 -23,7 +23,8 @@@
  #include <vector>
  #include <stdint.h>
  #include <boost/shared_ptr.hpp>
 -#include <libdcp/util.h>
 +#include <dcp/util.h>
 +#include "dcpomatic_time.h"
  
  class Content;
  class VideoContent;
@@@ -39,32 -38,39 +39,33 @@@ class AudioBuffers
   */
  #define SERVER_LINK_VERSION 1
  
 -typedef int64_t Time;
 -#define TIME_MAX INT64_MAX
 -#define TIME_HZ        ((Time) 96000)
 -typedef int64_t OutputAudioFrame;
 -typedef int   OutputVideoFrame;
  typedef std::vector<boost::shared_ptr<Content> > ContentList;
  typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
  typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
  typedef std::vector<boost::shared_ptr<SubtitleContent> > SubtitleContentList;
  typedef std::vector<boost::shared_ptr<FFmpegContent> > FFmpegContentList;
  
 -template<class T>
  struct TimedAudioBuffers
  {
        TimedAudioBuffers ()
                : time (0)
        {}
        
 -      TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
 +      TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, DCPTime t)
                : audio (a)
                , time (t)
        {}
        
        boost::shared_ptr<AudioBuffers> audio;
 -      T time;
 +      DCPTime time;
  };
  
  enum VideoFrameType
  {
        VIDEO_FRAME_TYPE_2D,
        VIDEO_FRAME_TYPE_3D_LEFT_RIGHT,
-       VIDEO_FRAME_TYPE_3D_TOP_BOTTOM
+       VIDEO_FRAME_TYPE_3D_TOP_BOTTOM,
+       VIDEO_FRAME_TYPE_3D_ALTERNATE
  };
  
  enum Eyes
@@@ -92,7 -98,7 +93,7 @@@ struct Cro
        /** Number of pixels to remove from the bottom */
        int bottom;
  
 -      libdcp::Size apply (libdcp::Size s, int minimum = 4) const {
 +      dcp::Size apply (dcp::Size s, int minimum = 4) const {
                s.width -= left + right;
                s.height -= top + bottom;
  
diff --combined src/lib/util.cc
index 15109eee05c442f0dab35982703e20e788148f79,85c52b039f10fb91cf614ddd1e1014099ba24833..5b3bd76ba0292607bcbdd8b10024a05e0d4b4718
  #include <openssl/md5.h>
  #include <magick/MagickCore.h>
  #include <magick/version.h>
 -#include <libdcp/version.h>
 -#include <libdcp/util.h>
 -#include <libdcp/signer_chain.h>
 -#include <libdcp/signer.h>
 +#include <pangomm/init.h>
 +#include <dcp/version.h>
 +#include <dcp/util.h>
 +#include <dcp/signer_chain.h>
 +#include <dcp/signer.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  #include <libswscale/swscale.h>
  #include <libavfilter/avfiltergraph.h>
- #include <libpostproc/postprocess.h>
  #include <libavutil/pixfmt.h>
  }
  #include "util.h"
@@@ -103,7 -101,7 +102,7 @@@ using boost::shared_ptr
  using boost::thread;
  using boost::lexical_cast;
  using boost::optional;
 -using libdcp::Size;
 +using dcp::Size;
  
  static boost::thread::id ui_thread;
  static boost::filesystem::path backtrace_file;
@@@ -249,11 -247,10 +248,10 @@@ dependency_version_summary (
          << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
          << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
          << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
-         << N_("libpostproc ") << ffmpeg_version_to_string (postproc_version()) << N_(", ")
          << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
          << MagickVersion << N_(", ")
          << N_("libssh ") << ssh_version (0) << N_(", ")
 -        << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
 +        << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
  
        return s.str ();
  }
@@@ -344,8 -341,7 +342,8 @@@ dcpomatic_setup (
  
        set_terminate (terminate);
  
 -      libdcp::init ();
 +      Pango::init ();
 +      dcp::init ();
        
        Ratio::setup_ratios ();
        VideoContentScale::setup_scales ();
@@@ -494,6 -490,33 +492,6 @@@ md5_digest (vector<boost::filesystem::p
        return s.str ();
  }
  
 -static bool
 -about_equal (float a, float b)
 -{
 -      /* A film of F seconds at f FPS will be Ff frames;
 -         Consider some delta FPS d, so if we run the same
 -         film at (f + d) FPS it will last F(f + d) seconds.
 -
 -         Hence the difference in length over the length of the film will
 -         be F(f + d) - Ff frames
 -          = Ff + Fd - Ff frames
 -          = Fd frames
 -          = Fd/f seconds
 - 
 -         So if we accept a difference of 1 frame, ie 1/f seconds, we can
 -         say that
 -
 -         1/f = Fd/f
 -      ie 1 = Fd
 -      ie d = 1/F
 - 
 -         So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
 -         FPS error is 1/F ~= 0.0001 ~= 10-e4
 -      */
 -
 -      return (fabs (a - b) < 1e-4);
 -}
 -
  /** @param An arbitrary audio frame rate.
   *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
   */
@@@ -753,6 -776,17 +751,6 @@@ ensure_ui_thread (
        assert (boost::this_thread::get_id() == ui_thread);
  }
  
 -/** @param v Content video frame.
 - *  @param audio_sample_rate Source audio sample rate.
 - *  @param frames_per_second Number of video frames per second.
 - *  @return Equivalent number of audio frames for `v'.
 - */
 -int64_t
 -video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
 -{
 -      return ((int64_t) v * audio_sample_rate / frames_per_second);
 -}
 -
  string
  audio_channel_name (int c)
  {
        return channels[c];
  }
  
 -FrameRateConversion::FrameRateConversion (float source, int dcp)
 -      : skip (false)
 -      , repeat (1)
 -      , change_speed (false)
 -{
 -      if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
 -              /* The difference between source and DCP frame rate will be lower
 -                 (i.e. better) if we skip.
 -              */
 -              skip = true;
 -      } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
 -              /* The difference between source and DCP frame rate would be better
 -                 if we repeated each frame once; it may be better still if we
 -                 repeated more than once.  Work out the required repeat.
 -              */
 -              repeat = round (dcp / source);
 -      }
 -
 -      change_speed = !about_equal (source * factor(), dcp);
 -
 -      if (!skip && repeat == 1 && !change_speed) {
 -              description = _("Content and DCP have the same rate.\n");
 -      } else {
 -              if (skip) {
 -                      description = _("DCP will use every other frame of the content.\n");
 -              } else if (repeat == 2) {
 -                      description = _("Each content frame will be doubled in the DCP.\n");
 -              } else if (repeat > 2) {
 -                      description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
 -              }
 -
 -              if (change_speed) {
 -                      float const pc = dcp * 100 / (source * factor());
 -                      description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
 -              }
 -      }
 -}
 -
  LocaleGuard::LocaleGuard ()
        : _old (0)
  {
@@@ -822,7 -894,7 +820,7 @@@ tidy_for_filename (string f
        return t;
  }
  
 -shared_ptr<const libdcp::Signer>
 +shared_ptr<const dcp::Signer>
  make_signer ()
  {
        boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
                if (!boost::filesystem::exists (p)) {
                        boost::filesystem::remove_all (sd);
                        boost::filesystem::create_directories (sd);
 -                      libdcp::make_signer_chain (sd, openssl_path ());
 +                      dcp::make_signer_chain (sd, openssl_path ());
                        break;
                }
  
                ++i;
        }
        
 -      libdcp::CertificateChain chain;
 +      dcp::CertificateChain chain;
  
        {
                boost::filesystem::path p (sd);
                p /= "ca.self-signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 +              chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
  
        {
                boost::filesystem::path p (sd);
                p /= "intermediate.signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 +              chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
  
        {
                boost::filesystem::path p (sd);
                p /= "leaf.signed.pem";
 -              chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
 +              chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
  
        boost::filesystem::path signer_key (sd);
        signer_key /= "leaf.key";
  
 -      return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
 +      return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
  }
  
  map<string, string>
@@@ -921,14 -993,14 +919,14 @@@ split_get_request (string url
        return r;
  }
  
 -libdcp::Size
 -fit_ratio_within (float ratio, libdcp::Size full_frame)
 +dcp::Size
 +fit_ratio_within (float ratio, dcp::Size full_frame)
  {
        if (ratio < full_frame.ratio ()) {
 -              return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
 +              return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
        }
        
 -      return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
 +      return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
  }
  
  void *
diff --combined src/lib/video_content.cc
index b6a2d031857ad2e9f644ebfa06b5479dcc743989,4a1ef95acfc3335995f420685e3f5d1de0a8fd9e..5864342a265c0fc873c302490acb3191f8f251bd
@@@ -19,7 -19,7 +19,7 @@@
  
  #include <iomanip>
  #include <libcxml/cxml.h>
 -#include <libdcp/colour_matrix.h>
 +#include <dcp/colour_matrix.h>
  #include "video_content.h"
  #include "video_examiner.h"
  #include "compose.hpp"
@@@ -61,7 -61,7 +61,7 @@@ VideoContent::VideoContent (shared_ptr<
        setup_default_colour_conversion ();
  }
  
 -VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
 +VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
        : Content (f, s)
        , _video_length (len)
        , _video_frame_rate (0)
@@@ -84,7 -84,7 +84,7 @@@ VideoContent::VideoContent (shared_ptr<
  VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
        : Content (f, node)
  {
 -      _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
 +      _video_length = ContentTime (node->number_child<int64_t> ("VideoLength"));
        _video_size.width = node->number_child<int> ("VideoWidth");
        _video_size.height = node->number_child<int> ("VideoHeight");
        _video_frame_rate = node->number_child<float> ("VideoFrameRate");
@@@ -155,7 -155,7 +155,7 @@@ voi
  VideoContent::as_xml (xmlpp::Node* node) const
  {
        boost::mutex::scoped_lock lm (_mutex);
 -      node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
 +      node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length.get ()));
        node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
        node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
        node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
  void
  VideoContent::setup_default_colour_conversion ()
  {
 -      _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
 +      _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
  }
  
  void
  VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
  {
        /* These examiner calls could call other content methods which take a lock on the mutex */
 -      libdcp::Size const vs = d->video_size ();
 +      dcp::Size const vs = d->video_size ();
        float const vfr = d->video_frame_rate ();
        
        {
@@@ -319,24 -319,22 +319,25 @@@ VideoContent::technical_summary () cons
  {
        return String::compose (
                "video: length %1, size %2x%3, rate %4",
-               video_length().seconds(),
 -              video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate()
++              video_length_after_3d_combine().seconds(),
 +              video_size().width,
 +              video_size().height,
 +              video_frame_rate()
                );
  }
  
 -libdcp::Size
 +dcp::Size
  VideoContent::video_size_after_3d_split () const
  {
 -      libdcp::Size const s = video_size ();
 +      dcp::Size const s = video_size ();
        switch (video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
+       case VIDEO_FRAME_TYPE_3D_ALTERNATE:
                return s;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
 -              return libdcp::Size (s.width / 2, s.height);
 +              return dcp::Size (s.width / 2, s.height);
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
 -              return libdcp::Size (s.width, s.height / 2);
 +              return dcp::Size (s.width, s.height / 2);
        }
  
        assert (false);
@@@ -354,21 -352,28 +355,21 @@@ VideoContent::set_colour_conversion (Co
  }
  
  /** @return Video size after 3D split and crop */
 -libdcp::Size
 +dcp::Size
  VideoContent::video_size_after_crop () const
  {
        return crop().apply (video_size_after_3d_split ());
  }
  
  /** @param t A time offset from the start of this piece of content.
 - *  @return Corresponding frame index.
 + *  @return Corresponding time with respect to the content.
   */
 -VideoContent::Frame
 -VideoContent::time_to_content_video_frames (Time t) const
 +ContentTime
 +VideoContent::dcp_time_to_content_time (DCPTime t) const
  {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
 -      
 -      FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
 -
 -      /* Here we are converting from time (in the DCP) to a frame number in the content.
 -         Hence we need to use the DCP's frame rate and the double/skip correction, not
 -         the source's rate.
 -      */
 -      return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
 +      return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
  }
  
  VideoContentScale::VideoContentScale (Ratio const * r)
@@@ -445,14 -450,14 +446,14 @@@ VideoContentScale::name () cons
  /** @param display_container Size of the container that we are displaying this content in.
   *  @param film_container The size of the film's image.
   */
 -libdcp::Size
 -VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
 +dcp::Size
 +VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
  {
        if (_ratio) {
                return fit_ratio_within (_ratio->ratio (), display_container);
        }
  
 -      libdcp::Size const ac = c->video_size_after_crop ();
 +      dcp::Size const ac = c->video_size_after_crop ();
  
        /* Force scale if the film_container is smaller than the content's image */
        if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
        /* Scale the image so that it will be in the right place in film_container, even if display_container is a
           different size.
        */
 -      return libdcp::Size (
 +      return dcp::Size (
                c->video_size().width  * float(display_container.width)  / film_container.width,
                c->video_size().height * float(display_container.height) / film_container.height
                );
diff --combined src/lib/video_content.h
index d2b19480f056f1895ab722d1ee50a96ee1989d37,f846b7ac97a095e4db0eb95d5fb0a33d17f341f2..eeb49cfa543a3ed4a746ae7c01093d7e65eb6f79
@@@ -45,7 -45,7 +45,7 @@@ public
        VideoContentScale (bool);
        VideoContentScale (boost::shared_ptr<cxml::Node>);
  
 -      libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const;
 +      dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const;
        std::string id () const;
        std::string name () const;
        void as_xml (xmlpp::Node *) const;
@@@ -81,7 -81,7 +81,7 @@@ public
        typedef int Frame;
  
        VideoContent (boost::shared_ptr<const Film>);
 -      VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
 +      VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
        VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
        virtual std::string information () const;
        virtual std::string identifier () const;
  
 -      VideoContent::Frame video_length () const {
 +      ContentTime video_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_length;
        }
  
 -      VideoContent::Frame video_length_after_3d_combine () const {
++      ContentTime video_length_after_3d_combine () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               if (_video_frame_type == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
 -                      return _video_length / 2;
++                      return ContentTime (_video_length.get() / 2);
+               }
+               
+               return _video_length;
+       }
 -      libdcp::Size video_size () const {
 +      dcp::Size video_size () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_size;
        }
                return _colour_conversion;
        }
  
 -      libdcp::Size video_size_after_3d_split () const;
 -      libdcp::Size video_size_after_crop () const;
 +      dcp::Size video_size_after_3d_split () const;
 +      dcp::Size video_size_after_crop () const;
  
 -      VideoContent::Frame time_to_content_video_frames (Time) const;
 +      ContentTime dcp_time_to_content_time (DCPTime) const;
  
  protected:
        void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
  
 -      VideoContent::Frame _video_length;
 +      ContentTime _video_length;
        float _video_frame_rate;
  
  private:
  
        void setup_default_colour_conversion ();
        
 -      libdcp::Size _video_size;
 +      dcp::Size _video_size;
        VideoFrameType _video_frame_type;
        Crop _crop;
        VideoContentScale _scale;
diff --combined src/lib/video_decoder.cc
index 15f91b8923b99ea0a8eff66f3bfc69eb005b4158,3ae963a202f7081322f8e9bab5ab525d0d7309cc..144a494dcd48fc5a6cd9916d4d9a7d40c90fd2e5
  
  using std::cout;
  using boost::shared_ptr;
 +using boost::optional;
  
 -VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
 -      : Decoder (f)
 -      , _video_content (c)
 -      , _video_position (0)
 +VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
 +      : _video_content (c)
  {
  
  }
  
 +/** Called by subclasses when they have a video frame ready */
  void
 -VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
 +VideoDecoder::video (shared_ptr<const Image> image, bool same, ContentTime time)
  {
        switch (_video_content->video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
 -              Video (image, EYES_BOTH, same, frame);
 +              _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image, EYES_BOTH, same)));
                break;
 -              Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, same, frame / 2);
+       case VIDEO_FRAME_TYPE_3D_ALTERNATE:
++              Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, same);
+               break;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
        {
                int const half = image->size().width / 2;
 -              Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
 -              Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
 +              _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same)));
 +              _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same)));
                break;
        }
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
        {
                int const half = image->size().height / 2;
 -              Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
 -              Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
 +              _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same)));
 +              _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (time, image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same)));
                break;
        }
 +      default:
 +              assert (false);
        }
 -      
 -      _video_position = frame + 1;
  }
 -
diff --combined src/wx/timecode.cc
index 0453272547a65a800eef18b2bc61db41ee1b6654,a8c90b4882623a1ab027bdaee5745769ac6023b5..634a15625c7d1b7ed79f52255f33758f15b4a721
@@@ -83,36 -83,40 +83,40 @@@ Timecode::Timecode (wxWindow* parent
  }
  
  void
 -Timecode::set (Time t, int fps)
 +Timecode::set (DCPTime t, int fps)
  {
-       int const h = t.seconds() / 3600;
-       t -= DCPTime::from_seconds (h * 3600);
-       int const m = t.seconds() / 60;
-       t -= DCPTime::from_seconds (m * 60);
-       int const s = t.seconds();
-       t -= DCPTime::from_seconds (s);
-       int const f = rint (t.seconds() * fps);
+       /* Do this calculation with frames so that we can round
+          to a frame boundary at the start rather than the end.
+       */
 -      int64_t f = divide_with_round (t * fps, TIME_HZ);
++      int64_t f = rint (t.seconds() * fps);
+       
+       int const h = f / (3600 * fps);
+       f -= h * 3600 * fps;
+       int const m = f / (60 * fps);
+       f -= m * 60 * fps;
+       int const s = f / fps;
+       f -= s * fps;
  
        checked_set (_hours, lexical_cast<string> (h));
        checked_set (_minutes, lexical_cast<string> (m));
        checked_set (_seconds, lexical_cast<string> (s));
        checked_set (_frames, lexical_cast<string> (f));
  
-       _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02d", h, m, s, f));
+       _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02ld", h, m, s, f));
  }
  
 -Time
 +DCPTime
  Timecode::get (int fps) const
  {
 -      Time t = 0;
 +      DCPTime t;
        string const h = wx_to_std (_hours->GetValue ());
 -      t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
 +      t += DCPTime::from_seconds (lexical_cast<int> (h.empty() ? "0" : h) * 3600);
        string const m = wx_to_std (_minutes->GetValue());
 -      t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
 +      t += DCPTime::from_seconds (lexical_cast<int> (m.empty() ? "0" : m) * 60);
        string const s = wx_to_std (_seconds->GetValue());
 -      t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
 +      t += DCPTime::from_seconds (lexical_cast<int> (s.empty() ? "0" : s));
        string const f = wx_to_std (_frames->GetValue());
 -      t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
 +      t += DCPTime::from_seconds (lexical_cast<double> (f.empty() ? "0" : f) / fps);
  
        return t;
  }
diff --combined src/wx/timing_panel.cc
index b8b923a6b941752c80962fc6088430d4b715a12f,2321fd0dfdbfb7ac84ce8f1d12b603d9064adcf9..3fcb9b1753eeecc41d808ef2d27f7777196d1bbe
@@@ -85,31 -85,35 +85,35 @@@ TimingPanel::film_content_changed (int 
                if (content) {
                        _position->set (content->position (), _editor->film()->video_frame_rate ());
                } else {
 -                      _position->set (0, 24);
 +                      _position->set (DCPTime () , 24);
                }
-       } else if (property == ContentProperty::LENGTH || property == VideoContentProperty::VIDEO_FRAME_RATE) {
+       } else if (
+               property == ContentProperty::LENGTH ||
+               property == VideoContentProperty::VIDEO_FRAME_RATE ||
+               property == VideoContentProperty::VIDEO_FRAME_TYPE
+               ) {
                if (content) {
                        _full_length->set (content->full_length (), _editor->film()->video_frame_rate ());
                        _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ());
                } else {
 -                      _full_length->set (0, 24);
 -                      _play_length->set (0, 24);
 +                      _full_length->set (DCPTime (), 24);
 +                      _play_length->set (DCPTime (), 24);
                }
        } else if (property == ContentProperty::TRIM_START) {
                if (content) {
                        _trim_start->set (content->trim_start (), _editor->film()->video_frame_rate ());
                        _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ());
                } else {
 -                      _trim_start->set (0, 24);
 -                      _play_length->set (0, 24);
 +                      _trim_start->set (DCPTime (), 24);
 +                      _play_length->set (DCPTime (), 24);
                }
        } else if (property == ContentProperty::TRIM_END) {
                if (content) {
                        _trim_end->set (content->trim_end (), _editor->film()->video_frame_rate ());
                        _play_length->set (content->length_after_trim (), _editor->film()->video_frame_rate ());
                } else {
 -                      _trim_end->set (0, 24);
 -                      _play_length->set (0, 24);
 +                      _trim_end->set (DCPTime (), 24);
 +                      _play_length->set (DCPTime (), 24);
                }
        }
  
@@@ -149,8 -153,7 +153,8 @@@ TimingPanel::full_length_changed (
        if (c.size() == 1) {
                shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
                if (ic && ic->still ()) {
 -                      ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
 +                      /* XXX: No effective FRC here... is this right? */
 +                      ic->set_video_length (ContentTime (_full_length->get (_editor->film()->video_frame_rate()), FrameRateChange (1, 1)));
                }
        }
  }
diff --combined src/wx/video_panel.cc
index 533545f64406932be8cc00737646f1d8f9c7d2cf,56848907b3e96a72e2d7d42f9e75af8cb91aed38..fad824727074574ad0ef9f71f23c94f6237de415
@@@ -196,6 -196,7 +196,7 @@@ VideoPanel::VideoPanel (FilmEditor* e
        _frame_type->wrapped()->Append (_("2D"));
        _frame_type->wrapped()->Append (_("3D left/right"));
        _frame_type->wrapped()->Append (_("3D top/bottom"));
+       _frame_type->wrapped()->Append (_("3D alternate"));
  
        _filters_button->Bind           (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_filters_clicked, this));
        _colour_conversion_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_colour_conversion_clicked, this));
@@@ -294,8 -295,8 +295,8 @@@ VideoPanel::setup_description (
        }
  
        Crop const crop = vcs->crop ();
 -      if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
 -              libdcp::Size cropped = vcs->video_size_after_crop ();
 +      if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
 +              dcp::Size cropped = vcs->video_size_after_crop ();
                d << wxString::Format (
                        _("Cropped to %dx%d (%.2f:1)\n"),
                        cropped.width, cropped.height,
                ++lines;
        }
  
 -      libdcp::Size const container_size = _editor->film()->frame_size ();
 -      libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
 +      dcp::Size const container_size = _editor->film()->frame_size ();
 +      dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
  
        if (scaled != vcs->video_size_after_crop ()) {
                d << wxString::Format (
  
        d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
        ++lines;
 -      FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
 +      FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
        d << std_to_wx (frc.description) << "\n";
        ++lines;
  
diff --combined test/play_test.cc
index 690b042d8c32722f78e1c75e5c800e22cc685251,51e2272568acbf26a1bc321d0ad1bdf63af3bc36..ed3e497ed6e3c4fc6248537ad1e98f7d22ce1241
@@@ -34,7 -34,7 +34,7 @@@ struct Vide
  {
        boost::shared_ptr<Content> content;
        boost::shared_ptr<const Image> image;
 -      Time time;
 +      DCPTime time;
  };
  
  class PlayerWrapper
@@@ -46,11 -46,11 +46,11 @@@ public
                _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _5));
        }
  
 -      void process_video (shared_ptr<PlayerImage> i, bool, Time t)
 +      void process_video (shared_ptr<PlayerImage> i, bool, DCPTime t)
        {
                Video v;
                v.content = _player->_last_video;
 -              v.image = i->image ();
 +              v.image = i->image (PIX_FMT_RGB24, false);
                v.time = t;
                _queue.push_front (v);
        }
@@@ -67,7 -67,7 +67,7 @@@
                return v;
        }
  
 -      void seek (Time t, bool ac)
 +      void seek (DCPTime t, bool ac)
        {
                _player->seek (t, ac);
                _queue.clear ();
@@@ -89,23 -89,25 +89,23 @@@ BOOST_AUTO_TEST_CASE (play_test
        film->examine_and_add_content (A);
        wait_for_jobs ();
  
-       BOOST_CHECK_EQUAL (A->video_length().frames (24), 16);
 -      BOOST_CHECK_EQUAL (A->video_length_after_3d_combine(), 16);
++      BOOST_CHECK_EQUAL (A->video_length_after_3d_combine().frames (24), 16);
  
        shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/red_30.mp4"));
        film->examine_and_add_content (B);
        wait_for_jobs ();
  
-       BOOST_CHECK_EQUAL (B->video_length().frames (30), 16);
 -      BOOST_CHECK_EQUAL (B->video_length_after_3d_combine(), 16);
++      BOOST_CHECK_EQUAL (B->video_length_after_3d_combine().frames (30), 16);
        
        /* Film should have been set to 25fps */
        BOOST_CHECK_EQUAL (film->video_frame_rate(), 25);
  
 -      BOOST_CHECK_EQUAL (A->position(), 0);
 +      BOOST_CHECK_EQUAL (A->position(), DCPTime ());
        /* A is 16 frames long at 25 fps */
 -      BOOST_CHECK_EQUAL (B->position(), 16 * TIME_HZ / 25);
 +      BOOST_CHECK_EQUAL (B->position(), DCPTime::from_frames (16, 25));
  
        shared_ptr<Player> player = film->make_player ();
        PlayerWrapper wrap (player);
 -      /* Seek and audio don't get on at the moment */
 -      player->disable_audio ();
  
        for (int i = 0; i < 32; ++i) {
                optional<Video> v = wrap.get_video ();
                }
        }
  
 -      player->seek (10 * TIME_HZ / 25, true);
 +      player->seek (DCPTime::from_frames (6, 25), true);
        optional<Video> v = wrap.get_video ();
        BOOST_CHECK (v);
 -      BOOST_CHECK_EQUAL (v.get().time, 10 * TIME_HZ / 25);
 +      BOOST_CHECK_EQUAL (v.get().time, DCPTime::from_frames (6, 25));
  }
  
  #endif