Merge master.
authorCarl Hetherington <cth@carlh.net>
Sat, 4 May 2013 14:59:31 +0000 (15:59 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 4 May 2013 14:59:31 +0000 (15:59 +0100)
1  2 
src/lib/config.cc
src/lib/config.h
src/lib/film.cc
src/lib/format.cc
src/lib/format.h
src/lib/writer.cc
src/tools/dcpomatic_cli.cc
src/wx/config_dialog.cc
src/wx/config_dialog.h

diff --combined src/lib/config.cc
index 4f90581f627b32f3201c6246516fcbd6c859a40a,8c65e371a229aef9ff090f50f7e9deb57170d6c6..b6f464717b48c7f6ca3f5a590c58ca5480d01e22
  #include <fstream>
  #include <glib.h>
  #include <boost/filesystem.hpp>
 +#include <libcxml/cxml.h>
  #include "config.h"
  #include "server.h"
  #include "scaler.h"
  #include "filter.h"
+ #include "format.h"
+ #include "dcp_content_type.h"
  #include "sound_processor.h"
  
  #include "i18n.h"
@@@ -35,10 -36,7 +37,10 @@@ using std::vector
  using std::ifstream;
  using std::string;
  using std::ofstream;
 +using std::list;
  using boost::shared_ptr;
 +using boost::lexical_cast;
 +using boost::optional;
  
  Config* Config::_instance = 0;
  
@@@ -49,7 -47,8 +51,9 @@@ Config::Config (
        , _reference_scaler (Scaler::from_id (N_("bicubic")))
        , _tms_path (N_("."))
        , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
 +      , _default_still_length (10)
+       , _default_format (0)
+       , _default_dcp_content_type (0)
  {
        _allowed_dcp_frame_rates.push_back (24);
        _allowed_dcp_frame_rates.push_back (25);
        _allowed_dcp_frame_rates.push_back (48);
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
 +
 +      if (!boost::filesystem::exists (file (false))) {
 +              read_old_metadata ();
 +              return;
 +      }
 +
 +      cxml::File f (file (false), "Config");
 +      optional<string> c;
 +
 +      _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads");
 +      _default_directory = f.string_child ("DefaultDirectory");
 +      _server_port = f.number_child<int> ("ServerPort");
 +      c = f.optional_string_child ("ReferenceScaler");
 +      if (c) {
 +              _reference_scaler = Scaler::from_id (c.get ());
 +      }
 +
 +      list<shared_ptr<cxml::Node> > filters = f.node_children ("ReferenceFilter");
 +      for (list<shared_ptr<cxml::Node> >::iterator i = filters.begin(); i != filters.end(); ++i) {
 +              _reference_filters.push_back (Filter::from_id ((*i)->content ()));
 +      }
        
 -      ifstream f (file().c_str ());
 +      list<shared_ptr<cxml::Node> > servers = f.node_children ("Server");
 +      for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) {
 +              _servers.push_back (new ServerDescription (*i));
 +      }
 +
 +      _tms_ip = f.string_child ("TMSIP");
 +      _tms_path = f.string_child ("TMSPath");
 +      _tms_user = f.string_child ("TMSUser");
 +      _tms_password = f.string_child ("TMSPassword");
 +
 +      c = f.optional_string_child ("SoundProcessor");
 +      if (c) {
 +              _sound_processor = SoundProcessor::from_id (c.get ());
 +      }
 +
 +      _language = f.optional_string_child ("Language");
++
++      c = f.optional_string_child ("DefaultFormat");
++      if (c) {
++              _default_format = Format::from_id (c.get ());
++      }
++
++      c = f.optional_string_child ("DefaultDCPContentType");
++      if (c) {
++              _default_dcp_content_type = DCPContentType::from_dci_name (c.get ());
++      }
++
++      _dcp_metadata.issuer = f.optional_string_child ("DCPMetadataIssuer").get_value_or ("");
++      _dcp_metadata.creator = f.optional_string_child ("DCPMetadataCreator").get_value_or ("");
++
 +      _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
 +      _default_still_length = f.optional_number_child<int>("DefaultStillLength").get_value_or (10);
 +}
 +
 +void
 +Config::read_old_metadata ()
 +{
 +      ifstream f (file(true).c_str ());
        string line;
        while (getline (f, line)) {
                if (line.empty ()) {
                        _sound_processor = SoundProcessor::from_id (v);
                } else if (k == "language") {
                        _language = v;
 -                      _default_format = Format::from_metadata (v);
+               } else if (k == "default_format") {
++                      _default_format = Format::from_id (v);
+               } else if (k == "default_dcp_content_type") {
+                       _default_dcp_content_type = DCPContentType::from_dci_name (v);
+               } else if (k == "dcp_metadata_issuer") {
+                       _dcp_metadata.issuer = v;
+               } else if (k == "dcp_metadata_creator") {
+                       _dcp_metadata.creator = v;
+               } else if (k == "dcp_metadata_issue_date") {
+                       _dcp_metadata.issue_date = v;
                }
  
 -              _default_dci_metadata.read (k, v);
 +              _default_dci_metadata.read_old_metadata (k, v);
        }
  }
  
  /** @return Filename to write configuration to */
  string
 -Config::file () const
 +Config::file (bool old) const
  {
        boost::filesystem::path p;
        p /= g_get_user_config_dir ();
 -      p /= N_(".dvdomatic");
 +      if (old) {
 +              p /= ".dvdomatic";
 +      } else {
 +              p /= ".dcpomatic.xml";
 +      }
        return p.string ();
  }
  
@@@ -180,40 -141,44 +208,48 @@@ Config::instance (
  void
  Config::write () const
  {
 -      ofstream f (file().c_str ());
 -      f << "num_local_encoding_threads " << _num_local_encoding_threads << "\n"
 -        << "default_directory " << _default_directory << "\n"
 -        << "server_port " << _server_port << "\n";
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("Config");
  
 +      root->add_child("NumLocalEncodingThreads")->add_child_text (lexical_cast<string> (_num_local_encoding_threads));
 +      root->add_child("DefaultDirectory")->add_child_text (_default_directory);
 +      root->add_child("ServerPort")->add_child_text (lexical_cast<string> (_server_port));
        if (_reference_scaler) {
 -              f << "reference_scaler " << _reference_scaler->id () << "\n";
 +              root->add_child("ReferenceScaler")->add_child_text (_reference_scaler->id ());
        }
  
        for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
 -              f << "reference_filter " << (*i)->id () << "\n";
 +              root->add_child("ReferenceFilter")->add_child_text ((*i)->id ());
        }
        
        for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
 -              f << "server " << (*i)->as_metadata () << "\n";
 +              (*i)->as_xml (root->add_child ("Server"));
        }
  
 -      f << "tms_ip " << _tms_ip << "\n";
 -      f << "tms_path " << _tms_path << "\n";
 -      f << "tms_user " << _tms_user << "\n";
 -      f << "tms_password " << _tms_password << "\n";
 +      root->add_child("TMSIP")->add_child_text (_tms_ip);
 +      root->add_child("TMSPath")->add_child_text (_tms_path);
 +      root->add_child("TMSUser")->add_child_text (_tms_user);
 +      root->add_child("TMSPassword")->add_child_text (_tms_password);
        if (_sound_processor) {
 -              f << "sound_processor " << _sound_processor->id () << "\n";
 +              root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
        }
        if (_language) {
 -              f << "language " << _language.get() << "\n";
 +              root->add_child("Language")->add_child_text (_language.get());
        }
 -              f << "default_format " << _default_format->as_metadata() << "\n";
+       if (_default_format) {
 -              f << "default_dcp_content_type " << _default_dcp_content_type->dci_name() << "\n";
++              root->add_child("DefaultFormat")->add_child_text (_default_format->id ());
+       }
+       if (_default_dcp_content_type) {
 -      f << "dcp_metadata_issuer " << _dcp_metadata.issuer << "\n";
 -      f << "dcp_metadata_creator " << _dcp_metadata.creator << "\n";
 -      f << "dcp_metadata_issue_date " << _dcp_metadata.issue_date << "\n";
++              root->add_child("DefaultDCPContentType")->add_child_text (_default_dcp_content_type->dci_name ());
+       }
++      root->add_child("DCPMetadataIssuer")->add_child_text (_dcp_metadata.issuer);
++      root->add_child("DCPMetadataCreator")->add_child_text (_dcp_metadata.creator);
 +
 +      _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
 +
 +      root->add_child("DefaultStillLength")->add_child_text (lexical_cast<string> (_default_still_length));
  
 -      _default_dci_metadata.write (f);
 +      doc.write_to_file_formatted (file (false));
  }
  
  string
