Logging improvements to allow prettier displays in the server GUI.
[dcpomatic.git] / src / lib / film.cc
index 66d651c27e06825c94425a61c55883e68da2fec0..a50aad81758afc218dab6e38bfed68e431ae909a 100644 (file)
 #include "util.h"
 #include "job_manager.h"
 #include "transcode_job.h"
-#include "scp_dcp_job.h"
-#include "log.h"
+#include "upload_job.h"
+#include "null_log.h"
+#include "file_log.h"
 #include "exceptions.h"
 #include "examine_content_job.h"
 #include "config.h"
 #include "playlist.h"
-#include "player.h"
 #include "dcp_content_type.h"
 #include "ratio.h"
 #include "cross.h"
-#include "cinema.h"
 #include "safe_stringstream.h"
 #include "environment_info.h"
 #include "raw_convert.h"
 #include "audio_processor.h"
 #include "md5_digester.h"
+#include "compose.hpp"
+#include "screen.h"
+#include "audio_content.h"
+#include "video_content.h"
+#include "subtitle_content.h"
+#include "ffmpeg_content.h"
+#include "dcp_content.h"
+#include "screen_kdm.h"
 #include <libcxml/cxml.h>
 #include <dcp/cpl.h>
-#include <dcp/signer.h>
+#include <dcp/certificate_chain.h>
 #include <dcp/util.h>
 #include <dcp/local_time.h>
 #include <dcp/decrypted_kdm.h>
 #include <libxml++/libxml++.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
-#include <boost/lexical_cast.hpp>
 #include <boost/foreach.hpp>
 #include <unistd.h>
 #include <stdexcept>
 #include <iostream>
 #include <algorithm>
-#include <fstream>
 #include <cstdlib>
 #include <iomanip>
 #include <set>
 #include "i18n.h"
 
 using std::string;
-using std::multimap;
 using std::pair;
-using std::map;
 using std::vector;
 using std::setfill;
 using std::min;
 using std::max;
 using std::make_pair;
-using std::endl;
 using std::cout;
 using std::list;
 using std::set;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
-using boost::to_upper_copy;
-using boost::ends_with;
-using boost::starts_with;
 using boost::optional;
 using boost::is_any_of;
-using dcp::Size;
-using dcp::Signer;
 
-#define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
-#define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, Log::TYPE_GENERAL);
+#define LOG_GENERAL(...) log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
+#define LOG_GENERAL_NC(...) log()->log (__VA_ARGS__, LogEntry::TYPE_GENERAL);
 
 /* 5 -> 6
  * AudioMapping XML changed.
@@ -127,8 +124,7 @@ Film::Film (boost::filesystem::path dir, bool log)
        , _audio_channels (6)
        , _three_d (false)
        , _sequence_video (true)
-       , _interop (false)
-       , _burn_subtitles (false)
+       , _interop (Config::instance()->default_interop ())
        , _audio_processor (0)
        , _state_version (current_state_version)
        , _dirty (false)
@@ -136,12 +132,12 @@ Film::Film (boost::filesystem::path dir, bool log)
        set_isdcf_date_today ();
 
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Film::playlist_changed, this));
-       _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
-       
+       _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2, _3));
+
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
        */
-       
+
        boost::filesystem::path p (boost::filesystem::system_complete (dir));
        boost::filesystem::path result;
        for (boost::filesystem::path::iterator i = p.begin(); i != p.end(); ++i) {
@@ -168,10 +164,14 @@ Film::Film (boost::filesystem::path dir, bool log)
 
 Film::~Film ()
 {
-       for (list<boost::signals2::connection>::const_iterator i = _job_connections.begin(); i != _job_connections.end(); ++i) {
-               i->disconnect ();
+       BOOST_FOREACH (boost::signals2::connection& i, _job_connections) {
+               i.disconnect ();
        }
-}      
+
+       BOOST_FOREACH (boost::signals2::connection& i, _audio_analysis_connections) {
+               i.disconnect ();
+       }
+}
 
 string
 Film::video_identifier () const
@@ -180,7 +180,7 @@ Film::video_identifier () const
 
        SafeStringStream s;
        s.imbue (std::locale::classic ());
-       
+
        s << container()->id()
          << "_" << resolution_to_string (_resolution)
          << "_" << _playlist->video_identifier()
@@ -199,17 +199,13 @@ Film::video_identifier () const
                s << "_S";
        }
 
