Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 1 Apr 2014 21:51:54 +0000 (22:51 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 1 Apr 2014 21:51:54 +0000 (22:51 +0100)
29 files changed:
1  2 
ChangeLog
src/lib/config.cc
src/lib/config.h
src/lib/dcp_video_frame.cc
src/lib/encoder.cc
src/lib/film.cc
src/lib/playlist.cc
src/lib/sndfile_content.cc
src/lib/types.h
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/lib/writer.cc
src/lib/writer.h
src/lib/wscript
src/wx/config_dialog.cc
src/wx/film_editor.cc
src/wx/properties_dialog.cc
src/wx/screen_dialog.cc
src/wx/screen_dialog.h
src/wx/timecode.cc
src/wx/timeline.cc
src/wx/timing_panel.cc
src/wx/video_panel.cc
src/wx/wscript
src/wx/wx_util.cc
wscript

diff --cc ChangeLog
+++ b/ChangeLog
@@@ -1,7 -1,56 +1,61 @@@
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
 +
+ 2014-04-01  Carl Hetherington  <cth@carlh.net>
+       * Basic support for separate left/right-eye files or directories
+       for 3D.
+ 2014-03-30  Carl Hetherington  <cth@carlh.net>
+       * Version 1.66.9 released.
+ 2014-03-30  Carl Hetherington  <cth@carlh.net>
+       * Version 1.66.8 released.
+       * nl_NL translation from Theo Kooijmans.
+ 2014-03-27  Carl Hetherington  <cth@carlh.net>
+       * Auto-save film metadata before starting DCP encode.
+ 2014-03-25  Carl Hetherington  <cth@carlh.net>
+       * Add support for downloading Doremi server certificates.
+ 2014-03-24  Carl Hetherington  <cth@carlh.net>
+       * Version 1.66.7 released.
+ 2014-03-24  Carl Hetherington  <cth@carlh.net>
+       * Fix error on creating DCPs without audio.
+ 2014-03-23  Carl Hetherington  <cth@carlh.net>
+       * Version 1.66.6 released.
+ 2014-03-23  Carl Hetherington  <cth@carlh.net>
+       * Attempt to fix format string specifier error on Windows.
+       * Version 1.66.5 released.
+ 2014-03-22  Carl Hetherington  <cth@carlh.net>
+       * Version 1.66.4 released.
+ 2014-03-22  Carl Hetherington  <cth@carlh.net>
+       * Allow specification of the video frame rate that a sound file
+       was prepared for.
+       * Another attempt to fix colour conversion dialog strange behaviour
+       on OS X.
++>>>>>>> master
  2014-03-18  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.66.3 released.
Simple merge
@@@ -262,12 -268,12 +268,12 @@@ public
  
        void set_default_dcp_content_type (DCPContentType const * t) {
                _default_dcp_content_type = t;
-               write ();
+               changed ();
        }
  
 -      void set_dcp_metadata (libdcp::XMLMetadata m) {
 +      void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
-               write ();
+               changed ();
        }
  
        void set_default_j2k_bandwidth (int b) {
Simple merge
Simple merge
diff --cc src/lib/film.cc
Simple merge
@@@ -81,14 -81,21 +81,21 @@@ Playlist::maybe_sequence_video (
        _sequencing_video = true;
        
        ContentList cl = _content;
-       DCPTime next;
 -      Time next_left = 0;
 -      Time next_right = 0;
++      DCPTime next_left;
++      DCPTime next_right;
        for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
-               if (!dynamic_pointer_cast<VideoContent> (*i)) {
+               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
+               if (!vc) {
                        continue;
                }
 -      
 +              
-               (*i)->set_position (next);
-               next = (*i)->end() + DCPTime::delta ();
+               if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
+                       vc->set_position (next_right);
 -                      next_right = vc->end() + 1;
++                      next_right = vc->end() + DCPTime::delta ();
+               } else {
+                       vc->set_position (next_left);
 -                      next_left = vc->end() + 1;
++                      next_left = vc->end() + DCPTime::delta ();
+               }
        }
  
        /* This won't change order, so it does not need a sort */
@@@ -1,5 -1,5 +1,5 @@@
  /*
--    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
++    Copyright (C) 2013-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
@@@ -165,3 -177,18 +165,4 @@@ SndfileContent::set_audio_mapping (Audi
  
        signal_changed (AudioContentProperty::AUDIO_MAPPING);
  }
 -float
 -SndfileContent::video_frame_rate () const
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_mutex);
 -              if (_video_frame_rate) {
 -                      return _video_frame_rate.get ();
 -              }
 -      }
 -
 -      shared_ptr<const Film> film = _film.lock ();
 -      assert (film);
 -      return film->video_frame_rate ();
 -}
diff --cc src/lib/types.h
Simple merge
diff --cc src/lib/util.cc
@@@ -1,5 -1,5 +1,5 @@@
  /*
--    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
++    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
      Copyright (C) 2000-2007 Paul Davis
  
      This program is free software; you can redistribute it and/or modify
@@@ -940,20 -1031,37 +940,55 @@@ divide_with_round (int64_t a, int64_t b
        }
  }
  
 +/** Return a user-readable string summarising the versions of our dependencies */
 +string
 +dependency_version_summary ()
 +{
 +      stringstream s;
 +      s << N_("libopenjpeg ") << opj_version () << N_(", ")
 +        << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
 +        << 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_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
 +        << MagickVersion << N_(", ")
 +        << N_("libssh ") << ssh_version (0) << N_(", ")
 +        << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
 +
 +      return s.str ();
 +}
++
+ ScopedTemporary::ScopedTemporary ()
+       : _open (0)
+ {
+       _file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
+ }
+ ScopedTemporary::~ScopedTemporary ()
+ {
+       close ();       
+       boost::system::error_code ec;
+       boost::filesystem::remove (_file, ec);
+ }
+ char const *
+ ScopedTemporary::c_str () const
+ {
+       return _file.string().c_str ();
+ }
+ FILE*
+ ScopedTemporary::open (char const * params)
+ {
+       _open = fopen (c_str(), params);
+       return _open;
+ }
+ void
+ ScopedTemporary::close ()
+ {
+       if (_open) {
+               fclose (_open);
+               _open = 0;
+       }
+ }
diff --cc src/lib/util.h
Simple merge
@@@ -333,11 -330,13 +333,13 @@@ VideoContent::video_size_after_3d_spli
        switch (video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
        case VIDEO_FRAME_TYPE_3D_ALTERNATE:
+       case VIDEO_FRAME_TYPE_3D_LEFT:
+       case VIDEO_FRAME_TYPE_3D_RIGHT:
                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);
Simple merge
@@@ -114,18 -53,18 +114,24 @@@ VideoDecoder::video (shared_ptr<const I
        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);
 +              _decoded_video.push_back (ContentVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, frame));
 +              _decoded_video.push_back (ContentVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, frame));
                break;
        }
 -              Video (image, EYES_LEFT, same, frame);