diff --combined src/lib/config.h
index 91926750b8a9a44b37096c144756fe6dca65ff95,a59cdcae0df448e61fb03c0e7694162e25a1600d..05005e5903d374a97472821976001320351c8133
   *  @brief Class holding configuration.
   */
  
 -#ifndef DVDOMATIC_CONFIG_H
 -#define DVDOMATIC_CONFIG_H
 +#ifndef DCPOMATIC_CONFIG_H
 +#define DCPOMATIC_CONFIG_H
  
  #include <vector>
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
+ #include <libdcp/metadata.h>
  #include "dci_metadata.h"
  
  class ServerDescription;
  class Scaler;
  class Filter;
  class SoundProcessor;
+ class Format;
+ class DCPContentType;
  
  /** @class Config
   *  @brief A singleton class holding configuration.
@@@ -107,10 -110,18 +110,22 @@@ public
                return _language;
        }
  
 +      int default_still_length () const {
 +              return _default_still_length;
 +      }
 +
+       Format const * default_format () const {
+               return _default_format;
+       }
+       DCPContentType const * default_dcp_content_type () const {
+               return _default_dcp_content_type;
+       }
+       libdcp::XMLMetadata dcp_metadata () const {
+               return _dcp_metadata;
+       }
        /** @param n New number of local encoding threads */
        void set_num_local_encoding_threads (int n) {
                _num_local_encoding_threads = n;
                _language = boost::none;
        }
  
 +      void set_default_still_length (int s) {
 +              _default_still_length = s;
 +      }
 +
+       void set_default_format (Format const * f) {
+               _default_format = f;
+       }
+       void set_default_dcp_content_type (DCPContentType const * t) {
+               _default_dcp_content_type = t;
+       }
+       void set_dcp_metadata (libdcp::XMLMetadata m) {
+               _dcp_metadata = m;
+       }
+       
        void write () const;
  
        static Config* instance ();
  
  private:
        Config ();
 -      std::string file () const;
 +      std::string file (bool) const;
 +      void read_old_metadata ();
  
        /** number of threads to use for J2K encoding on the local machine */
        int _num_local_encoding_threads;
        /** Default DCI metadata for newly-created Films */
        DCIMetadata _default_dci_metadata;
        boost::optional<std::string> _language;
 +      int _default_still_length;
+       Format const * _default_format;
+       DCPContentType const * _default_dcp_content_type;
+       libdcp::XMLMetadata _dcp_metadata;
  
        /** Singleton instance, or 0 */
        static Config* _instance;
diff --combined src/lib/film.cc
index 5481bd012a322d2390668ef8034fb6b14b17d912,0ca3746044291ff62de38ba8d130873a86b5001c..8fed87122977bdf311d9640f1cd2c8a4d92ab496
  #include <boost/algorithm/string.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/date_time.hpp>
 +#include <libxml++/libxml++.h>
 +#include <libcxml/cxml.h>
  #include "film.h"
  #include "format.h"
  #include "job.h"
  #include "filter.h"
 -#include "transcoder.h"
  #include "util.h"
  #include "job_manager.h"
  #include "ab_transcode_job.h"
  #include "transcode_job.h"
  #include "scp_dcp_job.h"
  #include "log.h"
 -#include "options.h"
  #include "exceptions.h"
  #include "examine_content_job.h"
  #include "scaler.h"
 -#include "decoder_factory.h"
  #include "config.h"
  #include "version.h"
  #include "ui_signaller.h"
 -#include "video_decoder.h"
 -#include "audio_decoder.h"
 -#include "sndfile_decoder.h"
  #include "analyse_audio_job.h"
 +#include "playlist.h"
 +#include "player.h"
 +#include "ffmpeg_content.h"
 +#include "imagemagick_content.h"
 +#include "sndfile_content.h"
 +#include "dcp_content_type.h"
  
  #include "i18n.h"
  
@@@ -69,8 -67,6 +69,8 @@@ using std::setfill
  using std::min;
  using std::make_pair;
  using std::endl;
 +using std::cout;
 +using std::list;
  using boost::shared_ptr;
  using boost::lexical_cast;
  using boost::to_upper_copy;
@@@ -90,18 -86,19 +90,18 @@@ int const Film::state_version = 4
   */
  
  Film::Film (string d, bool must_exist)
 -      : _use_dci_name (true)
 -      , _trust_content_header (true)
 +      : _playlist (new Playlist)
 +      , _use_dci_name (true)
 +      , _trust_content_headers (true)
