Merge master.
authorCarl Hetherington <cth@carlh.net>
Mon, 29 Sep 2014 12:49:54 +0000 (13:49 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 29 Sep 2014 12:49:54 +0000 (13:49 +0100)
1  2 
ChangeLog
src/lib/writer.cc
src/tools/dcpomatic.cc
src/wx/content_panel.h
src/wx/film_editor.h

diff --combined ChangeLog
index 57ec197eab5bad7dbcb268f5088a4d2987ad5489,f676d6b99dea3a4e6163b193d8ffdca43b1c559e..a8927a5471b975ca4ef4b64761403e5b42e5fa32
+++ b/ChangeLog
@@@ -1,11 -1,11 +1,19 @@@
 +2014-09-22  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.11 released.
 +
 +2014-09-18  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.10 released.
 +
+ 2014-09-28  Carl Hetherington  <cth@carlh.net>
+       * Version 1.73.8 released.
+ 2014-09-28  Carl Hetherington  <cth@carlh.net>
+       * Add a few key shortcuts.
  2014-09-16  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.73.7 released.
  
  2014-09-12  Carl Hetherington  <cth@carlh.net>
  
 +      * Version 2.0.9 released.
 +
 +2014-09-12  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add "re-examine" option to content context menu (#339).
 +
 +2014-09-11  Carl Hetherington  <cth@carlh.net>
 +
 +      * Restore encoding optimisations for still-image sources.
 +
 +      * Add option to re-make signing chain with specified organisation,
 +      common names etc. (#354)
 +
        * Allow separate X and Y scale for subtitles (#337).
  
  2014-09-10  Carl Hetherington  <cth@carlh.net>
  
        * Fix hidden advanced preferences button in some locales.
  
 -2014-09-08  Carl Hetherington  <cth@carlh.net>
 +      * Version 2.0.8 released.
 +
 +2014-09-10  Carl Hetherington  <cth@carlh.net>
 +
 +      * Fix loading of 1.x films.
 +
 +      * Fix crash on audio analysis in some cases.
 +
 +2014-09-09  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.7 released.
 +
 +2014-09-09  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.6 released.
 +
 +2014-09-09  Carl Hetherington  <cth@carlh.net>
  
 -      * Version 1.73.4 released.
 +      * Fix missing OS X dependencies.
 +
 +      * Use a different directory for DCP-o-matic 2
 +      configuration (not the same as 1.x).
  
  2014-09-08  Carl Hetherington  <cth@carlh.net>
  
 -      * Fix failure to load Targa files.
 +      * Version 2.0.5 released.
  
 -2014-09-07  Carl Hetherington  <cth@carlh.net>
 +      * Fix hidden advanced preferences button in some locales.
  
 -      * Version 1.73.3 released.
 +2014-09-08  Carl Hetherington  <cth@carlh.net>
 +
 +      * Fix failure to load Targa files.
  
  2014-09-07  Carl Hetherington  <cth@carlh.net>
  
  
        * Fix a few bad fuzzy translations from the preferences dialog.
  
 -2014-09-03  Carl Hetherington  <cth@carlh.net>
 -
 -      * Version 1.73.2 released.
 -
  2014-09-03  Carl Hetherington  <cth@carlh.net>
  
        * Fix server certificate downloads on OS X (#376).
  
  2014-08-29  Carl Hetherington  <cth@carlh.net>
  
 +      * Version 2.0.4 released.
 +
 +2014-08-24  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.3 released.
 +
 +2014-08-24  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.2 released.
 +
 +2014-08-06  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.1 released.
 +
 +2014-07-15  Carl Hetherington  <cth@carlh.net>
 +
 +      * A variety of changes were made on the 2.0 branch
 +      but not documented in the ChangeLog.  Most sigificantly:
 +
 +      - DCP import
 +      - Creation of DCPs with proper XML subtitles
 +      - Import of .srt and .xml subtitles
 +      - Audio processing framework (with some basic processors).
 +
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
        * Some improvements to the manual.
  
  2014-08-26  Carl Hetherington  <cth@carlh.net>
        * Attempt to fix random crashes on OS X (especially during encodes)
        thought to be caused by multiple threads using (different) stringstreams
        at the same time; see src/lib/safe_stringstream.
 +>>>>>>> origin/master
  
  2014-08-09  Carl Hetherington  <cth@carlh.net>
  
  2014-07-10  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.72.2 released.
 +>>>>>>> origin/master
  
  2014-07-10  Carl Hetherington  <cth@carlh.net>
  
diff --combined src/lib/writer.cc
index a023d5cd2e1cc28992573e180bc2ccd9cc540cbc,5af1aea1e1d8f15ea25c27017f60e9817cb55c9e..6262525c85d3a5777ead7a9dda416d7752622d52
  
  #include <fstream>
  #include <cerrno>
 -#include <libdcp/mono_picture_asset.h>
 -#include <libdcp/stereo_picture_asset.h>
 -#include <libdcp/sound_asset.h>
 -#include <libdcp/reel.h>
 -#include <libdcp/dcp.h>
 -#include <libdcp/cpl.h>
 +#include <dcp/mono_picture_mxf.h>
 +#include <dcp/stereo_picture_mxf.h>
 +#include <dcp/sound_mxf.h>
 +#include <dcp/sound_mxf_writer.h>
 +#include <dcp/reel.h>
 +#include <dcp/reel_mono_picture_asset.h>
 +#include <dcp/reel_stereo_picture_asset.h>
 +#include <dcp/reel_sound_asset.h>
 +#include <dcp/reel_subtitle_asset.h>
 +#include <dcp/dcp.h>
 +#include <dcp/cpl.h>
 +#include <dcp/signer.h>
  #include "writer.h"
  #include "compose.hpp"
  #include "film.h"
  #include "ratio.h"
  #include "log.h"
 -#include "dcp_video_frame.h"
 +#include "dcp_video.h"
  #include "dcp_content_type.h"
 -#include "player.h"
  #include "audio_mapping.h"
  #include "config.h"
  #include "job.h"
  #include "cross.h"
 +#include "audio_buffers.h"
  #include "md5_digester.h"
 +#include "encoded_data.h"
  #include "version.h"
  
  #include "i18n.h"
@@@ -52,6 -45,7 +52,7 @@@
  #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
  #define LOG_TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TYPE_TIMING);
  #define LOG_WARNING_NC(...) _film->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+ #define LOG_ERROR(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
  
  /* OS X strikes again */
  #undef set_key
@@@ -63,7 -57,6 +64,7 @@@ using std::list
  using std::cout;
  using boost::shared_ptr;
  using boost::weak_ptr;
 +using boost::dynamic_pointer_cast;
  
  int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
  
@@@ -78,6 -71,7 +79,6 @@@ Writer::Writer (shared_ptr<const Film> 
        , _last_written_eyes (EYES_RIGHT)
        , _full_written (0)
        , _fake_written (0)
 -      , _repeat_written (0)
        , _pushed_to_disk (0)
  {
        /* Remove any old DCP */
        */
  
        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
 +              );
  
        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_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_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);
 +      }
 +
 +      /* Check that the signer is OK if we need one */
 +      if (_film->is_signed() && !Config::instance()->signer()->valid ()) {
 +              throw InvalidSignerError ();
        }
  
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
@@@ -184,7 -175,7 +185,7 @@@ Writer::fake_write (int frame, Eyes eye
        }
        
        FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r");
 -      libdcp::FrameInfo info (ifi);
 +      dcp::FrameInfo info (ifi);
        fclose (ifi);
        
        QueueItem qi;
  void
  Writer::write (shared_ptr<const AudioBuffers> audio)
  {
 -      if (_sound_asset) {
 -              _sound_asset_writer->write (audio->data(), audio->frames());
 +      if (_sound_mxf_writer) {
 +              _sound_mxf_writer->write (audio->data(), audio->frames());
        }
  }
  
@@@ -286,7 -277,7 +287,7 @@@ tr
                                        qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
                                }
  
 -                              libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
 +                              dcp::FrameInfo fin = _picture_mxf_writer->write (qi.encoded->data(), qi.encoded->size());
                                qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
                                _last_written[qi.eyes] = qi.encoded;
                                ++_full_written;
                        }
                        case QueueItem::FAKE:
                                LOG_GENERAL (N_("Writer FAKE-writes %1 to MXF"), qi.frame);
 -                              _picture_asset_writer->fake_write (qi.size);
 +                              _picture_mxf_writer->fake_write (qi.size);
                                _last_written[qi.eyes].reset ();
                                ++_fake_written;
                                break;
 -                      case QueueItem::REPEAT:
 -                      {
 -                              LOG_GENERAL (N_("Writer REPEAT-writes %1 to MXF"), qi.frame);
 -                              libdcp::FrameInfo fin = _picture_asset_writer->write (
 -                                      _last_written[qi.eyes]->data(),
 -                                      _last_written[qi.eyes]->size()
 -                                      );
 -                              
 -                              _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
 -                              ++_repeat_written;
 -                              break;
 -                      }
                        }
                        lock.lock ();
  
                        _last_written_frame = qi.frame;
                        _last_written_eyes = qi.eyes;
                        
 -                      if (_film->length()) {
 -                              shared_ptr<Job> job = _job.lock ();
 -                              assert (job);
 -                              int total = _film->time_to_video_frames (_film->length ());
 -                              if (_film->three_d ()) {
 -                                      /* _full_written and so on are incremented for each eye, so we need to double the total
 -                                         frames to get the correct progress.
 -                                      */
 -                                      total *= 2;
 -                              }
 -                              job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
 +                      shared_ptr<Job> job = _job.lock ();
 +                      assert (job);
 +                      int64_t total = _film->length().frames (_film->video_frame_rate ());
 +                      if (_film->three_d ()) {
 +                              /* _full_written and so on are incremented for each eye, so we need to double the total
 +                                 frames to get the correct progress.
 +                              */
 +                              total *= 2;
 +                      }
 +                      if (total) {
 +                              job->set_progress (float (_full_written + _fake_written) / total);
                        }
                }
  
@@@ -389,11 -392,15 +390,11 @@@ Writer::finish (
        
        terminate_thread (true);
  
 -      _picture_asset_writer->finalize ();
 -      if (_sound_asset_writer) {
 -              _sound_asset_writer->finalize ();
 +      _picture_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();
        boost::system::error_code ec;
        boost::filesystem::create_hard_link (video_from, video_to, ec);
        if (ec) {
-               /* hard link failed; copy instead */
-               boost::filesystem::copy_file (video_from, video_to);
-               LOG_WARNING_NC ("Hard-link failed; fell back to copying");
+               LOG_WARNING_NC ("Hard-link failed; copying instead");
+               boost::filesystem::copy_file (video_from, video_to, ec);
+               if (ec) {
+                       LOG_ERROR ("Failed to copy video file from %1 to %2 (%3)", video_from.string(), video_to.string(), ec.message ());
+                       throw FileError (ec.message(), video_from);
+               }
        }
  
 -      /* And update the asset */
 -
 -      _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
 -      _picture_asset->set_file_name (_film->video_mxf_filename ());
 +      _picture_mxf->set_file (video_to);
  
        /* Move the audio MXF into the DCP */
  
 -      if (_sound_asset) {
 +      if (_sound_mxf) {
                boost::filesystem::path audio_to;
                audio_to /= _film->dir (_film->dcp_name ());
                audio_to /= _film->audio_mxf_filename ();
                                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);
 +
 +              _sound_mxf->set_file (audio_to);
        }
 -      
 -      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 ()
                        )
                );
        
 -      dcp.add_cpl (cpl);
 +      dcp.add (cpl);
 +
 +      shared_ptr<dcp::Reel> reel (new dcp::Reel ());
 +
 +      shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf);
 +      if (mono) {
 +              reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0)));
 +              dcp.add (mono);
 +      }
 +
 +      shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
 +      if (stereo) {
 +              reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
 +              dcp.add (stereo);
 +      }
 +
 +      if (_sound_mxf) {
 +              reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
 +              dcp.add (_sound_mxf);
 +      }
  
 -      cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
 -                                                       _picture_asset,
 -                                                       _sound_asset,
 -                                                       shared_ptr<libdcp::SubtitleAsset> ()
 -                                                       )
 -                             ));
 +      if (_subtitle_content) {
 +              _subtitle_content->write_xml (_film->dir (_film->dcp_name ()) / _film->subtitle_xml_filename ());
 +              reel->add (shared_ptr<dcp::ReelSubtitleAsset> (
 +                                 new dcp::ReelSubtitleAsset (
 +                                         _subtitle_content,
 +                                         dcp::Fraction (_film->video_frame_rate(), 1),
 +                                         _picture_mxf->intrinsic_duration (),
 +                                         0
 +                                         )
 +                                 ));
 +              
 +              dcp.add (_subtitle_content);
 +      }
 +      
 +      cpl->add (reel);
  
        shared_ptr<Job> job = _job.lock ();
        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));
  
 -      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;
 +      dcp::XMLMetadata meta;
        meta.issuer = Config::instance()->dcp_issuer ();
        meta.creator = String::compose ("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
        meta.set_issue_date_now ();
 -      dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
 -
 -      LOG_GENERAL (
 -              N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
 -              );
 -}
  
 -/** Tell the writer that frame `f' should be a repeat of the frame before it */
 -void
 -Writer::repeat (int f, Eyes e)
 -{
 -      boost::mutex::scoped_lock lock (_mutex);
 -
 -      while (_queued_full_in_memory > _maximum_frames_in_memory) {
 -              /* The queue is too big; wait until that is sorted out */
 -              _full_condition.wait (lock);
 -      }
 -      
 -      QueueItem qi;
 -      qi.type = QueueItem::REPEAT;
 -      qi.frame = f;
 -      if (_film->three_d() && e == EYES_BOTH) {
 -              qi.eyes = EYES_LEFT;
 -              _queue.push_back (qi);
 -              qi.eyes = EYES_RIGHT;
 -              _queue.push_back (qi);
 -      } else {
 -              qi.eyes = e;
 -              _queue.push_back (qi);
 +      shared_ptr<const dcp::Signer> signer;
 +      if (_film->is_signed ()) {
 +              signer = Config::instance()->signer ();
 +              /* We did check earlier, but check again here to be on the safe side */
 +              if (!signer->valid ()) {
 +                      throw InvalidSignerError ();
 +              }
        }
  
 -      /* Now there's something to do: wake anything wait()ing on _empty_condition */
 -      _empty_condition.notify_all ();
 +      dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer);
 +
 +      LOG_GENERAL (
 +              N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk
 +              );
  }
  
  bool
