Add option to analyse audio automatically when content is added (#673).
[dcpomatic.git] / src / lib / film.cc
index 2ef59fe216e04aa89fb378f7d849948a8375174b..231ac92e19daf13affba40b10a2d66650c323e19 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 <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);
@@ -167,8 +162,12 @@ 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 ();
        }
 }
 
@@ -227,29 +226,13 @@ 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 () const
+Film::audio_analysis_path (shared_ptr<const Playlist> playlist) const
 {
        boost::filesystem::path p = dir ("analysis");
 
        MD5Digester digester;
-       BOOST_FOREACH (shared_ptr<Content> i, content ()) {
+       BOOST_FOREACH (shared_ptr<Content> i, playlist->content ()) {
                shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (i);
                if (!ac) {
                        continue;
@@ -257,7 +240,15 @@ Film::audio_analysis_path () const
 
                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 ()) {
@@ -276,28 +267,19 @@ Film::make_dcp ()
                throw BadSettingError (_("name"), _("cannot contain slashes"));
        }
 
-       bool must_burn = false;
-       ContentList cl = content ();
-       BOOST_FOREACH (shared_ptr<Content> c, cl) {
-               shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c);
-               if (sc && sc->has_image_subtitles() && sc->use_subtitles() && !sc->burn_subtitles ()) {
-                       must_burn = true;
-               }
-       }
-
-       if (must_burn) {
-               throw EncodeError (_("this project has content with image-based subtitles, which this version of DCP-o-matic cannot include as separate DCP subtitles.  To use these subtitles you must burn them into the image (tick the box in the Subtitles tab)."));
-       }
-
        set_isdcf_date_today ();
 
        environment_info (log ());
 
-       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) {
@@ -323,7 +305,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);
 }
 
@@ -494,7 +476,7 @@ 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;
 
@@ -612,7 +594,9 @@ 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;
                }
        }
@@ -800,6 +784,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
@@ -940,14 +929,14 @@ 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);
@@ -962,8 +951,19 @@ Film::maybe_add_content (weak_ptr<Job> j, weak_ptr<Content> c)
        }
 
        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);
        }
 }
 
@@ -1015,8 +1015,10 @@ Film::active_frame_rate_change (DCPTime t) const
 }
 
 void
-Film::playlist_content_changed (boost::weak_ptr<Content> c, int p, bool frequent)
+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) {
@@ -1036,7 +1038,13 @@ Film::playlist_changed ()
 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;
 }
 
@@ -1080,7 +1088,7 @@ 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 ();
        }
@@ -1240,3 +1248,9 @@ Film::remove_content (ContentList c)
 {
        _playlist->remove (c);
 }
+
+void
+Film::audio_analysis_finished ()
+{
+       /* XXX */
+}