+       case VIDEO_FRAME_TYPE_3D_LEFT:
 -              Video (image, EYES_RIGHT, same, frame);
++              _decoded_video.push_back (ContentVideo (image, EYES_LEFT, frame));
+               break;
+       case VIDEO_FRAME_TYPE_3D_RIGHT:
++              _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, frame));
+               break;
 +      default:
 +              assert (false);
        }
 -      
 -      _video_position = frame + 1;
 +}
 +
 +void
 +VideoDecoder::seek (ContentTime, bool)
 +{
 +      _decoded_video.clear ();
  }
  
@@@ -88,33 -83,37 +88,35 @@@ Writer::Writer (shared_ptr<const Film> 
        */
  
        if (_film->three_d ()) {
 -              _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
 +              _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
        } else {
 -              _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
 +              _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
        }
  
 -      _picture_asset->set_edit_rate (_film->video_frame_rate ());
 -      _picture_asset->set_size (_film->frame_size ());
 -      _picture_asset->set_interop (_film->interop ());
 +      _picture_mxf->set_size (_film->frame_size ());
  
        if (_film->encrypted ()) {
 -              _picture_asset->set_key (_film->key ());
 +              _picture_mxf->set_key (_film->key ());
        }
        
 -      _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
 +      _picture_mxf_writer = _picture_mxf->start_write (
 +              _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
 +              _film->interop() ? dcp::INTEROP : dcp::SMPTE,
 +              _first_nonexistant_frame > 0
 +              );
  
-       _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
+       if (_film->audio_channels ()) {
 -              _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
 -              _sound_asset->set_edit_rate (_film->video_frame_rate ());
 -              _sound_asset->set_channels (_film->audio_channels ());
 -              _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
 -              _sound_asset->set_interop (_film->interop ());
++              _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
  
-       if (_film->encrypted ()) {
-               _sound_mxf->set_key (_film->key ());
-       }
+               if (_film->encrypted ()) {
 -                      _sound_asset->set_key (_film->key ());
++                      _sound_mxf->set_key (_film->key ());
+               }
 -              
 -              /* Write the sound asset into the film directory so that we leave the creation
 +      
-       /* Write the sound MXF into the film directory so that we leave the creation
-          of the DCP directory until the last minute.
-       */
-       _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
++              /* Write the sound MXF into the film directory so that we leave the creation
+                  of the DCP directory until the last minute.
+               */
 -              _sound_asset_writer = _sound_asset->start_write ();
++              _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
+       }
  
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
  
@@@ -191,7 -190,9 +193,9 @@@ Writer::fake_write (int frame, Eyes eye
  void
  Writer::write (shared_ptr<const AudioBuffers> audio)
  {
-       _sound_mxf_writer->write (audio->data(), audio->frames());
 -      if (_sound_asset) {
 -              _sound_asset_writer->write (audio->data(), audio->frames());
++      if (_sound_mxf_writer) {
++              _sound_mxf_writer->write (audio->data(), audio->frames());
+       }
  }
  
  /** This must be called from Writer::thread() with an appropriate lock held */
@@@ -367,9 -380,15 +371,11 @@@ Writer::finish (
        
        terminate_thread (true);
  
 -      _picture_asset_writer->finalize ();
 -      if (_sound_asset_writer) {
 -              _sound_asset_writer->finalize ();
 +      _picture_mxf_writer->finalize ();
-       _sound_mxf_writer->finalize ();
++      if (_sound_mxf_writer) {
++              _sound_mxf_writer->finalize ();
+       }
        
 -      int const frames = _last_written_frame + 1;
 -
 -      _picture_asset->set_duration (frames);
 -
        /* Hard-link the video MXF into the DCP */
        boost::filesystem::path video_from;
        video_from /= _film->internal_video_mxf_dir();
                _film->log()->log ("Hard-link failed; fell back to copying");
        }
  
 -      /* And update the asset */
 -
 -      _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
 -      _picture_asset->set_file_name (_film->video_mxf_filename ());
 -
        /* Move the audio MXF into the DCP */
  
-       boost::filesystem::path audio_to;
-       audio_to /= _film->dir (_film->dcp_name ());
-       audio_to /= _film->audio_mxf_filename ();
-       
-       boost::filesystem::rename (_film->file (_film->audio_mxf_filename ()), audio_to, ec);
-       if (ec) {
-               throw FileError (
-                       String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
-                       );
 -      if (_sound_asset) {
++      if (_sound_mxf) {
+               boost::filesystem::path audio_to;
+               audio_to /= _film->dir (_film->dcp_name ());
+               audio_to /= _film->audio_mxf_filename ();
+               
+               boost::filesystem::rename (_film->file (_film->audio_mxf_filename ()), audio_to, ec);
+               if (ec) {
+                       throw FileError (
+                               String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
+                               );
+               }
 -              
 -              _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
 -              _sound_asset->set_duration (frames);
        }
 -      
 -      libdcp::DCP dcp (_film->dir (_film->dcp_name()));
  
 -      shared_ptr<libdcp::CPL> cpl (
 -              new libdcp::CPL (
 -                      _film->dir (_film->dcp_name()),
 +      dcp::DCP dcp (_film->dir (_film->dcp_name()));
 +
 +      shared_ptr<dcp::CPL> cpl (
 +              new dcp::CPL (
                        _film->dcp_name(),
 -                      _film->dcp_content_type()->libdcp_kind (),
 -                      frames,
 -                      _film->video_frame_rate ()
 +                      _film->dcp_content_type()->libdcp_kind ()
                        )
                );
        
        assert (job);
  
        job->sub (_("Computing image digest"));
 -      _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
 +      _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
  
-       job->sub (_("Computing audio digest"));
-       _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
 -      if (_sound_asset) {
++      if (_sound_mxf) {
+               job->sub (_("Computing audio digest"));
 -              _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
++              _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
+       }
  
 -      libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
 +      dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
        meta.set_issue_date_now ();
 -      dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
 +      dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ());
  
        _film->log()->log (
 -              String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk)
 +              String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk)
                );
  }
  
@@@ -55,8 -51,8 +55,6 @@@ public
                    state but we use the data that is already on disk.
                */
                FAKE,
--              /** this is a repeat of the last frame to be written */
--              REPEAT
        } type;
  
        /** encoded data for FULL */
diff --cc src/lib/wscript
@@@ -32,7 -30,7 +32,8 @@@ sources = ""
            ffmpeg_examiner.cc
            film.cc
            filter.cc
 +          frame_rate_change.cc
+           internet.cc
            image.cc
            image_content.cc
            image_decoder.cc
Simple merge
Simple merge
@@@ -33,25 -33,20 +33,19 @@@ using boost::shared_ptr
  using boost::lexical_cast;
  
  PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
-       : wxDialog (parent, wxID_ANY, _("Film Properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
+       : TableDialog (parent, _("Film Properties"), 2, false)
        , _film (film)
  {
-       _table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       add (_("Frames"), true);
+       _frames = add (new wxStaticText (this, wxID_ANY, wxT ("")));
  
-       add_label_to_sizer (_table, this, _("Frames"), true);
-       _frames = new wxStaticText (this, wxID_ANY, wxT (""));
-       _table->Add (_frames, 1, wxALIGN_CENTER_VERTICAL);
+       add (_("Disk space required"), true);
+       _disk = add (new wxStaticText (this, wxID_ANY, wxT ("")));
  
-       add_label_to_sizer (_table, this, _("Disk space required"), true);
-       _disk = new wxStaticText (this, wxID_ANY, wxT (""));
-       _table->Add (_disk, 1, wxALIGN_CENTER_VERTICAL);
-       add_label_to_sizer (_table, this, _("Frames already encoded"), true);
-       _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
+       add (_("Frames already encoded"), true);
+       _encoded = add (new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)));
        _encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this));
-       _table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
 -      
 -      _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
 +      _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().frames (_film->video_frame_rate ()))));
        double const disk = double (_film->required_disk_space()) / 1073741824.0f;
        stringstream s;
        s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
  
  #include <wx/filepicker.h>
  #include <wx/validate.h>
 -#include <libdcp/exceptions.h>
 +#include <dcp/exceptions.h>
  #include "lib/compose.hpp"