-       if (_burn_subtitles) {
-               s << "_B";
-       }
-
        if (_three_d) {
                s << "_3D";
        }
 
        return s.str ();
 }
-         
+
 /** @return The file to write video frame info to */
 boost::filesystem::path
 Film::info_file () const
@@ -232,22 +228,6 @@ Film::internal_video_asset_filename () const
        return video_identifier() + ".mxf";
 }
 
-string
-Film::filename_safe_name () const
-{
-       string const n = name ();
-       string o;
-       for (size_t i = 0; i < n.length(); ++i) {
-               if (isalnum (n[i])) {
-                       o += n[i];
-               } else {
-                       o += "_";
-               }
-       }
-
-       return o;
-}
-
 boost::filesystem::path
 Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
 {
@@ -259,10 +239,18 @@ Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
                if (!ac) {
                        continue;
                }
-               
+
                digester.add (ac->digest ());
                digester.add (ac->audio_mapping().digest ());
-               digester.add (ac->audio_gain ());
+               if (playlist->content().size() != 1) {
+                       /* Analyses should be considered equal regardless of gain
+                          if they were made from just one piece of content.  This
+                          is because we can fake any gain change in a single-content
+                          analysis at the plotting stage rather than having to
+                          recompute it.
+                       */
+                       digester.add (ac->audio_gain ());
+               }
        }
 
        if (audio_processor ()) {
@@ -277,22 +265,25 @@ Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
 void
 Film::make_dcp ()
 {
-       set_isdcf_date_today ();
-       
        if (dcp_name().find ("/") != string::npos) {
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
 
+       set_isdcf_date_today ();
+
        environment_info (log ());
 
-       ContentList cl = content ();
-       for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
-               LOG_GENERAL ("Content: %1", (*i)->technical_summary());
+       BOOST_FOREACH (shared_ptr<const Content> i, content ()) {
+               LOG_GENERAL ("Content: %1", i->technical_summary());
        }
        LOG_GENERAL ("DCP video rate %1 fps", video_frame_rate());
-       LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads());
+       if (Config::instance()->only_servers_encode ()) {
+               LOG_GENERAL_NC ("0 threads: ONLY SERVERS SET TO ENCODE");
+       } else {
+               LOG_GENERAL ("%1 threads", Config::instance()->num_local_encoding_threads());
+       }
        LOG_GENERAL ("J2K bandwidth %1", j2k_bandwidth());
-       
+
        if (container() == 0) {
                throw MissingSettingError (_("container"));
        }
@@ -316,7 +307,7 @@ Film::make_dcp ()
 void
 Film::send_dcp_to_tms ()
 {
-       shared_ptr<Job> j (new SCPDCPJob (shared_from_this()));
+       shared_ptr<Job> j (new UploadJob (shared_from_this()));
        JobManager::instance()->add (j);
 }
 
@@ -347,7 +338,6 @@ Film::metadata () const
        root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0");
        root->add_child("SequenceVideo")->add_child_text (_sequence_video ? "1" : "0");
        root->add_child("Interop")->add_child_text (_interop ? "1" : "0");
-       root->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0");
        root->add_child("Signed")->add_child_text (_signed ? "1" : "0");
        root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
        root->add_child("Key")->add_child_text (_key.hex ());
@@ -386,7 +376,7 @@ Film::read_metadata ()
        if (_state_version > current_state_version) {
                throw StringError (_("This film was created with a newer version of DCP-o-matic, and it cannot be loaded into this version.  Sorry!"));
        }
-       
+
        _name = f.string_child ("Name");
        if (_state_version >= 9) {
                _use_isdcf_name = f.bool_child ("UseISDCFName");
@@ -429,9 +419,6 @@ Film::read_metadata ()
        _sequence_video = f.bool_child ("SequenceVideo");
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
-       if (_state_version >= 32) {
-               _burn_subtitles = f.bool_child ("BurnSubtitles");
-       }
        _key = dcp::Key (f.string_child ("Key"));
 
        if (f.optional_string_child ("AudioProcessor")) {
@@ -460,9 +447,9 @@ Film::dir (boost::filesystem::path d) const
        boost::filesystem::path p;
        p /= _directory;
        p /= d;
-       
+
        boost::filesystem::create_directories (p);
-       
+
        return p;
 }
 
@@ -477,7 +464,7 @@ Film::file (boost::filesystem::path f) const
        p /= f;
 
        boost::filesystem::create_directories (p.parent_path ());
-       
+
        return p;
 }
 
@@ -491,10 +478,10 @@ Film::isdcf_name (bool if_created_now) const
 
        /* Split the raw name up into words */
        vector<string> words;
-       split (words, raw_name, is_any_of (" "));
+       split (words, raw_name, is_any_of (" _-"));
 
        string fixed_name;
-       
+
        /* Add each word to fixed_name */
        for (vector<string>::const_iterator i = words.begin(); i != words.end(); ++i) {
                string w = *i;
@@ -509,7 +496,7 @@ Film::isdcf_name (bool if_created_now) const
                                ++caps;
                        }
                }
-               
+
                /* If w is all caps make the rest of it lower case, otherwise
                   leave it alone.
                */
@@ -540,15 +527,15 @@ Film::isdcf_name (bool if_created_now) const
        if (dm.temp_version) {
                d << "-Temp";
        }
-       
+
        if (dm.pre_release) {
                d << "-Pre";
        }
-       
+
        if (dm.red_band) {
                d << "-RedBand";
        }
-       
+
        if (!dm.chain.empty ()) {
                d << "-" << dm.chain;
        }
@@ -568,20 +555,18 @@ Film::isdcf_name (bool if_created_now) const
        if (video_frame_rate() != 24) {
                d << "-" << video_frame_rate();
        }
-       
+
        if (container()) {
                d << "_" << container()->isdcf_name();
        }
 
-       ContentList cl = content ();
-       
        /* XXX: this uses the first bit of content only */
 
        /* The standard says we don't do this for trailers, for some strange reason */
        if (dcp_content_type() && dcp_content_type()->libdcp_kind() != dcp::TRAILER) {
                Ratio const * content_ratio = 0;
-               for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) {
-                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
+               BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (i);
                        if (vc) {
                                /* Here's the first piece of video content */
                                if (vc->scale().ratio ()) {
@@ -592,7 +577,7 @@ Film::isdcf_name (bool if_created_now) const
                                break;
                        }
                }
-               
+
                if (content_ratio && content_ratio != container()) {
                        d << "-" << content_ratio->isdcf_name();
                }
@@ -609,39 +594,53 @@ Film::isdcf_name (bool if_created_now) const
 
        if (!dm.territory.empty ()) {
                d << "_" << dm.territory;
-               if (!dm.rating.empty ()) {
+               if (dm.rating.empty ()) {
+                       d << "-NR";
+               } else {
                        d << "-" << dm.rating;
                }
        }
 
        /* Find all mapped channels */
 
-       list<int> mapped;
-       for (ContentList::const_iterator i = cl.begin(); i != cl.end(); ++i) {
-               shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (*i);
-               if (ac) {
-                       list<int> c = ac->audio_mapping().mapped_output_channels ();
-                       copy (c.begin(), c.end(), back_inserter (mapped));
-               }
-       }
-
-       mapped.sort ();
-       mapped.unique ();
-       
-       /* Count them */
-                       
        int non_lfe = 0;
        int lfe = 0;
-       for (list<int>::const_iterator i = mapped.begin(); i != mapped.end(); ++i) {
-               if (*i >= audio_channels()) {
-                       /* This channel is mapped but is not included in the DCP */
-                       continue;
-               }
-               
-               if (static_cast<dcp::Channel> (*i) == dcp::LFE) {
+
+       if (audio_processor ()) {
+               /* Processors are mapped 1:1 to DCP outputs so we can guess the number of LFE/
+                  non-LFE from the channel counts.
+               */
+               non_lfe = audio_processor()->out_channels ();
+               if (non_lfe >= 4) {
+                       --non_lfe;
                        ++lfe;
-               } else {
-                       ++non_lfe;
+               }
+       } else {
+               list<int> mapped;
+               BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+                       shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (i);
+                       if (ac) {
+                               list<int> c = ac->audio_mapping().mapped_output_channels ();
+                               copy (c.begin(), c.end(), back_inserter (mapped));
+                       }
+               }
+
+               mapped.sort ();
+               mapped.unique ();
+
+               /* Count them */
+
+               for (list<int>::const_iterator i = mapped.begin(); i != mapped.end(); ++i) {
+                       if (*i >= audio_channels()) {
+                               /* This channel is mapped but is not included in the DCP */
+                               continue;
+                       }
+
+                       if (static_cast<dcp::Channel> (*i) == dcp::LFE) {
+                               ++lfe;
+                       } else {
+                               ++non_lfe;
+                       }
                }
        }
 
@@ -652,7 +651,7 @@ Film::isdcf_name (bool if_created_now) const
        /* XXX: HI/VI */
 
        d << "_" << resolution_to_string (_resolution);
-       
+
        if (!dm.studio.empty ()) {
                d << "_" << dm.studio;
        }
@@ -672,13 +671,23 @@ Film::isdcf_name (bool if_created_now) const
        } else {
                d << "_SMPTE";
        }
-       
+
        if (three_d ()) {
                d << "-3D";
        }
 
-       if (!dm.package_type.empty ()) {
-               d << "_" << dm.package_type;
+       bool vf = false;
+       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+               shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
+               if (dc && (dc->reference_video() || dc->reference_audio() || dc->reference_subtitle())) {
+                       vf = true;
+               }
+       }
+
+       if (vf) {
+               d << "_VF";
+       } else {
+               d << "_OV";
        }
 
        return d.str ();
@@ -706,7 +715,7 @@ Film::dcp_name (bool if_created_now) const
                        filtered += unfiltered[i];
                }
        }
-       
+
        return filtered;
 }
 
