Merge master.
authorCarl Hetherington <cth@carlh.net>
Thu, 12 Jun 2014 21:27:11 +0000 (22:27 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 12 Jun 2014 21:27:11 +0000 (22:27 +0100)
50 files changed:
1  2 
ChangeLog
cscript
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/config.cc
src/lib/config.h
src/lib/content.cc
src/lib/dcp_content_type.cc
src/lib/dcp_content_type.h
src/lib/ffmpeg_content.cc
src/lib/film.cc
src/lib/film.h
src/lib/frame_rate_change.h
src/lib/image_content.cc
src/lib/image_proxy.cc
src/lib/isdcf_metadata.cc
src/lib/isdcf_metadata.h
src/lib/player.cc
src/lib/playlist.h
src/lib/ratio.cc
src/lib/ratio.h
src/lib/server.cc
src/lib/sndfile_content.cc
src/lib/util.cc
src/lib/video_content.cc
src/lib/wscript
src/tools/dcpomatic_create.cc
src/wx/about_dialog.cc
src/wx/audio_panel.cc
src/wx/config_dialog.cc
src/wx/content_widget.h
src/wx/film_editor.cc
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timing_panel.cc
src/wx/video_panel.cc
src/wx/wscript
src/wx/wx_util.cc
test/4k_test.cc
test/audio_delay_test.cc
test/black_fill_test.cc
test/client_server_test.cc
test/colour_conversion_test.cc
test/film_metadata_test.cc
test/frame_rate_test.cc
test/recover_test.cc
test/scaling_test.cc
test/silence_padding_test.cc
test/test.cc
test/wscript

diff --cc ChangeLog
+++ b/ChangeLog
@@@ -1,7 -1,55 +1,59 @@@
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
 +
+ 2014-06-12  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.26 released.
+ 2014-06-12  Carl Hetherington  <cth@carlh.net>
+       * Fix bug where DCP-o-matic does not recreate video after
+       subtitles are turned on or off.
+ 2014-06-10  Carl Hetherington  <cth@carlh.net>
+       * Support ISDCF naming convention version 9 (#257).
+       * Rename DCI to ISDCF when talking about the digital cinema
+       naming convention (#362).
+       * Fix crash when opening the timeline with no content (#369).
+ 2014-06-09  Carl Hetherington  <cth@carlh.net>
+       * Fix server/client with non-RGB24 sources.
+       * Version 1.69.25 released.
+ 2014-06-09  Carl Hetherington  <cth@carlh.net>
+       * Make audio gain a floating-point value in the UI (#367).
+       * Work-around out-of-memory crashes with large start trims (#252).
+       * Version 1.69.24 released.
+ 2014-06-06  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.23 released.
+ 2014-06-05  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.22 released.
+ 2014-06-05  Carl Hetherington  <cth@carlh.net>
+       * Large speed-up to multi-image source file decoding.
+       * Back-port changes from v2 which work out how separate
+       audio files should be resampled by looking at the video
+       files which are present at the same time.
+ 2014-06-03  Carl Hetherington  <cth@carlh.net>
+       * Version 1.69.21 released.
  2014-06-03  Carl Hetherington  <cth@carlh.net>
  
        * Fix bad resampling of separate sound file sources that
diff --cc cscript
Simple merge
Simple merge
@@@ -58,24 -51,21 +58,24 @@@ public
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
  
 +      /** @return number of audio channels in the content */
        virtual int audio_channels () const = 0;
 -      virtual AudioContent::Frame audio_length () const = 0;
 -      virtual int content_audio_frame_rate () const = 0;
 +      /** @return the length of the audio in the content */
 +      virtual ContentTime audio_length () const = 0;
 +      /** @return the frame rate of the content */
 +      virtual int audio_frame_rate () const = 0;
        virtual AudioMapping audio_mapping () const = 0;
 -      virtual void set_audio_mapping (AudioMapping) = 0;
 +      virtual void set_audio_mapping (AudioMapping);
        virtual boost::filesystem::path audio_analysis_path () const;
  
 -      int output_audio_frame_rate () const;
 -      
 +      int resampled_audio_frame_rate () const;
 +
        boost::signals2::connection analyse_audio (boost::function<void()>);
  
-       void set_audio_gain (float);
+       void set_audio_gain (double);
        void set_audio_delay (int);
        
-       float audio_gain () const {
+       double audio_gain () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_gain;
        }
Simple merge
@@@ -28,8 -28,8 +28,8 @@@
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/metadata.h>
 +#include <dcp/metadata.h>
- #include "dci_metadata.h"
+ #include "isdcf_metadata.h"
  #include "colour_conversion.h"
  #include "server.h"
  
@@@ -40,8 -36,9 +40,9 @@@ using std::set
  using std::list;
  using std::cout;
  using std::vector;
+ using std::max;
  using boost::shared_ptr;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  int const ContentProperty::PATH = 400;
  int const ContentProperty::POSITION = 401;
@@@ -209,13 -206,22 +210,13 @@@ Content::clone () cons
  string
  Content::technical_summary () const
  {
 -      return String::compose ("%1 %2 %3", path_summary(), digest(), position());
 +      return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
  }
  
 -Time
 +DCPTime
  Content::length_after_trim () const
  {
-       return full_length() - trim_start() - trim_end();
 -      return max (int64_t (0), full_length() - trim_start() - trim_end());
 -}
 -
 -/** @param t A time relative to the start of this content (not the position).
 - *  @return true if this time is trimmed by our trim settings.
 - */
 -bool
 -Content::trimmed (Time t) const
 -{
 -      return (t < trim_start() || t > (full_length() - trim_end ()));
++      return max (DCPTime (), full_length() - trim_start() - trim_end());
  }
  
  /** @return string which includes everything about how this content affects
@@@ -30,10 -30,10 +30,10 @@@ using namespace std
  
  vector<DCPContentType const *> DCPContentType::_dcp_content_types;
  
 -DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
 +DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d)
        : _pretty_name (p)
        , _libdcp_kind (k)
-       , _dci_name (d)
+       , _isdcf_name (d)
  {
  
  }
@@@ -58,8 -58,8 +58,8 @@@ public
  
  private:
        std::string _pretty_name;
 -      libdcp::ContentKind _libdcp_kind;
 +      dcp::ContentKind _libdcp_kind;
-       std::string _dci_name;
+       std::string _isdcf_name;
  
        /** All available DCP content types */
        static std::vector<DCPContentType const *> _dcp_content_types;
@@@ -394,18 -482,3 +395,21 @@@ FFmpegContent::audio_analysis_path () c
        p /= name;
        return p;
  }
 +
 +bool
 +FFmpegContent::has_subtitle_during (ContentTimePeriod period) const
 +{
 +      shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream ();
++      if (!stream) {
++              return false;
++      }
 +
 +      /* XXX: inefficient */
 +      for (vector<ContentTimePeriod>::const_iterator i = stream->periods.begin(); i != stream->periods.end(); ++i) {
 +              if (i->from <= period.to && i->to >= period.from) {
 +                      return true;
 +              }
 +      }
 +
 +      return false;
 +}
diff --cc src/lib/film.cc
@@@ -90,11 -90,10 +90,13 @@@ using dcp::raw_convert
   * Subtitle offset changed to subtitle y offset, and subtitle x offset added.
   * 7 -> 8
   * Use <Scale> tag in <VideoContent> rather than <Ratio>.
+  * 8 -> 9
+  * DCI -> ISDCF
 + *
 + * Bumped to 32 for 2.0 branch; some times are expressed in Times rather
 + * than frames now.
   */
 -int const Film::current_state_version = 9;
 +int const Film::current_state_version = 32;
  
  /** Construct a Film object in a given directory.
   *
@@@ -511,12 -552,30 +555,30 @@@ Film::isdcf_name (bool if_created_now) 
        if (video_frame_rate() != 24) {
                d << "-" << video_frame_rate();
        }
+       
        if (container()) {
-               d << "_" << container()->dci_name();
+               d << "_" << container()->isdcf_name();
        }
  
-       DCIMetadata const dm = dci_metadata ();
+       /* XXX: this only works for content which has been scaled to a given ratio,
+          and uses the first bit of content only.
+       */
+       /* The standard says we don't do this for trailers, for some strange reason */
 -      if (dcp_content_type() && dcp_content_type()->libdcp_kind() != libdcp::TRAILER) {
++      if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
+               ContentList cl = content ();
+               Ratio const * content_ratio = 0;
+               for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
+                       if (vc && (content_ratio == 0 || vc->scale().ratio() != content_ratio)) {
+                               content_ratio = vc->scale().ratio();
+                       }
+               }
+               
+               if (content_ratio && content_ratio != container()) {
+                       d << "-" << content_ratio->isdcf_name();
+               }
+       }
  
        if (!dm.audio_language.empty ()) {
                d << "_" << dm.audio_language;
diff --cc src/lib/film.h
  #include <boost/signals2.hpp>
  #include <boost/enable_shared_from_this.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/key.h>
 -#include <libdcp/kdm.h>
 +#include <dcp/key.h>
 +#include <dcp/decrypted_kdm.h>
 +#include <dcp/encrypted_kdm.h>
  #include "util.h"
  #include "types.h"
- #include "dci_metadata.h"
+ #include "isdcf_metadata.h"
+ #include "frame_rate_change.h"
  
  class DCPContentType;
  class Log;
Simple merge
Simple merge
@@@ -69,8 -69,9 +69,9 @@@ voi
  RawImageProxy::add_metadata (xmlpp::Node* node) const
  {
        node->add_child("Type")->add_child_text (N_("Raw"));
 -      node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width));
 -      node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height));
 -      node->add_child("PixelFormat")->add_child_text (libdcp::raw_convert<string> (_image->pixel_format ()));
 +      node->add_child("Width")->add_child_text (dcp::raw_convert<string> (_image->size().width));
 +      node->add_child("Height")->add_child_text (dcp::raw_convert<string> (_image->size().height));
++      node->add_child("PixelFormat")->add_child_text (dcp::raw_convert<string> (_image->pixel_format ()));
  }
  
  void
@@@ -127,22 -128,17 +128,16 @@@ MagickImageProxy::image () cons
                throw DecodeError (_("Could not decode image file"));
        }
  
 +      dcp::Size size (magick_image->columns(), magick_image->rows());
        LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ());
  
 -      libdcp::Size size (magick_image->columns(), magick_image->rows());
 -
        _image.reset (new Image (PIX_FMT_RGB24, size, true));
  