+ #include "lib/util.h"
  #include "screen_dialog.h"
  #include "wx_util.h"
+ #include "doremi_certificate_dialog.h"
+ #include "dolby_certificate_dialog.h"
  
  using std::string;
  using std::cout;
  using boost::shared_ptr;
  
 -ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<libdcp::Certificate> certificate)
 +ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<dcp::Certificate> certificate)
-       : wxDialog (parent, wxID_ANY, std_to_wx (title))
+       : TableDialog (parent, std_to_wx (title), 2, true)
        , _certificate (certificate)
  {
-       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
-       table->AddGrowableCol (1, 1);
+       add ("Name", true);
+       _name = add (new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (320, -1)));
  
-       add_label_to_sizer (table, this, "Name", true);
-       _name = new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (320, -1));
-       table->Add (_name, 1, wxEXPAND);
+       add ("Server manufacturer", true);
+       _manufacturer = add (new wxChoice (this, wxID_ANY));
  
-       add_label_to_sizer (table, this, "Certificate", true);
-       _certificate_load = new wxButton (this, wxID_ANY, wxT ("Load from file..."));
-       table->Add (_certificate_load, 1, wxEXPAND);
+       add (_("Certificate"), true);
+       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+       _load_certificate = new wxButton (this, wxID_ANY, _("Load from file..."));
+       _download_certificate = new wxButton (this, wxID_ANY, _("Download"));
+       s->Add (_load_certificate, 1, wxEXPAND);
+       s->Add (_download_certificate, 1, wxEXPAND);
+       add (s);
  