-       , _dcp_content_type (0)
-       , _format (Format::from_id ("185"))
+       , _dcp_content_type (Config::instance()->default_dcp_content_type ())
+       , _format (Config::instance()->default_format ())
        , _scaler (Scaler::from_id ("bicubic"))
        , _trim_start (0)
        , _trim_end (0)
        , _trim_type (CPL)
 -      , _dcp_ab (false)
 -      , _use_content_audio (true)
 +      , _ab (false)
        , _audio_gain (0)
        , _audio_delay (0)
 -      , _still_duration (10)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
        , _dcp_frame_rate (0)
 -      , _source_frame_rate (0)
        , _dirty (false)
  {
        set_dci_date_today ();
 +
 +      _playlist->Changed.connect (bind (&Film::playlist_changed, this));
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
                }
        }
  
 -      _sndfile_stream = SndfileStream::create ();
 -      
        if (must_exist) {
                read_metadata ();
        } else {
@@@ -157,11 -154,11 +157,11 @@@ Film::Film (Film const & o
        : boost::enable_shared_from_this<Film> (o)
        /* note: the copied film shares the original's log */
        , _log               (o._log)
 +      , _playlist          (new Playlist (o._playlist))
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
 -      , _content           (o._content)
 -      , _trust_content_header (o._trust_content_header)
 +      , _trust_content_headers (o._trust_content_headers)
        , _dcp_content_type  (o._dcp_content_type)
        , _format            (o._format)
        , _crop              (o._crop)
        , _trim_start        (o._trim_start)
        , _trim_end          (o._trim_end)
        , _trim_type         (o._trim_type)
 -      , _dcp_ab            (o._dcp_ab)
 -      , _content_audio_stream (o._content_audio_stream)
 -      , _external_audio    (o._external_audio)
 -      , _use_content_audio (o._use_content_audio)
 +      , _ab                (o._ab)
        , _audio_gain        (o._audio_gain)
        , _audio_delay       (o._audio_delay)
 -      , _still_duration    (o._still_duration)
 -      , _subtitle_stream   (o._subtitle_stream)
        , _with_subtitles    (o._with_subtitles)
        , _subtitle_offset   (o._subtitle_offset)
        , _subtitle_scale    (o._subtitle_scale)
        , _colour_lut        (o._colour_lut)
        , _j2k_bandwidth     (o._j2k_bandwidth)
        , _dci_metadata      (o._dci_metadata)
 -      , _dci_date          (o._dci_date)
        , _dcp_frame_rate    (o._dcp_frame_rate)
 -      , _size              (o._size)
 -      , _length            (o._length)
 -      , _content_digest    (o._content_digest)
 -      , _content_audio_streams (o._content_audio_streams)
 -      , _sndfile_stream    (o._sndfile_stream)
 -      , _subtitle_streams  (o._subtitle_streams)
 -      , _source_frame_rate (o._source_frame_rate)
 +      , _dci_date          (o._dci_date)
        , _dirty             (o._dirty)
  {
 -      
 -}
 -
 -Film::~Film ()
 -{
 -
 +      _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
  }
  
  string
@@@ -196,7 -210,7 +196,7 @@@ Film::video_state_identifier () cons
  
        stringstream s;
        s << format()->id()
 -        << "_" << content_digest()
 +        << "_" << _playlist->video_digest()
          << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom
          << "_" << _dcp_frame_rate
          << "_" << f.first << "_" << f.second
          << "_" << j2k_bandwidth()
          << "_" << boost::lexical_cast<int> (colour_lut());
  
 -      if (dcp_ab()) {
 +      if (ab()) {
                pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
                s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
        }
@@@ -268,7 -282,7 +268,7 @@@ Film::audio_analysis_path () cons
  {
        boost::filesystem::path p;
        p /= "analysis";
 -      p /= content_digest();
 +      p /= _playlist->audio_digest();
        return file (p.string ());
  }
  
@@@ -282,7 -296,7 +282,7 @@@ Film::make_dcp (
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
        
 -      log()->log (String::compose ("DVD-o-matic %1 git %2 using %3", dvdomatic_version, dvdomatic_git_commit, dependency_version_summary()));
 +      log()->log (String::compose ("DCP-o-matic %1 git %2 using %3", dcpomatic_version, dcpomatic_git_commit, dependency_version_summary()));
  
        {
                char buffer[128];
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
        }
        
 -      log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 -      if (length()) {
 -              log()->log (String::compose ("Content length %1", length().get()));
 -      }
 -      log()->log (String::compose ("Content digest %1", content_digest()));
 -      log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
 +//    log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
 +//    if (length()) {
 +//            log()->log (String::compose ("Content length %1", length().get()));
 +//    }
 +//    log()->log (String::compose ("Content digest %1", content_digest()));
 +//    log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
 -#ifdef DVDOMATIC_DEBUG
 -      log()->log ("DVD-o-matic built in debug mode.");
 +#ifdef DCPOMATIC_DEBUG
 +      log()->log ("DCP-o-matic built in debug mode.");
  #else
 -      log()->log ("DVD-o-matic built in optimised mode.");
 +      log()->log ("DCP-o-matic built in optimised mode.");
  #endif
  #ifdef LIBDCP_DEBUG
        log()->log ("libdcp built in debug mode.");
                throw MissingSettingError (_("format"));
        }
  
 -      if (content().empty ()) {
 +      if (_playlist->content().empty ()) {
                throw MissingSettingError (_("content"));
        }
  
                throw MissingSettingError (_("name"));
        }
  
 -      DecodeOptions od;
 -      od.decode_subtitles = with_subtitles ();
 -
        shared_ptr<Job> r;
  
 -      if (dcp_ab()) {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
 +      if (ab()) {
 +              r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
        } else {
 -              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
 +              r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
        }
  }
  
 -/** Start a job to analyse the audio of our content file */
 +/** Start a job to analyse the audio in our Playlist */
  void
  Film::analyse_audio ()
  {
        JobManager::instance()->add (_analyse_audio_job);
  }
  
 -/** Start a job to examine our content file */
 +/** Start a job to examine a piece of content */
  void
 -Film::examine_content ()
 +Film::examine_content (shared_ptr<Content> c)
  {
 -      if (_examine_content_job) {
 -              return;
 -      }
 -
 -      _examine_content_job.reset (new ExamineContentJob (shared_from_this()));
 -      _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
 -      JobManager::instance()->add (_examine_content_job);
 +      shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c, trust_content_headers ()));
 +      JobManager::instance()->add (j);
  }
  
  void
@@@ -369,6 -391,12 +369,6 @@@ Film::analyse_audio_finished (
        _analyse_audio_job.reset ();
  }
  
 -void
 -Film::examine_content_finished ()
 -{
 -      _examine_content_job.reset ();
 -}
 -
  /** Start a job to send our DCP to the configured TMS */
  void
  Film::send_dcp_to_tms ()
@@@ -405,57 -433,81 +405,57 @@@ Film::write_metadata () cons
  
        boost::filesystem::create_directories (directory());
  
 -      string const m = file ("metadata");
 -      ofstream f (m.c_str ());
 -      if (!f.good ()) {
 -              throw CreateFileError (m);
 -      }
 +      xmlpp::Document doc;
 +      xmlpp::Element* root = doc.create_root_node ("Metadata");
  
 -      f << "version " << state_version << endl;
 +      root->add_child("Version")->add_child_text (boost::lexical_cast<string> (state_version));
 +      root->add_child("Name")->add_child_text (_name);
 +      root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
 +      root->add_child("TrustContentHeaders")->add_child_text (_trust_content_headers ? "1" : "0");
  
 -      /* User stuff */
 -      f << "name " << _name << endl;
 -      f << "use_dci_name " << _use_dci_name << endl;
 -      f << "content " << _content << endl;
 -      f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
        if (_dcp_content_type) {
 -              f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
 +              root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
 +
        if (_format) {
 -              f << "format " << _format->as_metadata () << endl;
 +              root->add_child("Format")->add_child_text (_format->id ());
        }
 -      f << "left_crop " << _crop.left << endl;
 -      f << "right_crop " << _crop.right << endl;
 -      f << "top_crop " << _crop.top << endl;
 -      f << "bottom_crop " << _crop.bottom << endl;
 -      for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
 -              f << "filter " << (*i)->id () << endl;
 -      }
 -      f << "scaler " << _scaler->id () << endl;
 -      f << "trim_start " << _trim_start << endl;
 -      f << "trim_end " << _trim_end << endl;
 +
        switch (_trim_type) {
        case CPL:
 -              f << "trim_type cpl\n";
 +              root->add_child("TrimType")->add_child_text ("CPL");
                break;
        case ENCODE:
 -              f << "trim_type encode\n";
 -              break;
 -      }
 -      f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
 -      if (_content_audio_stream) {
 -              f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
 -      }
 -      for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
 -              f << "external_audio " << *i << endl;
 -      }
 -      f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
 -      f << "audio_gain " << _audio_gain << endl;
 -      f << "audio_delay " << _audio_delay << endl;
 -      f << "still_duration " << _still_duration << endl;
 -      if (_subtitle_stream) {
 -              f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
 -      }
 -      f << "with_subtitles " << _with_subtitles << endl;
 -      f << "subtitle_offset " << _subtitle_offset << endl;
 -      f << "subtitle_scale " << _subtitle_scale << endl;
 -      f << "colour_lut " << _colour_lut << endl;
 -      f << "j2k_bandwidth " << _j2k_bandwidth << endl;
 -      _dci_metadata.write (f);
 -      f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
 -      f << "dcp_frame_rate " << _dcp_frame_rate << endl;
 -      f << "width " << _size.width << endl;
 -      f << "height " << _size.height << endl;
 -      f << "length " << _length.get_value_or(0) << endl;
 -      f << "content_digest " << _content_digest << endl;
 -
 -      for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -              f << "content_audio_stream " << (*i)->to_string () << endl;
 +              root->add_child("TrimType")->add_child_text ("Encode");
        }
 +                      
 +      root->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
 +      root->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
 +      root->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
 +      root->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
  
 -      f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
 -
 -      for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
 -              f << "subtitle_stream " << (*i)->to_string () << endl;
 +      for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
 +              root->add_child("Filter")->add_child_text ((*i)->id ());
        }
 -
 -      f << "source_frame_rate " << _source_frame_rate << endl;
 +      
 +      root->add_child("Scaler")->add_child_text (_scaler->id ());
 +      root->add_child("TrimStart")->add_child_text (boost::lexical_cast<string> (_trim_start));
 +      root->add_child("TrimEnd")->add_child_text (boost::lexical_cast<string> (_trim_end));
 +      root->add_child("AB")->add_child_text (_ab ? "1" : "0");
 +      root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain));
 +      root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay));
 +      root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
 +      root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset));
 +      root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale));
 +      root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut));
 +      root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth));
 +      _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
 +      root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate));
 +      root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
 +      _audio_mapping.as_xml (root->add_child("AudioMapping"));
 +      _playlist->as_xml (root->add_child ("Playlist"));
 +
 +      doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
  }