-       using namespace MagickCore;
-       
+       /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */
        uint8_t* p = _image->data()[0];
-       for (int y = 0; y < size.height; ++y) {
-               uint8_t* q = p;
-               for (int x = 0; x < size.width; ++x) {
-                       Magick::Color c = magick_image->pixelColor (x, y);
-                       *q++ = c.redQuantum() * 255 / QuantumRange;
-                       *q++ = c.greenQuantum() * 255 / QuantumRange;
-                       *q++ = c.blueQuantum() * 255 / QuantumRange;
-               }
+       for (int i = 0; i < size.height; ++i) {
+               using namespace MagickCore;
+               magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p);
                p += _image->stride()[0];
        }
  
index 0000000,dfba50a..7d960b6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,88 +1,88 @@@
 -#include <libdcp/raw_convert.h>
+ /*
+     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
+     the Free Software Foundation; either version 2 of the License, or
+     (at your option) any later version.
+     This program 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 this program; if not, write to the Free Software
+     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ #include <iostream>
+ #include <libcxml/cxml.h>
 -using libdcp::raw_convert;
++#include <dcp/raw_convert.h>
+ #include "isdcf_metadata.h"
+ #include "i18n.h"
+ using std::string;
+ using boost::shared_ptr;
 -ISDCFMetadata::ISDCFMetadata (shared_ptr<const cxml::Node> node)
++using dcp::raw_convert;
++ISDCFMetadata::ISDCFMetadata (cxml::ConstNodePtr node)
+ {
+       content_version = node->number_child<int> ("ContentVersion");
+       audio_language = node->string_child ("AudioLanguage");
+       subtitle_language = node->string_child ("SubtitleLanguage");
+       territory = node->string_child ("Territory");
+       rating = node->string_child ("Rating");
+       studio = node->string_child ("Studio");
+       facility = node->string_child ("Facility");
+       package_type = node->string_child ("PackageType");
+       /* This stuff was added later */
+       temp_version = node->optional_bool_child ("TempVersion").get_value_or (false);
+       pre_release = node->optional_bool_child ("PreRelease").get_value_or (false);
+       red_band = node->optional_bool_child ("RedBand").get_value_or (false);
+       chain = node->optional_string_child ("Chain").get_value_or ("");
+       two_d_version_of_three_d = node->optional_bool_child ("TwoDVersionOfThreeD").get_value_or (false);
+       mastered_luminance = node->optional_string_child ("MasteredLuminance").get_value_or ("");
+ }
+ void
+ ISDCFMetadata::as_xml (xmlpp::Node* root) const
+ {
+       root->add_child("ContentVersion")->add_child_text (raw_convert<string> (content_version));
+       root->add_child("AudioLanguage")->add_child_text (audio_language);
+       root->add_child("SubtitleLanguage")->add_child_text (subtitle_language);
+       root->add_child("Territory")->add_child_text (territory);
+       root->add_child("Rating")->add_child_text (rating);
+       root->add_child("Studio")->add_child_text (studio);
+       root->add_child("Facility")->add_child_text (facility);
+       root->add_child("PackageType")->add_child_text (package_type);
+       root->add_child("TempVersion")->add_child_text (temp_version ? "1" : "0");
+       root->add_child("PreRelease")->add_child_text (pre_release ? "1" : "0");
+       root->add_child("RedBand")->add_child_text (red_band ? "1" : "0");
+       root->add_child("Chain")->add_child_text (chain);
+       root->add_child("TwoDVersionOfThreeD")->add_child_text (two_d_version_of_three_d ? "1" : "0");
+       root->add_child("MasteredLuminance")->add_child_text (mastered_luminance);
+ }
+ void
+ ISDCFMetadata::read_old_metadata (string k, string v)
+ {
+       if (k == N_("audio_language")) {
+               audio_language = v;
+       } else if (k == N_("subtitle_language")) {
+               subtitle_language = v;
+       } else if (k == N_("territory")) {
+               territory = v;
+       } else if (k == N_("rating")) {
+               rating = v;
+       } else if (k == N_("studio")) {
+               studio = v;
+       } else if (k == N_("facility")) {
+               facility = v;
+       } else if (k == N_("package_type")) {
+               package_type = v;
+       }
+ }     
index 0000000,0fb7e7b..e63f290
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,68 +1,65 @@@
 -
 -namespace cxml {
 -      class Node;
 -}