-       table->AddSpacer (0);
+       add_spacer ();
        _certificate_text = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxSize (320, 256), wxTE_MULTILINE | wxTE_READONLY);
        if (certificate) {
                _certificate_text->SetValue (certificate->certificate ());
@@@ -83,19 -86,23 +86,23 @@@ ScreenDialog::certificate () cons
  }
  
  void
- ScreenDialog::load_certificate ()
+ ScreenDialog::load_certificate (boost::filesystem::path file)
  {
-       wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
+       try {
 -              _certificate.reset (new libdcp::Certificate (file));
++              _certificate.reset (new dcp::Certificate (file));
+               _certificate_text->SetValue (_certificate->certificate ());
 -      } catch (libdcp::MiscError& e) {
++      } catch (dcp::MiscError& e) {
+               error_dialog (this, String::compose ("Could not read certificate file (%1)", e.what()));
+       }
+ }
  
+ void
+ ScreenDialog::select_certificate ()
+ {
+       wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
        if (d->ShowModal () == wxID_OK) {
-               try {
-                       _certificate.reset (new dcp::Certificate (boost::filesystem::path (wx_to_std (d->GetPath ()))));
-                       _certificate_text->SetValue (_certificate->certificate ());
-               } catch (dcp::MiscError& e) {
-                       error_dialog (this, String::compose ("Could not read certificate file (%1)", e.what()));
-               }
+               load_certificate (boost::filesystem::path (wx_to_std (d->GetPath ())));
        }
-       
        d->Destroy ();
  
        setup_sensitivity ();
@@@ -105,5 -128,10 +128,10 @@@ voi
  ScreenDialog::setup_sensitivity ()
  {
        wxButton* ok = dynamic_cast<wxButton*> (FindWindowById (wxID_OK, this));
 -      ok->Enable (_certificate);
 +      ok->Enable (_certificate.get ());
+       _download_certificate->Enable (
+               _manufacturer->GetStringSelection() == _("Doremi") ||
+               _manufacturer->GetStringSelection() == _("Dolby")
+               );
  }
  
  #include <wx/wx.h>
  #include <boost/shared_ptr.hpp>
 -#include <libdcp/certificates.h>
 +#include <dcp/certificates.h>
+ #include "table_dialog.h"
  
- class ScreenDialog : public wxDialog
+ class Progress;
+ class ScreenDialog : public TableDialog
  {
  public:
 -      ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<libdcp::Certificate> c = boost::shared_ptr<libdcp::Certificate> ());
 +      ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<dcp::Certificate> c = boost::shared_ptr<dcp::Certificate> ());
  
        std::string name () const;
 -      boost::shared_ptr<libdcp::Certificate> certificate () const;
 +      boost::shared_ptr<dcp::Certificate> certificate () const;
        
  private:
-       void load_certificate ();
+       void select_certificate ();
+       void load_certificate (boost::filesystem::path);
+       void download_certificate ();
        void setup_sensitivity ();
        
        wxTextCtrl* _name;
-       wxButton* _certificate_load;
+       wxChoice* _manufacturer;
+       wxButton* _load_certificate;
+       wxButton* _download_certificate;
        wxTextCtrl* _certificate_text;
  
 -      boost::shared_ptr<libdcp::Certificate> _certificate;
 +      boost::shared_ptr<dcp::Certificate> _certificate;
  };
@@@ -102,21 -102,21 +102,21 @@@ Timecode::set (DCPTime t, int fps
        checked_set (_seconds, lexical_cast<string> (s));
        checked_set (_frames, lexical_cast<string> (f));
  
-       _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02ld", h, m, s, f));
+       _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02" wxLongLongFmtSpec "d", 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;
  }
@@@ -99,8 -100,8 +100,8 @@@ public
                
                return dcpomatic::Rect<int> (
                        time_x (content->position ()) - 8,
-                       y_pos (_track) - 8,
+                       y_pos (_track.get()) - 8,
 -                      content->length_after_trim () * _timeline.pixels_per_time_unit() + 16,
 +                      content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16,
                        _timeline.track_height() + 16
                        );
        }
@@@ -169,8 -172,8 +172,8 @@@ private
                wxDouble name_leading;
                gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
                
-               gc->Clip (wxRegion (time_x (position), y_pos (_track), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height()));
-               gc->DrawText (name, time_x (position) + 12, y_pos (_track + 1) - name_height - 4);
 -              gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
++              gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height()));
+               gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4);
                gc->ResetClip ();
        }
  