@@@ -467,69 -519,165 +467,69 @@@ Film::read_metadata (
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
  
 -      _external_audio.clear ();
 -      _content_audio_streams.clear ();
 -      _subtitle_streams.clear ();
 -
 -      boost::optional<int> version;
 -
 -      /* Backward compatibility things */
 -      boost::optional<int> audio_sample_rate;
 -      boost::optional<int> audio_stream_index;
 -      boost::optional<int> subtitle_stream_index;
 -
 -      ifstream f (file ("metadata").c_str());
 -      if (!f.good()) {
 -              throw OpenFileError (file ("metadata"));
 +      if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
 +              throw StringError (_("This film was created with an older version of DCP-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
 -      
 -      multimap<string, string> kv = read_key_value (f);
  
 -      /* We need version before anything else */
 -      multimap<string, string>::iterator v = kv.find ("version");
 -      if (v != kv.end ()) {
 -              version = atoi (v->second.c_str());
 -      }
 +      cxml::File f (file ("metadata.xml"), "Metadata");
        
 -      for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
 -              string const k = i->first;
 -              string const v = i->second;
 +      _name = f.string_child ("Name");
 +      _use_dci_name = f.bool_child ("UseDCIName");
 +      _trust_content_headers = f.bool_child ("TrustContentHeaders");
  
 -              if (k == "audio_sample_rate") {
 -                      audio_sample_rate = atoi (v.c_str());
 -              }
 -
 -              /* User-specified stuff */
 -              if (k == "name") {
 -                      _name = v;
 -              } else if (k == "use_dci_name") {
 -                      _use_dci_name = (v == "1");
 -              } else if (k == "content") {
 -                      _content = v;
 -              } else if (k == "trust_content_header") {
 -                      _trust_content_header = (v == "1");
 -              } else if (k == "dcp_content_type") {
 -                      if (version < 3) {
 -                              _dcp_content_type = DCPContentType::from_pretty_name (v);
 -                      } else {
 -                              _dcp_content_type = DCPContentType::from_dci_name (v);
 -                      }
 -              } else if (k == "format") {
 -                      _format = Format::from_metadata (v);
 -              } else if (k == "left_crop") {
 -                      _crop.left = atoi (v.c_str ());
 -              } else if (k == "right_crop") {
 -                      _crop.right = atoi (v.c_str ());
 -              } else if (k == "top_crop") {
 -                      _crop.top = atoi (v.c_str ());
 -              } else if (k == "bottom_crop") {
 -                      _crop.bottom = atoi (v.c_str ());
 -              } else if (k == "filter") {
 -                      _filters.push_back (Filter::from_id (v));
 -              } else if (k == "scaler") {
 -                      _scaler = Scaler::from_id (v);
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
 -                      _trim_start = atoi (v.c_str ());
 -              } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
 -                      _trim_end = atoi (v.c_str ());
 -              } else if (k == "trim_type") {
 -                      if (v == "cpl") {
 -                              _trim_type = CPL;
 -                      } else if (v == "encode") {
 -                              _trim_type = ENCODE;
 -                      }
 -              } else if (k == "dcp_ab") {
 -                      _dcp_ab = (v == "1");
 -              } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
 -                      if (!version) {
 -                              audio_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _content_audio_stream = audio_stream_factory (v, version);
 -                      }
 -              } else if (k == "external_audio") {
 -                      _external_audio.push_back (v);
 -              } else if (k == "use_content_audio") {
 -                      _use_content_audio = (v == "1");
 -              } else if (k == "audio_gain") {
 -                      _audio_gain = atof (v.c_str ());
 -              } else if (k == "audio_delay") {
 -                      _audio_delay = atoi (v.c_str ());
 -              } else if (k == "still_duration") {
 -                      _still_duration = atoi (v.c_str ());
 -              } else if (k == "selected_subtitle_stream") {
 -                      if (!version) {
 -                              subtitle_stream_index = atoi (v.c_str ());
 -                      } else {
 -                              _subtitle_stream = subtitle_stream_factory (v, version);
 -                      }
 -              } else if (k == "with_subtitles") {
 -                      _with_subtitles = (v == "1");
 -              } else if (k == "subtitle_offset") {
 -                      _subtitle_offset = atoi (v.c_str ());
 -              } else if (k == "subtitle_scale") {
 -                      _subtitle_scale = atof (v.c_str ());
 -              } else if (k == "colour_lut") {
 -                      _colour_lut = atoi (v.c_str ());
 -              } else if (k == "j2k_bandwidth") {
 -                      _j2k_bandwidth = atoi (v.c_str ());
 -              } else if (k == "dci_date") {
 -                      _dci_date = boost::gregorian::from_undelimited_string (v);
 -              } else if (k == "dcp_frame_rate") {
 -                      _dcp_frame_rate = atoi (v.c_str ());
 +      {
 +              optional<string> c = f.optional_string_child ("DCPContentType");
 +              if (c) {
 +                      _dcp_content_type = DCPContentType::from_dci_name (c.get ());
                }
 +      }
  
 -              _dci_metadata.read (k, v);
 -              
 -              /* Cached stuff */
 -              if (k == "width") {
 -                      _size.width = atoi (v.c_str ());
 -              } else if (k == "height") {
 -                      _size.height = atoi (v.c_str ());
 -              } else if (k == "length") {
 -                      int const vv = atoi (v.c_str ());
 -                      if (vv) {
 -                              _length = vv;
 -                      }
 -              } else if (k == "content_digest") {
 -                      _content_digest = v;
 -              } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
 -                      _content_audio_streams.push_back (audio_stream_factory (v, version));
 -              } else if (k == "external_audio_stream") {
 -                      _sndfile_stream = audio_stream_factory (v, version);
 -              } else if (k == "subtitle_stream") {
 -                      _subtitle_streams.push_back (subtitle_stream_factory (v, version));
 -              } else if (k == "source_frame_rate") {
 -                      _source_frame_rate = atof (v.c_str ());
 -              } else if (version < 4 && k == "frames_per_second") {
 -                      _source_frame_rate = atof (v.c_str ());
 -                      /* Fill in what would have been used for DCP frame rate by the older version */
 -                      _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
 +      {
 +              optional<string> c = f.optional_string_child ("Format");
 +              if (c) {
 +                      _format = Format::from_id (c.get ());
                }
        }
  
 -      if (!version) {
 -              if (audio_sample_rate) {
 -                      /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
 -                      for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
 -                              (*i)->set_sample_rate (audio_sample_rate.get());
 -                      }
 +      {
 +              optional<string> c = f.optional_string_child ("TrimType");
 +              if (!c || c.get() == "CPL") {
 +                      _trim_type = CPL;
 +              } else if (c && c.get() == "Encode") {
 +                      _trim_type = ENCODE;
                }
 +      }
  
 -              /* also the selected stream was specified as an index */
 -              if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) {
 -                      _content_audio_stream = _content_audio_streams[audio_stream_index.get()];
 -              }
 +      _crop.left = f.number_child<int> ("LeftCrop");
 +      _crop.right = f.number_child<int> ("RightCrop");
 +      _crop.top = f.number_child<int> ("TopCrop");
 +      _crop.bottom = f.number_child<int> ("BottomCrop");
  
 -              /* similarly the subtitle */
 -              if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) {
 -                      _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()];
 +      {
 +              list<shared_ptr<cxml::Node> > c = f.node_children ("Filter");
 +              for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
 +                      _filters.push_back (Filter::from_id ((*i)->content ()));
                }
        }
 -              
 +
 +      _scaler = Scaler::from_id (f.string_child ("Scaler"));
 +      _trim_start = f.number_child<int> ("TrimStart");
 +      _trim_end = f.number_child<int> ("TrimEnd");
 +      _ab = f.bool_child ("AB");
 +      _audio_gain = f.number_child<float> ("AudioGain");
 +      _audio_delay = f.number_child<int> ("AudioDelay");
 +      _with_subtitles = f.bool_child ("WithSubtitles");
 +      _subtitle_offset = f.number_child<float> ("SubtitleOffset");
 +      _subtitle_scale = f.number_child<float> ("SubtitleScale");
 +      _colour_lut = f.number_child<int> ("ColourLUT");
 +      _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
 +      _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
 +      _dcp_frame_rate = f.number_child<int> ("DCPFrameRate");
 +      _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
 +
 +      _playlist->set_from_xml (f.node_child ("Playlist"));
 +      _audio_mapping.set_from_xml (_playlist->content(), f.node_child ("AudioMapping"));
 +
        _dirty = false;
  }
  