+ /*
+     Copyright (C) 2012 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
+     the Free Software Foundation; either version 2 of the License, or
+     (at your option) any later version.
+     This program 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 this program; if not, write to the Free Software
+     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ #ifndef DCPOMATIC_ISDCF_METADATA_H
+ #define DCPOMATIC_ISDCF_METADATA_H
+ #include <string>
+ #include <libxml++/libxml++.h>
 -      ISDCFMetadata (boost::shared_ptr<const cxml::Node>);
++#include <libcxml/cxml.h>
+ class ISDCFMetadata
+ {
+ public:
+       ISDCFMetadata ()
+               : content_version (1)
+               , temp_version (false)
+               , pre_release (false)
+               , red_band (false)
+               , two_d_version_of_three_d (false)
+       {}
+       
++      ISDCFMetadata (cxml::ConstNodePtr);
+       void as_xml (xmlpp::Node *) const;
+       void read_old_metadata (std::string, std::string);
+       int content_version;
+       std::string audio_language;
+       std::string subtitle_language;
+       std::string territory;
+       std::string rating;
+       std::string studio;
+       std::string facility;
+       std::string package_type;
+       /** true if this is a temporary version (without final picture or sound) */
+       bool temp_version;
+       /** true if this is a pre-release version (final picture and sound, but without accessibility features) */
+       bool pre_release;
+       /** true if this has adult content */
+       bool red_band;
+       /** specific theatre chain or event */
+       std::string chain;
+       /** true if this is a 2D version of content that also exists in 3D */
+       bool two_d_version_of_three_d;
+       /** mastered luminance if there are multiple versions distributed (e.g. 35, 4fl, 6fl etc.) */
+       std::string mastered_luminance;
+ };
+ #endif
  #include "image.h"
  #include "image_proxy.h"
  #include "ratio.h"
 -#include "resampler.h"
  #include "log.h"
  #include "scaler.h"
 +#include "render_subtitles.h"
 +#include "config.h"
 +#include "content_video.h"
  #include "player_video_frame.h"
