Try to tidy up frame indexing; use DCP length obtained from the transcode to make...
[dcpomatic.git] / src / lib / film.cc
index f0441c9e0b038733b86a7797cc32b3706cc5a028..1741d49e691269e79abf7079cdfd11a67fc9a4ec 100644 (file)
@@ -72,8 +72,9 @@ using boost::to_upper_copy;
 using boost::ends_with;
 using boost::starts_with;
 using boost::optional;
+using libdcp::Size;
 
-int const Film::state_version = 1;
+int const Film::state_version = 2;
 
 /** Construct a Film object in a given directory, reading any metadata
  *  file that exists in that directory.  An exception will be thrown if
@@ -89,8 +90,8 @@ Film::Film (string d, bool must_exist)
        , _dcp_content_type (0)
        , _format (0)
        , _scaler (Scaler::from_id ("bicubic"))
-       , _dcp_trim_start (0)
-       , _dcp_trim_end (0)
+       , _trim_start (0)
+       , _trim_end (0)
        , _dcp_ab (false)
        , _use_content_audio (true)
        , _audio_gain (0)
@@ -155,8 +156,8 @@ Film::Film (Film const & o)
        , _crop              (o._crop)
        , _filters           (o._filters)
        , _scaler            (o._scaler)
-       , _dcp_trim_start    (o._dcp_trim_start)
-       , _dcp_trim_end      (o._dcp_trim_end)
+       , _trim_start        (o._trim_start)
+       , _trim_end          (o._trim_end)
        , _reel_size         (o._reel_size)
        , _dcp_ab            (o._dcp_ab)
        , _content_audio_stream (o._content_audio_stream)
@@ -180,6 +181,7 @@ Film::Film (Film const & o)
        , _package_type      (o._package_type)
        , _size              (o._size)
        , _length            (o._length)
+       , _dcp_intrinsic_duration (o._dcp_intrinsic_duration)
        , _content_digest    (o._content_digest)
        , _content_audio_streams (o._content_audio_streams)
        , _external_audio_stream (o._external_audio_stream)
@@ -255,7 +257,9 @@ Film::make_dcp (bool transcode)
        }
        
        log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video")));
-       log()->log (String::compose ("Content length %1", length().get()));
+       if (length()) {
+               log()->log (String::compose ("Content length %1", length().get()));
+       }
        log()->log (String::compose ("Content digest %1", content_digest()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
@@ -288,47 +292,21 @@ Film::make_dcp (bool transcode)
                throw MissingSettingError ("name");
        }
 
-       shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
-       oe->out_size = format()->dcp_size ();
-       oe->padding = format()->dcp_padding (shared_from_this ());
-       if (dcp_length ()) {
-               oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
-               if (audio_stream()) {
-                       oe->audio_range = make_pair (
-
-                               video_frames_to_audio_frames (
-                                       oe->video_range.get().first,
-                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
-                                       dcp_frame_rate (frames_per_second()).frames_per_second
-                                       ),
-                               
-                               video_frames_to_audio_frames (
-                                       oe->video_range.get().second,
-                                       dcp_audio_sample_rate (audio_stream()->sample_rate()),
-                                       dcp_frame_rate (frames_per_second()).frames_per_second
-                                       )
-                               );
-               }
-                       
-       }
-       
-       oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
-
-       shared_ptr<DecodeOptions> od (new DecodeOptions);
-       od->decode_subtitles = with_subtitles ();
+       DecodeOptions od;
+       od.decode_subtitles = with_subtitles ();
 
        shared_ptr<Job> r;
 
        if (transcode) {
                if (dcp_ab()) {
-                       r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
+                       r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
                } else {
-                       r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
+                       r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
                }
        }
 
-       r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
-       JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
+       r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, r)));
+       JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), r)));
 }
 
 /** Start a job to examine our content file */
@@ -412,8 +390,8 @@ Film::write_metadata () const
                f << "filter " << (*i)->id () << "\n";
        }
        f << "scaler " << _scaler->id () << "\n";
-       f << "dcp_trim_start " << _dcp_trim_start << "\n";
-       f << "dcp_trim_end " << _dcp_trim_end << "\n";
+       f << "trim_start " << _trim_start << "\n";
+       f << "trim_end " << _trim_end << "\n";
        if (_reel_size) {
                f << "reel_size " << _reel_size.get() << "\n";
        }
@@ -447,6 +425,7 @@ Film::write_metadata () const
        f << "width " << _size.width << "\n";
        f << "height " << _size.height << "\n";
        f << "length " << _length.get_value_or(0) << "\n";
+       f << "dcp_intrinsic_duration " << _dcp_intrinsic_duration.get_value_or(0) << "\n";
        f << "content_digest " << _content_digest << "\n";
 
        for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