@@@ -577,18 -725,47 +577,18 @@@ Film::file (string f) cons
        return p.string ();
  }
  
 -/** @return full path of the content (actual video) file
 - *  of the Film.
 - */
 -string
 -Film::content_path () const
 -{
 -      boost::mutex::scoped_lock lm (_state_mutex);
 -      if (boost::filesystem::path(_content).has_root_directory ()) {
 -              return _content;
 -      }
 -
 -      return file (_content);
 -}
 -
 -ContentType
 -Film::content_type () const
 -{
 -      if (boost::filesystem::is_directory (_content)) {
 -              /* Directory of images, we assume */
 -              return VIDEO;
 -      }
 -
 -      if (still_image_file (_content)) {
 -              return STILL;
 -      }
 -
 -      return VIDEO;
 -}
 -
  /** @return The sampling rate that we will resample the audio to */
  int
  Film::target_audio_sample_rate () const
  {
 -      if (!audio_stream()) {
 +      if (!has_audio ()) {
                return 0;
        }
        
        /* Resample to a DCI-approved sample rate */
 -      double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
 +      double t = dcp_audio_sample_rate (audio_frame_rate());
  
 -      FrameRateConversion frc (source_frame_rate(), dcp_frame_rate());
 +      FrameRateConversion frc (video_frame_rate(), dcp_frame_rate());
  
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
        */
  
        if (frc.change_speed) {
 -              t *= source_frame_rate() * frc.factor() / dcp_frame_rate();
 +              t *= video_frame_rate() * frc.factor() / dcp_frame_rate();
        }
  
        return rint (t);
  }
  
 -int
 -Film::still_duration_in_frames () const
 -{
 -      return still_duration() * source_frame_rate();
 -}
 -
  /** @return a DCI-compliant name for a DCP of this film */
  string
  Film::dci_name (bool if_created_now) const
                }
        }
  
 -      switch (audio_channels()) {
 +      switch (audio_channels ()) {
        case 1:
                d << "_10";
                break;
@@@ -728,22 -911,110 +728,22 @@@ Film::set_use_dci_name (bool u
  }
  
  void
 -Film::set_content (string c)
 -{
 -      string check = directory ();
 -
 -      boost::filesystem::path slash ("/");
 -      string platform_slash = slash.make_preferred().string ();
 -
 -      if (!ends_with (check, platform_slash)) {
 -              check += platform_slash;
 -      }
 -      
 -      if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
 -              c = c.substr (_directory.length() + 1);
 -      }
 -
 -      string old_content;
 -      
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              if (c == _content) {
 -                      return;
 -              }
 -
 -              old_content = _content;
 -              _content = c;
 -      }
 -
 -      /* Reset streams here in case the new content doesn't have one or the other */
 -      _content_audio_stream = shared_ptr<AudioStream> ();
 -      _subtitle_stream = shared_ptr<SubtitleStream> ();
 -
 -      /* Start off using content audio */
 -      set_use_content_audio (true);
 -
 -      /* Create a temporary decoder so that we can get information
 -         about the content.
 -      */
 -
 -      try {
 -              Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
 -              
 -              set_size (d.video->native_size ());
 -              set_source_frame_rate (d.video->frames_per_second ());
 -              set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
 -              set_subtitle_streams (d.video->subtitle_streams ());
 -              if (d.audio) {
 -                      set_content_audio_streams (d.audio->audio_streams ());
 -              }
 -
 -              {
 -                      boost::mutex::scoped_lock lm (_state_mutex);
 -                      _content = c;
 -              }
 -              
 -              signal_changed (CONTENT);
 -              
 -              /* Start off with the first audio and subtitle streams */
 -              if (d.audio && !d.audio->audio_streams().empty()) {
 -                      set_content_audio_stream (d.audio->audio_streams().front());
 -              }
 -              
 -              if (!d.video->subtitle_streams().empty()) {
 -                      set_subtitle_stream (d.video->subtitle_streams().front());
 -              }
 -              
 -              examine_content ();
 -
 -      } catch (...) {
 -
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content = old_content;
 -              throw;
 -
 -      }
 -
 -      /* Default format */
 -      switch (content_type()) {
 -      case STILL:
 -              set_format (Format::from_id ("var-185"));
 -              break;
 -      case VIDEO:
 -              set_format (Format::from_id ("185"));
 -              break;
 -      }
 -
 -      /* Still image DCPs must use external audio */
 -      if (content_type() == STILL) {
 -              set_use_content_audio (false);
 -      }
 -}
 -
 -void
 -Film::set_trust_content_header (bool t)
 +Film::set_trust_content_headers (bool t)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _trust_content_header = t;
 +              _trust_content_headers = t;
        }
        
 -      signal_changed (TRUST_CONTENT_HEADER);
 +      signal_changed (TRUST_CONTENT_HEADERS);
  
 -      if (!_trust_content_header && !content().empty()) {
 +      
 +      ContentList content = _playlist->content ();
 +      if (!_trust_content_headers && !content.empty()) {
                /* We just said that we don't trust the content's header */
 -              examine_content ();
 +              for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 +                      examine_content (*i);
 +              }
        }
  }
               
@@@ -885,13 -1156,50 +885,13 @@@ Film::set_trim_type (TrimType t
  }
  
  void
 -Film::set_dcp_ab (bool a)
 +Film::set_ab (bool a)
  {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
 -              _dcp_ab = a;
 +              _ab = a;
        }
 -      signal_changed (DCP_AB);
 -}
 -
 -void
 -Film::set_content_audio_stream (shared_ptr<AudioStream> s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_stream = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAM);
 -}
 -
 -void
 -Film::set_external_audio (vector<string> a)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _external_audio = a;
 -      }
 -
 -      shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
 -      if (decoder->audio_stream()) {
 -              _sndfile_stream = decoder->audio_stream ();
 -      }
 -      
 -      signal_changed (EXTERNAL_AUDIO);
 -}
 -
 -void
 -Film::set_use_content_audio (bool e)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _use_content_audio = e;
 -      }
 -
 -      signal_changed (USE_CONTENT_AUDIO);
 +      signal_changed (AB);
  }
  
  void
@@@ -914,6 -1222,26 +914,6 @@@ Film::set_audio_delay (int d
        signal_changed (AUDIO_DELAY);
  }
  
 -void
 -Film::set_still_duration (int d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _still_duration = d;
 -      }
 -      signal_changed (STILL_DURATION);
 -}
 -
 -void
 -Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_stream = s;
 -      }
 -      signal_changed (SUBTITLE_STREAM);
 -}
 -
  void
  Film::set_with_subtitles (bool w)
  {
@@@ -985,6 -1313,76 +985,6 @@@ Film::set_dcp_frame_rate (int f
        signal_changed (DCP_FRAME_RATE);
  }
  
 -void
 -Film::set_size (libdcp::Size s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _size = s;
 -      }
 -      signal_changed (SIZE);
 -}
 -
 -void
 -Film::set_length (SourceFrame l)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = l;
 -      }
 -      signal_changed (LENGTH);
 -}
 -
 -void
 -Film::unset_length ()
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _length = boost::none;
 -      }
 -      signal_changed (LENGTH);
 -}
 -
 -void
 -Film::set_content_digest (string d)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_digest = d;
 -      }
 -      _dirty = true;
 -}
 -
 -void
 -Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _content_audio_streams = s;
 -      }
 -      signal_changed (CONTENT_AUDIO_STREAMS);
 -}
 -
 -void
 -Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _subtitle_streams = s;
 -      }
 -      signal_changed (SUBTITLE_STREAMS);
 -}
 -
 -void
 -Film::set_source_frame_rate (float f)
 -{
 -      {
 -              boost::mutex::scoped_lock lm (_state_mutex);
 -              _source_frame_rate = f;
 -      }
 -      signal_changed (SOURCE_FRAME_RATE);
 -}
 -      
  void
  Film::signal_changed (Property p)
  {
                _dirty = true;
        }
  
 -      if (ui_signaller) {
 -              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
 +      switch (p) {
 +      case Film::CONTENT:
 +              set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
 +              set_audio_mapping (_playlist->default_audio_mapping ());
 +              break;
 +      default:
 +              break;
        }
 -}
  
 -int
 -Film::audio_channels () const
 -{
 -      shared_ptr<AudioStream> s = audio_stream ();
 -      if (!s) {
 -              return 0;
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (Changed), p));
        }
 -
 -      return s->channels ();
  }
  
  void