+ #include "frame_rate_change.h"
  
  #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
  
@@@ -25,7 -25,7 +25,8 @@@
  #include <boost/enable_shared_from_this.hpp>
  #include "ffmpeg_content.h"
  #include "audio_mapping.h"
 +#include "util.h"
+ #include "frame_rate_change.h"
  
  class Content;
  class FFmpegContent;
Simple merge
diff --cc src/lib/ratio.h
Simple merge
Simple merge
@@@ -155,6 -166,5 +155,5 @@@ SndfileContent::set_audio_mapping (Audi
                _audio_mapping = m;
        }
  
 -      signal_changed (AudioContentProperty::AUDIO_MAPPING);
 +      AudioContent::set_audio_mapping (m);
  }
diff --cc src/lib/util.cc
Simple merge
Simple merge
diff --cc src/lib/wscript
@@@ -13,12 -13,10 +13,11 @@@ sources = ""
            config.cc
            content.cc
            content_factory.cc
 +          content_subtitle.cc
            cross.cc
-           dci_metadata.cc
            dcp_content_type.cc
            dcp_video_frame.cc
 -          decoder.cc
 +          dcpomatic_time.cc
            dolby_cp750.cc
            encoder.cc
            examine_content_job.cc
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -66,9 -64,9 +66,9 @@@ public
  protected:
        virtual void do_paint (wxGraphicsContext *) = 0;
        
 -      int time_x (Time t) const
 +      int time_x (DCPTime t) const
        {
-               return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second ();
 -              return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit().get_value_or (0);
++              return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second().get_value_or (0);
        }
        
        Timeline& _timeline;