@@@ -518,7 -525,7 +522,7 @@@ Writer::check_existing_picture_mxf_fram
                return false;
        }
        
 -      libdcp::FrameInfo info (ifi);
 +      dcp::FrameInfo info (ifi);
        fclose (ifi);
        if (info.size == 0) {
                LOG_GENERAL ("Existing frame %1 has no info file", f);
@@@ -603,24 -610,6 +607,24 @@@ Writer::can_fake_write (int frame) cons
        return (frame != 0 && frame < _first_nonexistant_frame);
  }
  
 +void
 +Writer::write (PlayerSubtitles subs)
 +{
 +      if (subs.text.empty ()) {
 +              return;
 +      }
 +      
 +      if (!_subtitle_content) {
 +              _subtitle_content.reset (
 +                      new dcp::SubtitleContent (_film->name(), _film->isdcf_metadata().subtitle_language)
 +                      );
 +      }
 +      
 +      for (list<dcp::SubtitleString>::const_iterator i = subs.text.begin(); i != subs.text.end(); ++i) {
 +              _subtitle_content->add (*i);
 +      }
 +}
 +
  bool
  operator< (QueueItem const & a, QueueItem const & b)
  {
diff --combined src/tools/dcpomatic.cc
index 8a0aa245e7d995680a91cc8a434b9ad337d36e73,fa89a4871e77d071940bacb8494b29dda26311d4..01aa0158b5b7596f5cc96f1008e0caad91ce6621
@@@ -30,7 -30,7 +30,7 @@@
  #include <wx/stdpaths.h>
  #include <wx/cmdline.h>
  #include <wx/preferences.h>
 -#include <libdcp/exceptions.h>
 +#include <dcp/exceptions.h>
  #include "wx/film_viewer.h"
  #include "wx/film_editor.h"
  #include "wx/job_manager_view.h"
@@@ -45,7 -45,6 +45,7 @@@
  #include "wx/servers_list_dialog.h"
  #include "wx/hints_dialog.h"
  #include "wx/update_dialog.h"
 +#include "wx/content_panel.h"
  #include "lib/film.h"
  #include "lib/config.h"
  #include "lib/util.h"
@@@ -73,6 -72,8 +73,6 @@@ using std::exception
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
  
 -// #define DCPOMATIC_WINDOWS_CONSOLE 1
 -
  class FilmChangedDialog
  {
  public:
@@@ -126,7 -127,9 +126,9 @@@ enum 
        ID_jobs_show_dcp,
        ID_tools_hints,
        ID_tools_encoding_servers,
-       ID_tools_check_for_updates
+       ID_tools_check_for_updates,
+       /* IDs for shortcuts (with no associated menu item) */
+       ID_add_file
  };
  
  class Frame : public wxFrame
@@@ -142,24 -145,20 +144,24 @@@ public
                , _history_position (0)
                , _history_separator (0)
        {
 -#if defined(DCPOMATIC_WINDOWS) && defined(DCPOMATIC_WINDOWS_CONSOLE)
 -                AllocConsole();
 -              
 -              HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
 -              int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
 -              FILE* hf_out = _fdopen(hCrt, "w");
 -              setvbuf(hf_out, NULL, _IONBF, 1);
 -              *stdout = *hf_out;
 -              
 -              HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
 -              hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
 -              FILE* hf_in = _fdopen(hCrt, "r");
 -              setvbuf(hf_in, NULL, _IONBF, 128);
 -              *stdin = *hf_in;
 +#if defined(DCPOMATIC_WINDOWS)
 +              if (Config::instance()->win32_console ()) {
 +                      AllocConsole();
 +                      
 +                      HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
 +                      int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
 +                      FILE* hf_out = _fdopen(hCrt, "w");
 +                      setvbuf(hf_out, NULL, _IONBF, 1);
 +                      *stdout = *hf_out;
 +                      
 +                      HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
 +                      hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
 +                      FILE* hf_in = _fdopen(hCrt, "r");
 +                      setvbuf(hf_in, NULL, _IONBF, 128);
 +                      *stdin = *hf_in;
 +
 +                      cout << "DCP-o-matic is starting." << "\n";
 +              }
  #endif
  
                wxMenuBar* bar = new wxMenuBar;
  
                Bind (wxEVT_CLOSE_WINDOW, boost::bind (&Frame::close, this, _1));
  
 -              Bind (wxEVT_MENU, boost::bind (&FilmEditor::content_add_file_clicked, _film_editor), ID_add_file);
+               wxAcceleratorEntry accel[1];
+               accel[0].Set (wxACCEL_CTRL, static_cast<int>('A'), ID_add_file);
++              Bind (wxEVT_MENU, boost::bind (&ContentPanel::add_file_clicked, _film_editor->content_panel()), ID_add_file);
+               wxAcceleratorTable accel_table (1, accel);
+               SetAcceleratorTable (accel_table);
                /* Use a panel as the only child of the Frame so that we avoid
                   the dark-grey background on Windows.
                */
@@@ -405,7 -410,7 +413,7 @@@ private
                                        shared_ptr<Job> (new SendKDMEmailJob (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ()))
                                        );
                        }
 -              } catch (libdcp::NotEncryptedError& e) {
 +              } catch (dcp::NotEncryptedError& e) {
                        error_dialog (this, _("CPL's content is not encrypted."));
                } catch (exception& e) {
                        error_dialog (this, e.what ());
  
        void content_scale_to_fit_width ()
        {
 -              VideoContentList vc = _film_editor->selected_video_content ();
 +              VideoContentList vc = _film_editor->content_panel()->selected_video ();
                for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
                        (*i)->scale_and_crop_to_fit_width ();
                }
  
        void content_scale_to_fit_height ()
        {
 -              VideoContentList vc = _film_editor->selected_video_content ();
 +              VideoContentList vc = _film_editor->content_panel()->selected_video ();
                for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
                        (*i)->scale_and_crop_to_fit_height ();
                }
                }
                bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
                bool const have_cpl = _film && !_film->cpls().empty ();
 -              bool const have_selected_video_content = !_film_editor->selected_video_content().empty();
 +              bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty();
                
                for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
                        
        void setup_menu (wxMenuBar* m)
        {
                _file_menu = new wxMenu;
-               add_item (_file_menu, _("New..."), ID_file_new, ALWAYS);
-               add_item (_file_menu, _("&Open..."), ID_file_open, ALWAYS);
+               add_item (_file_menu, _("New...\tCtrl-N"), ID_file_new, ALWAYS);
+               add_item (_file_menu, _("&Open...\tCtrl-O"), ID_file_open, ALWAYS);
                _file_menu->AppendSeparator ();
-               add_item (_file_menu, _("&Save"), ID_file_save, NEEDS_FILM);
+               add_item (_file_menu, _("&Save\tCtrl-S"), ID_file_save, NEEDS_FILM);
                _file_menu->AppendSeparator ();
                add_item (_file_menu, _("&Properties..."), ID_file_properties, NEEDS_FILM);
  
  #endif        
        
  #ifdef __WXOSX__      
-               add_item (_file_menu, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+               add_item (_file_menu, _("&Preferences...\tCtrl-P"), wxID_PREFERENCES, ALWAYS);
  #else
                wxMenu* edit = new wxMenu;
-               add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+               add_item (edit, _("&Preferences...\tCtrl-P"), wxID_PREFERENCES, ALWAYS);
  #endif
  
                wxMenu* content = new wxMenu;
                add_item (content, _("Scale to fit &height"), ID_content_scale_to_fit_height, NEEDS_FILM | NEEDS_SELECTED_VIDEO_CONTENT);
                
                wxMenu* jobs_menu = new wxMenu;
-               add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
-               add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM);
+               add_item (jobs_menu, _("&Make DCP\tCtrl-M"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
+               add_item (jobs_menu, _("Make &KDMs...\tCtrl-K"), ID_jobs_make_kdms, NEEDS_FILM);
                add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
                add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
  
                wxMenu* tools = new wxMenu;
-               add_item (tools, _("Hints..."), ID_tools_hints, 0);
+               add_item (tools, _("Hints...\tCtrl-H"), ID_tools_hints, 0);
                add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
                add_item (tools, _("Check for updates"), ID_tools_check_for_updates, 0);
                
@@@ -702,9 -707,6 +710,9 @@@ static const wxCmdLineEntryDesc command
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
  };
  
 +/** @class App
 + *  @brief The magic App class for wxWidgets.
 + */
  class App : public wxApp
  {
        bool OnInit ()
diff --combined src/wx/content_panel.h
index 1f64d51c63bd8bf86332f9574b32efb54e0c60af,0000000000000000000000000000000000000000..ab198411ddf5b9296aa19aab163621af55b34bb4
mode 100644,000000..100644
--- /dev/null
@@@ -1,102 -1,0 +1,103 @@@
-       void add_file_clicked ();
 +/*
 +    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 <list>
 +#include <boost/shared_ptr.hpp>
 +#include "lib/types.h"
 +#include "lib/film.h"
 +#include "content_menu.h"
 +
 +class wxNotebook;
 +class wxPanel;
 +class wxSizer;
 +class wxListCtrl;
 +class wxListEvent;
 +class TimelineDialog;
 +class FilmEditor;
 +class ContentSubPanel;
 +class Film;
 +
 +class ContentPanel
 +{
 +public:
 +      ContentPanel (wxNotebook *, boost::shared_ptr<Film>);
 +
 +      boost::shared_ptr<Film> film () const {
 +              return _film;
 +      }
 +
 +      void set_film (boost::shared_ptr<Film> f);
 +      void set_general_sensitivity (bool s);
 +      void set_selection (boost::weak_ptr<Content>);
 +
 +      void film_changed (Film::Property p);
 +      void film_content_changed (int p);
 +
 +      wxPanel* panel () const {
 +              return _panel;
 +      }
 +
 +      wxNotebook* notebook () const {
 +              return _notebook;
 +      }
 +
 +      ContentList selected ();
 +      VideoContentList selected_video ();
 +      AudioContentList selected_audio ();
 +      SubtitleContentList selected_subtitle ();
 +      FFmpegContentList selected_ffmpeg ();
 +
++      void add_file_clicked ();
++      
 +private:      
 +      void sequence_video_changed ();
 +      void selection_changed ();
 +      void add_folder_clicked ();
 +      void remove_clicked ();
 +      void earlier_clicked ();
 +      void later_clicked ();
 +      void right_click (wxListEvent &);
 +      void files_dropped (wxDropFilesEvent &);
 +      void timeline_clicked ();
 +
 +      void setup ();
 +      void setup_sensitivity ();
 +
 +      wxPanel* _panel;
 +      wxSizer* _sizer;
 +      wxNotebook* _notebook;
 +      wxListCtrl* _content;
 +      wxButton* _add_file;
 +      wxButton* _add_folder;
 +      wxButton* _remove;
 +      wxButton* _earlier;
 +      wxButton* _later;
 +      wxButton* _timeline;
 +      wxCheckBox* _sequence_video;
 +      ContentSubPanel* _video_panel;
 +      ContentSubPanel* _audio_panel;
 +      ContentSubPanel* _subtitle_panel;
 +      ContentSubPanel* _timing_panel;
 +      std::list<ContentSubPanel *> _panels;
 +      ContentMenu* _menu;
 +      TimelineDialog* _timeline_dialog;
 +
 +      boost::shared_ptr<Film> _film;
 +      bool _generally_sensitive;
 +};
diff --combined src/wx/film_editor.h
index b311184fae003a73bf5da5dd6e39c29abea89f07,ba9ff6fa0cad78924f0b062fb7ab258135f5a9e5..25749fffaf10ec8d3120ee1f02a442fa31ecde11
   */
  
  #include <wx/wx.h>
 -#include <wx/spinctrl.h>
 -#include <wx/filepicker.h>
 -#include <wx/collpane.h>
  #include <boost/signals2.hpp>
  #include "lib/film.h"
 -#include "content_menu.h"
  
 +class wxSpinCtrl;
  class wxNotebook;
 -class wxListCtrl;
 -class wxListEvent;
 -class wxGridBagSizer;
  class Film;
 -class TimelineDialog;
  class Ratio;
 -class Timecode;
 -class FilmEditorPanel;
 -class SubtitleContent;
 +class ContentPanel;
 +class DCPPanel;
  
  /** @class FilmEditor
   *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@@ -41,31 -49,121 +41,30 @@@ public
        FilmEditor (wxWindow *);
  
        void set_film (boost::shared_ptr<Film>);
 -      void set_selection (boost::weak_ptr<Content>);
  
        boost::signals2::signal<void (boost::filesystem::path)> FileChanged;
  
        /* Stuff for panels */
 -      
 -      wxNotebook* content_notebook () const {
 -              return _content_notebook;
 -      }
  
 +      ContentPanel* content_panel () const {
 +              return _content_panel;
 +      }
 +      
        boost::shared_ptr<Film> film () const {
                return _film;
        }
  
 -      ContentList selected_content ();
 -      VideoContentList selected_video_content ();
 -      AudioContentList selected_audio_content ();
 -      SubtitleContentList selected_subtitle_content ();
 -      FFmpegContentList selected_ffmpeg_content ();
 -
 -      void content_add_file_clicked ();
 -      
--private:
 -      void make_dcp_panel ();
 -      void make_content_panel ();
 -      void connect_to_widgets ();
 -      
 -      /* Handle changes to the view */
 -      void name_changed ();
 -      void use_isdcf_name_toggled ();
 -      void edit_isdcf_button_clicked ();
 -      void content_selection_changed ();
 -      void content_add_folder_clicked ();
 -      void content_remove_clicked ();
 -      void content_earlier_clicked ();
 -      void content_later_clicked ();
 -      void content_files_dropped (wxDropFilesEvent& event);
 -      void container_changed ();
 -      void dcp_content_type_changed ();
 -      void scaler_changed ();
 -      void j2k_bandwidth_changed ();
 -      void frame_rate_choice_changed ();
 -      void frame_rate_spin_changed ();
 -      void best_frame_rate_clicked ();
 -      void content_timeline_clicked ();
 -      void audio_channels_changed ();
 -      void resolution_changed ();
 -      void sequence_video_changed ();
 -      void content_right_click (wxListEvent &);
 -      void three_d_changed ();
 -      void standard_changed ();
 -      void signed_toggled ();
 -      void encrypted_toggled ();
 -
        /* Handle changes to the model */
        void film_changed (Film::Property);
        void film_content_changed (int);
 -      void use_isdcf_name_changed ();
  
        void set_general_sensitivity (bool);
 -      void setup_dcp_name ();
 -      void setup_content ();
 -      void setup_container ();
 -      void setup_content_sensitivity ();
 -      void setup_frame_rate_widget ();
 -      
        void active_jobs_changed (bool);
 -      void config_changed ();
 -
 -      FilmEditorPanel* _video_panel;
 -      FilmEditorPanel* _audio_panel;
 -      FilmEditorPanel* _subtitle_panel;
 -      FilmEditorPanel* _timing_panel;
 -      std::list<FilmEditorPanel *> _panels;
  
        wxNotebook* _main_notebook;
 -      wxNotebook* _content_notebook;
 -      wxPanel* _dcp_panel;
 -      wxSizer* _dcp_sizer;
 -      wxPanel* _content_panel;
 -      wxSizer* _content_sizer;
 +      ContentPanel* _content_panel;
 +      DCPPanel* _dcp_panel;
  
        /** The film we are editing */
        boost::shared_ptr<Film> _film;
 -      wxTextCtrl* _name;
 -      wxStaticText* _dcp_name;
 -      wxCheckBox* _use_isdcf_name;
 -      wxChoice* _container;
 -      wxListCtrl* _content;
 -      wxButton* _content_add_file;
 -      wxButton* _content_add_folder;
 -      wxButton* _content_remove;
 -      wxButton* _content_earlier;
 -      wxButton* _content_later;
 -      wxButton* _content_timeline;
 -      wxCheckBox* _sequence_video;
 -      wxButton* _edit_isdcf_button;
 -      wxChoice* _scaler;
 -      wxSpinCtrl* _j2k_bandwidth;
 -      wxChoice* _dcp_content_type;
 -      wxChoice* _frame_rate_choice;
 -      wxSpinCtrl* _frame_rate_spin;
 -      wxSizer* _frame_rate_sizer;
 -      wxSpinCtrl* _audio_channels;
 -      wxButton* _best_frame_rate;
 -      wxCheckBox* _three_d;
 -      wxChoice* _resolution;
 -      wxChoice* _standard;
 -      wxCheckBox* _signed;
 -      wxCheckBox* _encrypted;
 -
 -      ContentMenu _menu;
 -
 -      std::vector<Ratio const *> _ratios;
 -
 -      bool _generally_sensitive;
 -      TimelineDialog* _timeline_dialog;
  };