@@@ -1013,6 -1413,16 +1013,6 @@@ Film::set_dci_date_today (
        _dci_date = boost::gregorian::day_clock::local_day ();
  }
  
 -boost::shared_ptr<AudioStream>
 -Film::audio_stream () const
 -{
 -      if (use_content_audio()) {
 -              return _content_audio_stream;
 -      }
 -
 -      return _sndfile_stream;
 -}
 -
  string
  Film::info_path (int f) const
  {
@@@ -1068,193 -1478,20 +1068,193 @@@ Film::have_dcp () cons
        return true;
  }
  
 +shared_ptr<Player>
 +Film::player () const
 +{
 +      boost::mutex::scoped_lock lm (_state_mutex);
 +      return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
 +}
 +
 +ContentList
 +Film::content () const
 +{
 +      return _playlist->content ();
 +}
 +
 +void
 +Film::add_content (shared_ptr<Content> c)
 +{
 +      _playlist->add (c);
 +      examine_content (c);
 +}
 +
 +void
 +Film::remove_content (shared_ptr<Content> c)
 +{
 +      _playlist->remove (c);
 +}
 +
 +void
 +Film::move_content_earlier (shared_ptr<Content> c)
 +{
 +      _playlist->move_earlier (c);
 +}
 +
 +void
 +Film::move_content_later (shared_ptr<Content> c)
 +{
 +      _playlist->move_later (c);
 +}
 +
 +ContentAudioFrame
 +Film::audio_length () const
 +{
 +      return _playlist->audio_length ();
 +}
 +
 +int
 +Film::audio_channels () const
 +{
 +      return _playlist->audio_channels ();
 +}
 +
 +int
 +Film::audio_frame_rate () const
 +{
 +      return _playlist->audio_frame_rate ();
 +}
 +
  bool
  Film::has_audio () const
  {
 -      if (use_content_audio()) {
 -              return audio_stream();
 +      return _playlist->has_audio ();
 +}
 +
 +float
 +Film::video_frame_rate () const
 +{
 +      return _playlist->video_frame_rate ();
 +}
 +
 +libdcp::Size
 +Film::video_size () const
 +{
 +      return _playlist->video_size ();
 +}
 +
 +ContentVideoFrame
 +Film::video_length () const
 +{
 +      return _playlist->video_length ();
 +}
 +
 +ContentVideoFrame
 +Film::content_length () const
 +{
 +      return _playlist->content_length ();
 +}
 +
 +vector<FFmpegSubtitleStream>
 +Film::ffmpeg_subtitle_streams () const
 +{
 +      shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
 +      if (f) {
 +              return f->subtitle_streams ();
        }
  
 -      vector<string> const e = external_audio ();
 -      for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
 -              if (!i->empty ()) {
 -                      return true;
 -              }
 +      return vector<FFmpegSubtitleStream> ();
 +}
 +
 +boost::optional<FFmpegSubtitleStream>
 +Film::ffmpeg_subtitle_stream () const
 +{
 +      shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
 +      if (f) {
 +              return f->subtitle_stream ();
        }
  
 -      return false;
 +      return boost::none;
  }
  
 +vector<FFmpegAudioStream>
 +Film::ffmpeg_audio_streams () const
 +{
 +      shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
 +      if (f) {
 +              return f->audio_streams ();
 +      }
 +
 +      return vector<FFmpegAudioStream> ();
 +}
 +
 +boost::optional<FFmpegAudioStream>
 +Film::ffmpeg_audio_stream () const
 +{
 +      shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
 +      if (f) {
 +              return f->audio_stream ();
 +      }
 +
 +      return boost::none;
 +}
 +
 +void
 +Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s)
 +{
 +      shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
 +      if (f) {
 +              f->set_subtitle_stream (s);
 +      }
 +}
 +
 +void
 +Film::set_ffmpeg_audio_stream (FFmpegAudioStream s)
 +{
 +      shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
 +      if (f) {
 +              f->set_audio_stream (s);
 +      }
 +}
 +
 +void
 +Film::set_audio_mapping (AudioMapping m)
 +{
 +      {
 +              boost::mutex::scoped_lock lm (_state_mutex);
 +              _audio_mapping = m;
 +      }
 +
 +      signal_changed (AUDIO_MAPPING);
 +}
 +
 +void
 +Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 +{
 +      if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
 +              set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
 +      } else if (p == AudioContentProperty::AUDIO_CHANNELS) {
 +              set_audio_mapping (_playlist->default_audio_mapping ());
 +      } 
 +
 +      if (ui_signaller) {
 +              ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
 +      }
 +}
 +
 +void
 +Film::playlist_changed ()
 +{
 +      signal_changed (CONTENT);
 +}     
 +
 +int
 +Film::loop () const
 +{
 +      return _playlist->loop ();
 +}
 +
 +void
 +Film::set_loop (int c)
 +{
 +      _playlist->set_loop (c);
 +}
diff --combined src/lib/format.cc
index a83d53ebd1aa3ba01a486c488cb35d90b3645e67,8c3d0d8ad7f8f5eea541258032415c1b6cab12b7..f5026c0da8faf0ddf8267c11b0a32adcc163a9c8
@@@ -29,7 -29,6 +29,7 @@@
  #include <iostream>
  #include "format.h"
  #include "film.h"
 +#include "playlist.h"
  
  #include "i18n.h"
  
@@@ -60,13 -59,13 +60,6 @@@ FixedFormat::name () cons
        return s.str ();
  }
  
--/** @return Identifier for this format as metadata for a Film's metadata file */
--string
--Format::as_metadata () const
--{
--      return _id;
--}
--
  /** Fill our _formats vector with all available formats */
  void
  Format::setup_formats ()
@@@ -165,16 -164,16 +158,6 @@@ Format::from_id (string i
        return *j;
  }
  
--
--/** @param m Metadata, as returned from as_metadata().
-- *  @return Matching Format, or 0.
-- */
--Format const *
--Format::from_metadata (string m)
--{
--      return from_id (m);
--}
--
  /** @return All available formats */
  vector<Format const *>
  Format::all ()