@@@ -105,7 -101,7 +105,7 @@@ public
                return dcpomatic::Rect<int> (
                        time_x (content->position ()) - 8,
                        y_pos (_track.get()) - 8,
-                       content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16,
 -                      content->length_after_trim () * _timeline.pixels_per_time_unit().get_value_or(0) + 16,
++                      content->length_after_trim().seconds() * _timeline.pixels_per_second().get_value_or(0) + 16,
                        _timeline.track_height() + 16
                        );
        }
@@@ -176,7 -172,7 +176,7 @@@ private
                wxDouble name_leading;
                gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
                
-               gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height()));
 -              gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len * _timeline.pixels_per_time_unit().get_value_or(0), _timeline.track_height()));
++              gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second().get_value_or(0), _timeline.track_height()));
                gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4);
                gc->ResetClip ();
        }
@@@ -292,20 -269,26 +292,26 @@@ private
  
        void do_paint (wxGraphicsContext* gc)
        {
 -              if (!_timeline.pixels_per_time_unit()) {
++              if (!_timeline.pixels_per_second()) {
+                       return;
+               }
 -              double const pptu = _timeline.pixels_per_time_unit().get ();
++              double const pps = _timeline.pixels_per_second().get ();
+               
                gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
                
-               double mark_interval = rint (128 / _timeline.pixels_per_second ());
 -              int mark_interval = rint (128 / (TIME_HZ * pptu));
++              double mark_interval = rint (128 / pps);
                if (mark_interval > 5) {
 -                      mark_interval -= mark_interval % 5;
 +                      mark_interval -= int (rint (mark_interval)) % 5;
                }
                if (mark_interval > 10) {
 -                      mark_interval -= mark_interval % 10;
 +                      mark_interval -= int (rint (mark_interval)) % 10;
                }
                if (mark_interval > 60) {
 -                      mark_interval -= mark_interval % 60;
 +                      mark_interval -= int (rint (mark_interval)) % 60;
                }
                if (mark_interval > 3600) {
 -                      mark_interval -= mark_interval % 3600;
 +                      mark_interval -= int (rint (mark_interval)) % 3600;
                }
                
                if (mark_interval < 1) {
                path.AddLineToPoint (_timeline.width(), _y);
                gc->StrokePath (path);
  
 -              Time t = 0;
 -              while ((t * pptu) < _timeline.width()) {
 +              /* Time in seconds */
 +              DCPTime t;
-               while ((t.seconds() * _timeline.pixels_per_second()) < _timeline.width()) {
++              while ((t.seconds() * pps) < _timeline.width()) {
                        wxGraphicsPath path = gc->CreatePath ();
                        path.MoveToPoint (time_x (t), _y - 4);
                        path.AddLineToPoint (time_x (t), _y + 4);
                        wxDouble str_leading;
                        gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
                        
-                       int const tx = _timeline.x_offset() + t.seconds() * _timeline.pixels_per_second();
 -                      int const tx = _timeline.x_offset() + t * pptu;
++                      int const tx = _timeline.x_offset() + t.seconds() * pps;
                        if ((tx + str_width) < _timeline.width()) {
                                gc->DrawText (str, time_x (t), _y + 16);
                        }
@@@ -602,6 -580,12 +607,12 @@@ Timeline::right_down (wxMouseEvent& ev
  void
  Timeline::set_position_from_event (wxMouseEvent& ev)
  {
 -      if (!_pixels_per_time_unit) {
++      if (!_pixels_per_second) {
+               return;
+       }
 -      double const pptu = _pixels_per_time_unit.get ();
++      double const pps = _pixels_per_second.get ();
        wxPoint const p = ev.GetPosition();
  
        if (!_first_move) {
                return;
        }
        
-       DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / _pixels_per_second);
 -      Time new_position = _down_view_position + (p.x - _down_point.x) / pptu;
++      DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
        
        if (_snap) {
                
                
                if (!first) {
                        /* Snap if it's close; `close' means within a proportion of the time on the timeline */
-                       if (nearest_distance < DCPTime::from_seconds ((width() / pixels_per_second()) / 32)) {
 -                      if (nearest_distance < (width() / pptu) / 32) {
++                      if (nearest_distance < DCPTime::from_seconds ((width() / pps) / 32)) {
                                new_position = nearest_new_position;
                        }
                }
@@@ -52,8 -52,8 +52,8 @@@ public
                return 48;
        }
  
-       double pixels_per_second () const {
 -      boost::optional<double> pixels_per_time_unit () const {
 -              return _pixels_per_time_unit;
++      boost::optional<double> pixels_per_second () const {
 +              return _pixels_per_second;
        }
  
        Position<int> tracks_position () const {
@@@ -96,7 -96,7 +96,7 @@@ private
        ViewList _views;
        boost::shared_ptr<TimeAxisView> _time_axis_view;
        int _tracks;
-       double _pixels_per_second;
 -      boost::optional<double> _pixels_per_time_unit;
++      boost::optional<double> _pixels_per_second;
        bool _left_down;
        wxPoint _down_point;
        boost::shared_ptr<ContentView> _down_view;
Simple merge
Simple merge
diff --cc src/wx/wscript
Simple merge
Simple merge
diff --cc test/4k_test.cc
Simple merge
Simple merge
Simple merge
@@@ -47,12 -39,12 +47,12 @@@ do_remote_encode (shared_ptr<DCPVideoFr
        BOOST_CHECK (remotely_encoded);
        
        BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
 -      BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
 +      BOOST_CHECK_EQUAL (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()), 0);
  }
  
- BOOST_AUTO_TEST_CASE (client_server_test)
+ BOOST_AUTO_TEST_CASE (client_server_test_rgb)
  {
 -      shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
 +      shared_ptr<Image> image (new Image (PIX_FMT_RGB24, dcp::Size (1998, 1080), true));
        uint8_t* p = image->data()[0];
        
        for (int y = 0; y < 1080; ++y) {
                p += sub_image->stride()[0];
        }
  
-       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log"));
+       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test_rgb.log"));
+       shared_ptr<PlayerVideoFrame> pvf (
+               new PlayerVideoFrame (
+                       shared_ptr<ImageProxy> (new RawImageProxy (image, log)),
+                       Crop (),
 -                      libdcp::Size (1998, 1080),
 -                      libdcp::Size (1998, 1080),
++                      dcp::Size (1998, 1080),
++                      dcp::Size (1998, 1080),
+                       Scaler::from_id ("bicubic"),
+                       EYES_BOTH,
+                       PART_WHOLE,
+                       ColourConversion ()
+                       )
+               );
 -      pvf->set_subtitle (sub_image, Position<int> (50, 60));
++      pvf->set_subtitle (PositionImage (sub_image, Position<int> (50, 60)));
+       shared_ptr<DCPVideoFrame> frame (
+               new DCPVideoFrame (
+                       pvf,
+                       0,
+                       24,
+                       200000000,
+                       RESOLUTION_2K,
+                       log
+                       )
+               );
+       shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
+       BOOST_ASSERT (locally_encoded);
+       
+       Server* server = new Server (log, true);
+       new thread (boost::bind (&Server::run, server, 2));
+       /* Let the server get itself ready */
+       dcpomatic_sleep (1);
+       ServerDescription description ("localhost", 2);
+       list<thread*> threads;
+       for (int i = 0; i < 8; ++i) {
+               threads.push_back (new thread (boost::bind (do_remote_encode, frame, description, locally_encoded)));
+       }
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               (*i)->join ();
+       }
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               delete *i;
+       }
+ }
+ BOOST_AUTO_TEST_CASE (client_server_test_yuv)
+ {
 -      shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (1998, 1080), true));
++      shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, dcp::Size (1998, 1080), true));
+       uint8_t* p = image->data()[0];
+       for (int i = 0; i < image->components(); ++i) {
+               uint8_t* p = image->data()[i];
+               for (int j = 0; j < image->line_size()[i]; ++j) {
+                       *p++ = j % 256;
+               }
+       }
 -      shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
++      shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, dcp::Size (100, 200), true));
+       p = sub_image->data()[0];
+       for (int y = 0; y < 200; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < 100; ++x) {
+                       *q++ = y % 256;
+                       *q++ = x % 256;
+                       *q++ = (x + y) % 256;
+                       *q++ = 1;
+               }
+               p += sub_image->stride()[0];
+       }
+       shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test_yuv.log"));
  
        shared_ptr<PlayerVideoFrame> pvf (
                new PlayerVideoFrame (
  
  using std::cout;
  
 -/* Basic test of identifier() for ColourConversion (i.e. a hash of the numbers) */
  BOOST_AUTO_TEST_CASE (colour_conversion_test)
  {
 -      ColourConversion A (2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6);
 -      ColourConversion B (2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6);
 +      ColourConversion A (2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6);
 +      ColourConversion B (2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6);
  
-       BOOST_CHECK_EQUAL (A.identifier(), "246ff9b7dc32c0488948a32a713924b3");
-       BOOST_CHECK_EQUAL (B.identifier(), "a8d1da30f96a121d8db06a03409758b3");
+       BOOST_CHECK_EQUAL (A.identifier(), "1e720d2d99add654d7816f3b72da815e");
+       BOOST_CHECK_EQUAL (B.identifier(), "18751a247b22682b725bf9c4caf71522");
  }
@@@ -37,10 -33,14 +37,10 @@@ using boost::shared_ptr
  
  BOOST_AUTO_TEST_CASE (film_metadata_test)
  {
 -      string const test_film = "build/test/film_metadata_test";
 -      
 -      if (boost::filesystem::exists (test_film)) {
 -              boost::filesystem::remove_all (test_film);
 -      }
 +      shared_ptr<Film> f = new_test_film ("film_metadata_test");
 +      boost::filesystem::path dir = test_film_dir ("film_metadata_test");
  
-       f->_dci_date = boost::gregorian::from_undelimited_string ("20130211");
 -      shared_ptr<Film> f (new Film (test_film));
+       f->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211");
        BOOST_CHECK (f->container() == 0);
        BOOST_CHECK (f->dcp_content_type() == 0);
  
@@@ -27,7 -22,7 +27,8 @@@
  #include "lib/config.h"
  #include "lib/ffmpeg_content.h"
  #include "lib/playlist.h"
 +#include "lib/ffmpeg_audio_stream.h"
+ #include "lib/frame_rate_change.h"
  #include "test.h"
  
  using boost::shared_ptr;
Simple merge
Simple merge
Simple merge
diff --cc test/test.cc
@@@ -67,10 -59,9 +67,10 @@@ struct TestConfi
  
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
-               Config::instance()->set_default_dci_metadata (DCIMetadata ());
+               Config::instance()->set_default_isdcf_metadata (ISDCFMetadata ());
                Config::instance()->set_default_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
 +              Config::instance()->set_default_audio_delay (0);
  
                ServerFinder::instance()->disable ();
  
diff --cc test/wscript
@@@ -34,12 -31,12 +34,13 @@@ def build(bld)
                   film_metadata_test.cc
                   frame_rate_test.cc
                   image_test.cc
+                  isdcf_name_test.cc
                   job_test.cc
                   make_black_test.cc
 +                 player_test.cc
                   pixel_formats_test.cc
 -                 play_test.cc
                   ratio_test.cc
 +                 repeat_frame_test.cc
                   recover_test.cc
                   resampler_test.cc
                   scaling_test.cc