@@ -785,6 +794,11 @@ Film::set_three_d (bool t)
 {
        _three_d = t;
        signal_changed (THREE_D);
+
+       if (_three_d && _isdcf_metadata.two_d_version_of_three_d) {
+               _isdcf_metadata.two_d_version_of_three_d = false;
+               signal_changed (ISDCF_METADATA);
+       }
 }
 
 void
@@ -794,13 +808,6 @@ Film::set_interop (bool i)
        signal_changed (INTEROP);
 }
 
-void
-Film::set_burn_subtitles (bool b)
-{
-       _burn_subtitles = b;
-       signal_changed (BURN_SUBTITLES);
-}
-
 void
 Film::set_audio_processor (AudioProcessor const * processor)
 {
@@ -851,7 +858,7 @@ Film::j2c_path (int f, Eyes e, bool t) const
        } else if (e == EYES_RIGHT) {
                s << ".R";
        }
-       
+
        s << ".j2c";
 
        if (t) {
@@ -867,7 +874,7 @@ vector<CPLSummary>
 Film::cpls () const
 {
        vector<CPLSummary> out;
-       
+
        boost::filesystem::path const dir = directory ();
        for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); ++i) {
                if (
@@ -891,14 +898,8 @@ Film::cpls () const
                        }
                }
        }