@@@ -225,7 -224,7 +208,7 @@@ VariableFormat::VariableFormat (libdcp:
  float
  VariableFormat::ratio (shared_ptr<const Film> f) const
  {
 -      libdcp::Size const c = f->cropped_size (f->size ());
 +      libdcp::Size const c = f->cropped_size (f->video_size ());
        return float (c.width) / c.height;
  }
  
diff --combined src/lib/format.h
index f240c89fc85db4b76ee45f34291e8294dfdbd5b4,e953062329d9b583a0cfd078159499fb991320dc..d45a3a10acc4bc6f6c3d2ebf64be445d9b6c0f07
@@@ -41,7 -41,7 +41,7 @@@ public
        /** @return the ratio of the container (including any padding) */
        float container_ratio () const;
  
 -      int dcp_padding (boost::shared_ptr<const Film> f) const;
 +      int dcp_padding (boost::shared_ptr<const Film>) const;
  
        /** @return size in pixels of the images that we should
         *  put in a DCP for this ratio.  This size will not correspond
                return _dci_name;
        }
  
--      std::string as_metadata () const;
--
        static Format const * from_nickname (std::string n);
--      static Format const * from_metadata (std::string m);
        static Format const * from_id (std::string i);
        static std::vector<Format const *> all ();
        static void setup_formats ();
diff --combined src/lib/writer.cc
index 8e771a5e239e73044de2456310e754d042d0ff90,177e929aebf2e64c989f0d4146ace0e0c296f82a..b545848cbcc7d871d7d2b0aaa1e234d20c38cd80
  #include <libdcp/sound_asset.h>
  #include <libdcp/picture_frame.h>
  #include <libdcp/reel.h>
 +#include <libdcp/dcp.h>
+ #include <libdcp/cpl.h>
  #include "writer.h"
  #include "compose.hpp"
  #include "film.h"
  #include "format.h"
  #include "log.h"
  #include "dcp_video_frame.h"
 +#include "dcp_content_type.h"
 +#include "player.h"
 +#include "audio_mapping.h"
+ #include "config.h"
  
  #include "i18n.h"
  
@@@ -78,14 -76,16 +80,14 @@@ Writer::Writer (shared_ptr<Film> f
  
        _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
  
 -      AudioMapping m (_film->audio_channels ());
 -      
 -      if (m.dcp_channels() > 0) {
 +      if (_film->audio_channels() > 0) {
                _sound_asset.reset (
                        new libdcp::SoundAsset (
                                _film->dir (_film->dcp_name()),
                                _film->dcp_audio_mxf_filename (),
                                _film->dcp_frame_rate (),
 -                              m.dcp_channels (),
 -                              dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
 +                              _film->audio_mapping().dcp_channels (),
 +                              dcp_audio_sample_rate (_film->audio_frame_rate())
                                )
                        );
  
@@@ -322,7 -322,9 +324,9 @@@ Writer::finish (
                                                         )
                               ));
  
-       dcp.write_xml ();
+       libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+       meta.set_issue_date_now ();
+       dcp.write_xml (meta);
  
        _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));
  }
index 86c3cf4b14881a3268a96758d9716242af373362,0000000000000000000000000000000000000000..d4a4210dee560549db7868319b9fb28e8c6d8442
mode 100644,000000..100644
--- /dev/null
@@@ -1,217 -1,0 +1,204 @@@
- #include <libdcp/test_mode.h>
 +/*
 +    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.
 +
 +*/
 +
 +#include <iostream>
 +#include <iomanip>
 +#include <getopt.h>
-            << "  -t, --test         run in test mode (repeatable UUID generation, timestamps etc.)\n"
 +#include <libdcp/version.h>
 +#include "format.h"
 +#include "film.h"
 +#include "filter.h"
 +#include "transcode_job.h"
 +#include "job_manager.h"
 +#include "ab_transcode_job.h"
 +#include "util.h"
 +#include "scaler.h"
 +#include "version.h"
 +#include "cross.h"
 +#include "config.h"
 +#include "log.h"
 +
 +using std::string;
 +using std::cerr;
 +using std::cout;
 +using std::vector;
 +using std::pair;
 +using std::list;
 +using boost::shared_ptr;
 +
 +static void
 +help (string n)
 +{
 +      cerr << "Syntax: " << n << " [OPTION] <FILM>\n"
 +           << "  -v, --version      show DCP-o-matic version\n"
 +           << "  -h, --help         show this help\n"
 +           << "  -d, --deps         list DCP-o-matic dependency details and quit\n"
-       bool test_mode = false;
 +           << "  -n, --no-progress  do not print progress to stdout\n"
 +           << "  -r, --no-remote    do not use any remote servers\n"
 +           << "\n"
 +           << "<FILM> is the film directory.\n";
 +}
 +
 +int
 +main (int argc, char* argv[])
 +{
 +      string film_dir;
-                       { "test", no_argument, 0, 't'},
 +      bool progress = true;
 +      bool no_remote = false;
 +      int log_level = 0;
 +
 +      int option_index = 0;
 +      while (1) {
 +              static struct option long_options[] = {
 +                      { "version", no_argument, 0, 'v'},
 +                      { "help", no_argument, 0, 'h'},
 +                      { "deps", no_argument, 0, 'd'},
-               int c = getopt_long (argc, argv, "vhdtnrl:", long_options, &option_index);
 +                      { "no-progress", no_argument, 0, 'n'},
 +                      { "no-remote", no_argument, 0, 'r'},
 +                      { "log-level", required_argument, 0, 'l' },
 +                      { 0, 0, 0, 0 }
 +              };
 +
-               case 't':
-                       test_mode = true;
-                       break;
++              int c = getopt_long (argc, argv, "vhdnrl:", long_options, &option_index);
 +
 +              if (c == -1) {
 +                      break;
 +              }
 +
 +              switch (c) {
 +              case 'v':
 +                      cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
 +                      exit (EXIT_SUCCESS);
 +              case 'h':
 +                      help (argv[0]);
 +                      exit (EXIT_SUCCESS);
 +              case 'd':
 +                      cout << dependency_version_summary () << "\n";
 +                      exit (EXIT_SUCCESS);
-       if (test_mode) {
-               libdcp::enable_test_mode ();
-               cout << dependency_version_summary() << "\n";
-       }
 +              case 'n':
 +                      progress = false;
 +                      break;
 +              case 'r':
 +                      no_remote = true;
 +                      break;
 +              case 'l':
 +                      log_level = atoi (optarg);
 +                      break;
 +              }
 +      }
 +
 +      if (optind >= argc) {
 +              help (argv[0]);
 +              exit (EXIT_FAILURE);
 +      }
 +
 +      film_dir = argv[optind];
 +                      
 +      dcpomatic_setup ();
 +
 +      if (no_remote) {
 +              Config::instance()->set_servers (vector<ServerDescription*> ());
 +      }
 +
 +      cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
 +      char buf[256];
 +      if (gethostname (buf, 256) == 0) {
 +              cout << " on " << buf;
 +      }
 +      cout << "\n";
 +
-       cout << "Test mode: " << (test_mode ? "yes" : "no") << "\n";
 +      shared_ptr<Film> film;
 +      try {
 +              film.reset (new Film (film_dir, true));
 +      } catch (std::exception& e) {
 +              cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
 +              exit (EXIT_FAILURE);
 +      }
 +
 +      film->log()->set_level ((Log::Level) log_level);
 +
 +      cout << "\nMaking ";
 +      if (film->ab()) {
 +              cout << "A/B ";
 +      }
 +      cout << "DCP for " << film->name() << "\n";
 +//    cout << "Content: " << film->content() << "\n";
 +      pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
 +      cout << "Filters: " << f.first << " " << f.second << "\n";
 +
 +      film->make_dcp ();
 +
 +      bool should_stop = false;
 +      bool first = true;
 +      bool error = false;
 +      while (!should_stop) {
 +
 +              dcpomatic_sleep (5);
 +
 +              list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
 +
 +              if (!first && progress) {
 +                      cout << "\033[" << jobs.size() << "A";
 +                      cout.flush ();
 +              }
 +
 +              first = false;
 +
 +              int unfinished = 0;
 +              int finished_in_error = 0;
 +
 +              for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
 +                      if (progress) {
 +                              cout << (*i)->name() << ": ";
 +                              
 +                              float const p = (*i)->overall_progress ();
 +                              
 +                              if (p >= 0) {
 +                                      cout << (*i)->status() << "                         \n";
 +                              } else {
 +                                      cout << ": Running           \n";
 +                              }
 +                      }
 +
 +                      if (!(*i)->finished ()) {
 +                              ++unfinished;
 +                      }
 +
 +                      if ((*i)->finished_in_error ()) {
 +                              ++finished_in_error;
 +                              error = true;
 +                      }
 +
 +                      if (!progress && (*i)->finished_in_error ()) {
 +                              /* We won't see this error if we haven't been showing progress,
 +                                 so show it now.
 +                              */
 +                              cout << (*i)->status() << "\n";
 +                      }
 +              }
 +
 +              if (unfinished == 0 || finished_in_error != 0) {
 +                      should_stop = true;
 +              }
 +      }
 +
 +      return error ? EXIT_FAILURE : EXIT_SUCCESS;
 +}
 +
 +        
diff --combined src/wx/config_dialog.cc
index e1fc7a20fced8ed4caf66720f2c06a8a1e3f4acf,98657b666fd09f06e0a69514aa6fac3bc51dd516..50b8990b13a2afcd1c25319512cfc391e9a1275e
@@@ -31,6 -31,7 +31,7 @@@
  #include "lib/format.h"
  #include "lib/scaler.h"
  #include "lib/filter.h"
+ #include "lib/dcp_content_type.h"
  #include "config_dialog.h"
  #include "wx_util.h"
  #include "filter_dialog.h"
@@@ -52,6 -53,8 +53,8 @@@ ConfigDialog::ConfigDialog (wxWindow* p
        _notebook->AddPage (_misc_panel, _("Miscellaneous"), true);
        make_servers_panel ();
        _notebook->AddPage (_servers_panel, _("Encoding servers"), false);
+       make_metadata_panel ();
+       _notebook->AddPage (_metadata_panel, _("Metadata"), false);
        make_tms_panel ();
        _notebook->AddPage (_tms_panel, _("TMS"), false);
        make_ab_panel ();
@@@ -105,11 -108,6 +108,11 @@@ ConfigDialog::make_misc_panel (
        table->Add (_num_local_encoding_threads, 1, wxEXPAND);
        table->AddSpacer (0);
  
 +      add_label_to_sizer (table, _misc_panel, _("Default duration of still images"));
 +      _default_still_length = new wxSpinCtrl (_misc_panel);
 +      table->Add (_default_still_length, 1, wxEXPAND);
 +      add_label_to_sizer (table, _misc_panel, _("s"));
 +
        add_label_to_sizer (table, _misc_panel, _("Default directory for new films"));
  #ifdef __WXMSW__
        _default_directory = new DirPickerCtrl (_misc_panel);
        table->Add (_default_dci_metadata_button);
        table->AddSpacer (1);
  
+       add_label_to_sizer (table, _misc_panel, _("Default format"));
+       _default_format = new wxChoice (_misc_panel, wxID_ANY);
+       table->Add (_default_format);
+       table->AddSpacer (1);
+       add_label_to_sizer (table, _misc_panel, _("Default content type"));
+       _default_dcp_content_type = new wxChoice (_misc_panel, wxID_ANY);
+       table->Add (_default_dcp_content_type);
+       table->AddSpacer (1);
+       
        Config* config = Config::instance ();
  
        _set_language->SetValue (config->language ());
        _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
        _num_local_encoding_threads->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::num_local_encoding_threads_changed), 0, this);
  
 +      _default_still_length->SetRange (1, 3600);
 +      _default_still_length->SetValue (config->default_still_length ());
 +      _default_still_length->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (ConfigDialog::default_still_length_changed), 0, this);
 +
        _default_directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir()))));
        _default_directory->Connect (wxID_ANY, wxEVT_COMMAND_DIRPICKER_CHANGED, wxCommandEventHandler (ConfigDialog::default_directory_changed), 0, this);
  
        _default_dci_metadata_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_default_dci_metadata_clicked), 0, this);
  