@@ -527,10 +506,10 @@ Film::read_metadata ()
                        _filters.push_back (Filter::from_id (v));
                } else if (k == "scaler") {
                        _scaler = Scaler::from_id (v);
-               } else if (k == "dcp_trim_start") {
-                       _dcp_trim_start = atoi (v.c_str ());
-               } else if (k == "dcp_trim_end") {
-                       _dcp_trim_end = atoi (v.c_str ());
+               } else if ( ((!version || version < 2) && k == "trim_start") || k == "trim_start") {
+                       _trim_start = atoi (v.c_str ());
+               } else if ( ((!version || version < 2) && k == "trim_end") || k == "trim_end") {
+                       _trim_end = atoi (v.c_str ());
                } else if (k == "reel_size") {
                        _reel_size = boost::lexical_cast<uint64_t> (v);
                } else if (k == "dcp_ab") {
@@ -593,6 +572,11 @@ Film::read_metadata ()
                        if (vv) {
                                _length = vv;
                        }
+               } else if (k == "dcp_intrinsic_duration") {
+                       int const vv = atoi (v.c_str ());
+                       if (vv) {
+                               _dcp_intrinsic_duration = vv;
+                       }
                } else if (k == "content_digest") {
                        _content_digest = v;
                } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
@@ -704,30 +688,25 @@ Film::target_audio_sample_rate () const
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
 
-       DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
+       DCPFrameRate dfr (frames_per_second ());
 
-       /* Compensate for the fact that video will be rounded to the
-          nearest integer number of frames per second.
+       /* 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
+          look different in the DCP compared to the source (slower or faster).
+          skip/repeat doesn't come into effect here.
        */
-       if (dfr.run_fast) {
-               t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
+
+       if (dfr.change_speed) {
+               t *= _frames_per_second * dfr.factor() / dfr.frames_per_second;
        }
 
        return rint (t);
 }
 
-boost::optional<int>
-Film::dcp_length () const
+int
+Film::still_duration_in_frames () const
 {
-       if (content_type() == STILL) {
-               return _still_duration * frames_per_second();
-       }
-       
-       if (!length()) {
-               return boost::optional<int> ();
-       }
-
-       return length().get() - dcp_trim_start() - dcp_trim_end();
+       return still_duration() * frames_per_second();
 }
 
 /** @return a DCI-compliant name for a DCP of this film */
@@ -899,8 +878,7 @@ Film::set_content (string c)
        */
 
        try {
-               shared_ptr<DecodeOptions> o (new DecodeOptions);
-               Decoders d = decoder_factory (shared_from_this(), o, 0);
+               Decoders d = decoder_factory (shared_from_this(), DecodeOptions(), 0);
                
                set_size (d.video->native_size ());
                set_frames_per_second (d.video->frames_per_second ());
@@ -1075,23 +1053,23 @@ Film::set_scaler (Scaler const * s)
 }
 
 void
-Film::set_dcp_trim_start (int t)
+Film::set_trim_start (int t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_start = t;
+               _trim_start = t;
        }
-       signal_changed (DCP_TRIM_START);
+       signal_changed (TRIM_START);
 }
 
 void
-Film::set_dcp_trim_end (int t)
+Film::set_trim_end (int t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_end = t;
+               _trim_end = t;
        }
-       signal_changed (DCP_TRIM_END);
+       signal_changed (TRIM_END);
 }
 
 void
@@ -1142,8 +1120,7 @@ Film::set_external_audio (vector<string> a)
                _external_audio = a;
        }
 
-       shared_ptr<DecodeOptions> o (new DecodeOptions);
-       shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
+       shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), DecodeOptions(), 0));
        if (decoder->audio_stream()) {
                _external_audio_stream = decoder->audio_stream ();
        }
@@ -1350,7 +1327,17 @@ Film::unset_length ()
                _length = boost::none;
        }
        signal_changed (LENGTH);
-}      
+}
+
+void
+Film::set_dcp_intrinsic_duration (int d)
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _dcp_intrinsic_duration = d;
+       }
+       signal_changed (DCP_INTRINSIC_DURATION);
+}
 
 void
 Film::set_content_digest (string d)
@@ -1431,3 +1418,45 @@ Film::audio_stream () const
 
        return _external_audio_stream;
 }
+
+/** @param f DCP frame index.
+ *  @param t true to return a temporary file path, otherwise a permanent one.
+ *  @return The path to write this video frame to.
+ */
+string
+Film::frame_out_path (int f, bool t) const
+{
+       stringstream s;
+       s << j2k_dir() << "/";
+       s.width (8);
+       s << std::setfill('0') << f << ".j2c";
+
+       if (t) {
+               s << ".tmp";
+       }
+
+       return s.str ();
+}
+
+string
+Film::hash_out_path (int f, bool t) const
+{
+       return frame_out_path (f, t) + ".md5";
+}
+
+/** @param c Audio channel index.
+ *  @param t true to return a temporary file path, otherwise a permanent one.
+ *  @return The path to write this audio file to.
+ */
+string
+Film::multichannel_audio_out_path (int c, bool t) const
+{
+       stringstream s;
+       s << dir ("wavs") << "/" << (c + 1) << ".wav";
+       if (t) {
+               s << ".tmp";
+       }
+       
+       return s.str ();
+}
+