@@@ -431,55 -411,30 +434,30 @@@ Timeline::playlist_changed (
  void
  Timeline::assign_tracks ()
  {
-       list<shared_ptr<VideoContentView> > video;
-       list<shared_ptr<AudioContentView> > audio;
-       list<shared_ptr<SubtitleContentView> > subtitle;
        for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
-               shared_ptr<VideoContentView> v = dynamic_pointer_cast<VideoContentView> (*i);
-               if (v) {
-                       video.push_back (v);
-               }
-               
-               shared_ptr<AudioContentView> a = dynamic_pointer_cast<AudioContentView> (*i);
-               if (a) {
-                       audio.push_back (a);
-               }
-               shared_ptr<SubtitleContentView> s = dynamic_pointer_cast<SubtitleContentView> (*i);
-               if (s) {
-                       subtitle.push_back (s);
-               }
-       }
-       _tracks = 0;
-       if (!video.empty ()) {
-               for (list<shared_ptr<VideoContentView> >::iterator i = video.begin(); i != video.end(); ++i) {
-                       (*i)->set_track (_tracks);
+               shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
+               if (!cv) {
+                       continue;
                }
-               ++_tracks;
-       }
--      
-       if (!subtitle.empty ()) {
-               for (list<shared_ptr<SubtitleContentView> >::iterator i = subtitle.begin(); i != subtitle.end(); ++i) {
-                       (*i)->set_track (_tracks);
-               }
-               ++_tracks;
-       }
 +
-       int const audio_start = _tracks;
+               shared_ptr<Content> content = cv->content();
  
-       for (list<shared_ptr<AudioContentView> >::iterator i = audio.begin(); i != audio.end(); ++i) {
-               shared_ptr<Content> acv_content = (*i)->content();
-               int t = audio_start;
-               while (1) {
-                       list<shared_ptr<AudioContentView> >::iterator j = audio.begin ();
-                       while (j != audio.end()) {
-                               if ((*j)->track() == t) {
+               int t = 0;
 -              while (1) {
++              while (true) {
+                       ViewList::iterator j = _views.begin();
+                       while (j != _views.end()) {
+                               shared_ptr<ContentView> test = dynamic_pointer_cast<ContentView> (*j);
+                               if (!test) {
+                                       ++j;
+                                       continue;
+                               }
+                               
+                               shared_ptr<Content> test_content = test->content();
+                                       
+                               if (test && test->track() && test->track().get() == t) {
                                        bool const no_overlap =
-                                               (acv_content->position() < (*j)->content()->position() && acv_content->end() < (*j)->content()->position()) ||
-                                               (acv_content->position() > (*j)->content()->end()      && acv_content->end() > (*j)->content()->end());
+                                               (content->position() < test_content->position() && content->end() < test_content->position()) ||
+                                               (content->position() > test_content->end()      && content->end() > test_content->end());
                                        
                                        if (!no_overlap) {
                                                /* we have an overlap on track `t' */
@@@ -83,37 -86,38 +86,37 @@@ TimingPanel::film_content_changed (int 
        
        if (property == ContentProperty::POSITION) {
                if (content) {
-                       _position->set (content->position (), _editor->film()->video_frame_rate ());
+                       _position->set (content->position (), film_video_frame_rate);
                } else {
 -                      _position->set (0, 24);
 +                      _position->set (DCPTime () , 24);
                }
        } else if (
                property == ContentProperty::LENGTH ||
                property == VideoContentProperty::VIDEO_FRAME_RATE ||
 -              property == VideoContentProperty::VIDEO_FRAME_TYPE ||
 -              property == SndfileContentProperty::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 ());
+                       _full_length->set (content->full_length (), film_video_frame_rate);
+                       _play_length->set (content->length_after_trim (), 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 ());
+                       _trim_start->set (content->trim_start (), film_video_frame_rate);
+                       _play_length->set (content->length_after_trim (), 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 ());
+                       _trim_end->set (content->trim_end (), film_video_frame_rate);
+                       _play_length->set (content->length_after_trim (), 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);
                }
        }
  
@@@ -201,8 -208,12 +205,8 @@@ TimingPanel::set_video_frame_rate (
                shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
                if (ic) {
                        ic->set_video_frame_rate (lexical_cast<float> (wx_to_std (_video_frame_rate->GetValue ())));
-                       _set_video_frame_rate->Enable (false);
                }
 -              shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (c.front ());
 -              if (sc) {
 -                      sc->set_video_frame_rate (lexical_cast<float> (wx_to_std (_video_frame_rate->GetValue ())));
 -              }
+               _set_video_frame_rate->Enable (false);
        }
  }
  
Simple merge
diff --cc src/wx/wscript
@@@ -35,7 -38,7 +38,8 @@@ sources = ""
            server_dialog.cc
            servers_list_dialog.cc
            subtitle_panel.cc
 +          subtitle_view.cc
+           table_dialog.cc
            timecode.cc
            timeline.cc
            timeline_dialog.cc
Simple merge
diff --cc wscript
Simple merge