+       vector<Format const *> fmt = Format::all ();
+       int n = 0;
+       for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
+               _default_format->Append (std_to_wx ((*i)->name ()));
+               if (*i == config->default_format ()) {
+                       _default_format->SetSelection (n);
+               }
+               ++n;
+       }
+       _default_format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_format_changed), 0, this);
+       
+       vector<DCPContentType const *> const ct = DCPContentType::all ();
+       n = 0;
+       for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
+               _default_dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
+               if (*i == config->default_dcp_content_type ()) {
+                       _default_dcp_content_type->SetSelection (n);
+               }
+               ++n;
+       }
+       _default_dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::default_dcp_content_type_changed), 0, this);
  }
  
  void
@@@ -199,6 -226,33 +235,6 @@@ ConfigDialog::make_tms_panel (
        _tms_password->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::tms_password_changed), 0, this);
  }
  
 -void
 -ConfigDialog::make_metadata_panel ()
 -{
 -      _metadata_panel = new wxPanel (_notebook);
 -      wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
 -      _metadata_panel->SetSizer (s);
 -
 -      wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
 -      table->AddGrowableCol (1, 1);
 -      s->Add (table, 1, wxALL | wxEXPAND, 8);
 -
 -      add_label_to_sizer (table, _metadata_panel, _("Issuer"));
 -      _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
 -      table->Add (_issuer, 1, wxEXPAND);
 -
 -      add_label_to_sizer (table, _metadata_panel, _("Creator"));
 -      _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
 -      table->Add (_creator, 1, wxEXPAND);
 -
 -      Config* config = Config::instance ();
 -
 -      _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
 -      _issuer->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::issuer_changed), 0, this);
 -      _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
 -      _creator->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::creator_changed), 0, this);
 -}
 -
  void
  ConfigDialog::make_ab_panel ()
  {
                add_label_to_sizer (table, _ab_panel, _("Reference filters"));
                wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _reference_filters = new wxStaticText (_ab_panel, wxID_ANY, wxT (""));
 -              s->Add (_reference_filters, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 6);
 +              s->Add (_reference_filters, 1, wxEXPAND);
                _reference_filters_button = new wxButton (_ab_panel, wxID_ANY, _("Edit..."));
                s->Add (_reference_filters_button, 0);
                table->Add (s, 1, wxEXPAND);
                table->AddSpacer (0);
        }
++}
++
++void
++ConfigDialog::make_metadata_panel ()
++{
++      _metadata_panel = new wxPanel (_notebook);
++      wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
++      _metadata_panel->SetSizer (s);
++
++      wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
++      table->AddGrowableCol (1, 1);
++      s->Add (table, 1, wxALL | wxEXPAND, 8);
++
++      add_label_to_sizer (table, _metadata_panel, _("Issuer"));
++      _issuer = new wxTextCtrl (_metadata_panel, wxID_ANY);
++      table->Add (_issuer, 1, wxEXPAND);
++
++      add_label_to_sizer (table, _metadata_panel, _("Creator"));
++      _creator = new wxTextCtrl (_metadata_panel, wxID_ANY);
++      table->Add (_creator, 1, wxEXPAND);
  
        Config* config = Config::instance ();
--      
--      _reference_scaler->SetSelection (Scaler::as_index (config->reference_scaler ()));
--      _reference_scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (ConfigDialog::reference_scaler_changed), 0, this);
  
--      pair<string, string> p = Filter::ffmpeg_strings (config->reference_filters ());
--      _reference_filters->SetLabel (std_to_wx (p.first) + N_(" ") + std_to_wx (p.second));
--      _reference_filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (ConfigDialog::edit_reference_filters_clicked), 0, this);
++      _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
++      _issuer->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::issuer_changed), 0, this);
++      _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
++      _creator->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ConfigDialog::creator_changed), 0, this);
  }
  
  void
@@@ -472,8 -526,32 +526,38 @@@ ConfigDialog::setup_language_sensitivit
        _language->Enable (_set_language->GetValue ());
  }
  
 +void
 +ConfigDialog::default_still_length_changed (wxCommandEvent &)
 +{
 +      Config::instance()->set_default_still_length (_default_still_length->GetValue ());
 +}
++
+ void
+ ConfigDialog::default_format_changed (wxCommandEvent &)
+ {
+       vector<Format const *> fmt = Format::all ();
+       Config::instance()->set_default_format (fmt[_default_format->GetSelection()]);
+ }
+ void
+ ConfigDialog::default_dcp_content_type_changed (wxCommandEvent &)
+ {
+       vector<DCPContentType const *> ct = DCPContentType::all ();
+       Config::instance()->set_default_dcp_content_type (ct[_default_dcp_content_type->GetSelection()]);
+ }
+ void
+ ConfigDialog::issuer_changed (wxCommandEvent &)
+ {
+       libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+       m.issuer = wx_to_std (_issuer->GetValue ());
+       Config::instance()->set_dcp_metadata (m);
+ }
+ void
+ ConfigDialog::creator_changed (wxCommandEvent &)
+ {
+       libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+       m.creator = wx_to_std (_creator->GetValue ());
+       Config::instance()->set_dcp_metadata (m);
+ }
diff --combined src/wx/config_dialog.h
index 852925e1da1ca0bb45e92f842c18b5e9193e9e47,526480912abade4c92ef56030b65f07bf928cd59..5df1a634fd25041eb202404b28e23d7a9981c102
@@@ -47,7 -47,6 +47,7 @@@ private
        void tms_user_changed (wxCommandEvent &);
        void tms_password_changed (wxCommandEvent &);
        void num_local_encoding_threads_changed (wxCommandEvent &);
 +      void default_still_length_changed (wxCommandEvent &);
        void default_directory_changed (wxCommandEvent &);
        void edit_default_dci_metadata_clicked (wxCommandEvent &);
        void reference_scaler_changed (wxCommandEvent &);
        void edit_server_clicked (wxCommandEvent &);
        void remove_server_clicked (wxCommandEvent &);
        void server_selection_changed (wxListEvent &);
+       void default_format_changed (wxCommandEvent &);
+       void default_dcp_content_type_changed (wxCommandEvent &);
+       void issuer_changed (wxCommandEvent &);
+       void creator_changed (wxCommandEvent &);
  
        void add_server_to_control (ServerDescription *);
        void setup_language_sensitivity ();
  
        void make_misc_panel ();
        void make_tms_panel ();
+       void make_metadata_panel ();
        void make_ab_panel ();
        void make_servers_panel ();
  
        wxPanel* _tms_panel;
        wxPanel* _ab_panel;
        wxPanel* _servers_panel;
+       wxPanel* _metadata_panel;
        wxCheckBox* _set_language;
        wxChoice* _language;
+       wxChoice* _default_format;
+       wxChoice* _default_dcp_content_type;
        wxTextCtrl* _tms_ip;
        wxTextCtrl* _tms_path;
        wxTextCtrl* _tms_user;
        wxTextCtrl* _tms_password;
        wxSpinCtrl* _num_local_encoding_threads;
 +      wxSpinCtrl* _default_still_length;
  #ifdef __WXMSW__      
        DirPickerCtrl* _default_directory;
  #else
        wxButton* _add_server;
        wxButton* _edit_server;
        wxButton* _remove_server;
+       wxTextCtrl* _issuer;
+       wxTextCtrl* _creator;
  };