-       
-       return out;
-}
 
-shared_ptr<Player>
-Film::make_player () const
-{
-       return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
+       return out;
 }
 
 void
@@ -922,12 +923,6 @@ Film::set_key (dcp::Key key)
        signal_changed (KEY);
 }
 
-shared_ptr<Playlist>
-Film::playlist () const
-{
-       return _playlist;
-}
-
 ContentList
 Film::content () const
 {
@@ -944,16 +939,16 @@ Film::examine_content (shared_ptr<Content> c)
 void
 Film::examine_and_add_content (shared_ptr<Content> c)
 {
-       if (dynamic_pointer_cast<FFmpegContent> (c)) {
+       if (dynamic_pointer_cast<FFmpegContent> (c) && !_directory.empty ()) {
                run_ffprobe (c->path(0), file ("ffprobe.log"), _log);
        }
-                       
+
        shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c));
 
        _job_connections.push_back (
-               j->Finished.connect (bind (&Film::maybe_add_content, this, boost::weak_ptr<Job> (j), boost::weak_ptr<Content> (c)))
+               j->Finished.connect (bind (&Film::maybe_add_content, this, weak_ptr<Job> (j), weak_ptr<Content> (c)))
                );
-       
+
        JobManager::instance()->add (j);
 }
 
@@ -964,10 +959,21 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
        if (!job || !job->finished_ok ()) {
                return;
        }
-       
+
        shared_ptr<Content> content = c.lock ();
-       if (content) {
-               add_content (content);
+       if (!content) {
+               return;
+       }
+
+       add_content (content);
+       if (Config::instance()->automatic_audio_analysis ()) {
+               shared_ptr<Playlist> playlist (new Playlist);
+               playlist->add (content);
+               boost::signals2::connection c;
+               JobManager::instance()->analyse_audio (
+                       shared_from_this (), playlist, c, bind (&Film::audio_analysis_finished, this)
+                       );
+               _audio_analysis_connections.push_back (c);
        }
 }
 
@@ -1000,6 +1006,7 @@ Film::move_content_later (shared_ptr<Content> c)
        _playlist->move_later (c);
 }
 
+/** @return length of the film from time 0 to the last thing on the playlist */
 DCPTime
 Film::length () const
 {
@@ -1019,15 +1026,17 @@ Film::active_frame_rate_change (DCPTime t) const
 }
 
 void
-Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
+Film::playlist_content_changed (weak_ptr<Content> c, int p, bool frequent)
 {
+       _dirty = true;
+
        if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
                set_video_frame_rate (_playlist->best_dcp_frame_rate ());
        } else if (p == AudioContentProperty::AUDIO_STREAMS) {
                signal_changed (NAME);
        }
 
-       emit (boost::bind (boost::ref (ContentChanged), c, p));
+       emit (boost::bind (boost::ref (ContentChanged), c, p, frequent));
 }
 
 void
@@ -1035,12 +1044,18 @@ Film::playlist_changed ()
 {
        signal_changed (CONTENT);
        signal_changed (NAME);
-}      
+}
 
 int
 Film::audio_frame_rate () const
 {
-       /* XXX */
+       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+               shared_ptr<AudioContent> a = dynamic_pointer_cast<AudioContent> (i);
+               if (a && a->has_rate_above_48k ()) {
+                       return 96000;
+               }
+       }
+
        return 48000;
 }
 
@@ -1084,17 +1099,17 @@ Film::make_kdm (
        ) const
 {
        shared_ptr<const dcp::CPL> cpl (new dcp::CPL (cpl_file));
-       shared_ptr<const dcp::Signer> signer = Config::instance()->signer();
+       shared_ptr<const dcp::CertificateChain> signer = Config::instance()->signer_chain ();
        if (!signer->valid ()) {
                throw InvalidSignerError ();
        }
-       
+
        return dcp::DecryptedKDM (
                cpl, key(), from, until, "DCP-o-matic", cpl->content_title_text(), dcp::LocalTime().as_string()
                ).encrypt (signer, target, formulation);
 }
 
-list<dcp::EncryptedKDM>
+list<ScreenKDM>
 Film::make_kdms (
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
@@ -1103,11 +1118,11 @@ Film::make_kdms (
        dcp::Formulation formulation
        ) const
 {
-       list<dcp::EncryptedKDM> kdms;
+       list<ScreenKDM> kdms;
 
-       for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
-               if ((*i)->certificate) {
-                       kdms.push_back (make_kdm ((*i)->certificate.get(), dcp, from, until, formulation));
+       BOOST_FOREACH (shared_ptr<Screen> i, screens) {
+               if (i->certificate) {
+                       kdms.push_back (ScreenKDM (i, make_kdm (i->certificate.get(), dcp, from, until, formulation)));
                }
        }
 
@@ -1162,7 +1177,7 @@ string
 Film::subtitle_language () const
 {
        set<string> languages;
-       
+
        ContentList cl = content ();
        BOOST_FOREACH (shared_ptr<Content>& c, cl) {
                shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c);
@@ -1215,7 +1230,7 @@ Film::audio_output_names () const
        if (audio_processor ()) {
                return audio_processor()->input_names ();
        }
-       
+
        vector<string> n;
        n.push_back (_("L"));
        n.push_back (_("R"));
@@ -1232,3 +1247,21 @@ Film::audio_output_names () const
 
        return vector<string> (n.begin(), n.begin() + audio_channels ());
 }
+
+void
+Film::repeat_content (ContentList c, int n)
+{
+       _playlist->repeat (c, n);
+}
+
+void
+Film::remove_content (ContentList c)
+{
+       _playlist->remove (c);
+}
+
+void
+Film::audio_analysis_finished ()
+{
+       /* XXX */
+}