Various range fixes.
[dcpomatic.git] / src / lib / film.cc
index 8750ab442c185018fb61c6c09421b7551f5f32b0..26edc80b07a00bbd38d3d83af825cb56a88ebf38 100644 (file)
@@ -62,6 +62,8 @@ using std::vector;
 using std::ifstream;
 using std::ofstream;
 using std::setfill;
+using std::min;
+using std::make_pair;
 using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::to_upper_copy;
@@ -81,8 +83,8 @@ Film::Film (string d, bool must_exist)
        , _dcp_content_type (0)
        , _format (0)
        , _scaler (Scaler::from_id ("bicubic"))
-       , _dcp_frames (0)
-       , _dcp_trim_action (CUT)
+       , _dcp_trim_start (0)
+       , _dcp_trim_end (0)
        , _dcp_ab (false)
        , _audio_stream (-1)
        , _audio_gain (0)
@@ -92,7 +94,6 @@ Film::Film (string d, bool must_exist)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
-       , _length (0)
        , _audio_sample_rate (0)
        , _has_subtitles (false)
        , _frames_per_second (0)
@@ -129,6 +130,7 @@ Film::Film (string d, bool must_exist)
        read_metadata ();
 
        _log = new FileLog (file ("log"));
+       set_dci_date_today ();
 }
 
 Film::Film (Film const & o)
@@ -142,8 +144,8 @@ Film::Film (Film const & o)
        , _crop              (o._crop)
        , _filters           (o._filters)
        , _scaler            (o._scaler)
-       , _dcp_frames        (o._dcp_frames)
-       , _dcp_trim_action   (o._dcp_trim_action)
+       , _dcp_trim_start    (o._dcp_trim_start)
+       , _dcp_trim_end      (o._dcp_trim_end)
        , _dcp_ab            (o._dcp_ab)
        , _audio_stream      (o._audio_stream)
        , _audio_gain        (o._audio_gain)
@@ -222,6 +224,8 @@ Film::j2k_dir () const
 void
 Film::make_dcp (bool transcode)
 {
+       set_dci_date_today ();
+       
        if (dcp_name().find ("/") != string::npos) {
                throw BadSettingError ("name", "cannot contain slashes");
        }
@@ -252,24 +256,13 @@ Film::make_dcp (bool transcode)
 
        shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", dir ("wavs")));
        o->out_size = format()->dcp_size ();
-       if (dcp_frames() == 0) {
-               /* Decode the whole film, no blacking */
-               o->black_after = 0;
-       } else {
-               switch (dcp_trim_action()) {
-               case CUT:
-                       /* Decode only part of the film, no blacking */
-                       o->black_after = 0;
-                       break;
-               case BLACK_OUT:
-                       /* Decode the whole film, but black some frames out */
-                       o->black_after = dcp_frames ();
-               }
+       o->padding = format()->dcp_padding (shared_from_this ());
+       o->ratio = format()->ratio_as_float (shared_from_this ());
+       if (dcp_length ()) {
+               o->decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
        }
-       
-       o->padding = format()->dcp_padding (this);
-       o->ratio = format()->ratio_as_float (this);
        o->decode_subtitles = with_subtitles ();
+       o->decode_video_skip = dcp_frame_rate (frames_per_second()).skip;
 
        shared_ptr<Job> r;
 
@@ -293,7 +286,7 @@ Film::examine_content ()
                return;
        }
 
-       set_thumbs (vector<int> ());
+       set_thumbs (vector<SourceFrame> ());
        boost::filesystem::remove_all (dir ("thumbs"));
 
        /* This call will recreate the directory */
@@ -390,10 +383,10 @@ void
 Film::write_metadata () const
 {
        boost::mutex::scoped_lock lm (_state_mutex);
-       
+
        boost::filesystem::create_directories (directory());
 
-       string const m = file_locked ("metadata");
+       string const m = file ("metadata");
        ofstream f (m.c_str ());
        if (!f.good ()) {
                throw CreateFileError (m);
@@ -417,18 +410,8 @@ Film::write_metadata () const
                f << "filter " << (*i)->id () << "\n";
        }
        f << "scaler " << _scaler->id () << "\n";
-       f << "dcp_frames " << _dcp_frames << "\n";
-
-       f << "dcp_trim_action ";
-       switch (_dcp_trim_action) {
-       case CUT:
-               f << "cut\n";
-               break;
-       case BLACK_OUT:
-               f << "black_out\n";
-               break;
-       }
-       
+       f << "dcp_trim_start " << _dcp_trim_start << "\n";
+       f << "dcp_trim_end " << _dcp_trim_end << "\n";
        f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
        f << "selected_audio_stream " << _audio_stream << "\n";
        f << "audio_gain " << _audio_gain << "\n";
@@ -449,12 +432,12 @@ Film::write_metadata () const
        /* Cached stuff; this is information about our content; we could
           look it up each time, but that's slow.
        */
-       for (vector<int>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) {
+       for (vector<SourceFrame>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) {
                f << "thumb " << *i << "\n";
        }
        f << "width " << _size.width << "\n";
        f << "height " << _size.height << "\n";
-       f << "length " << _length << "\n";
+       f << "length " << _length.get_value_or(0) << "\n";
        f << "audio_sample_rate " << _audio_sample_rate << "\n";
        f << "content_digest " << _content_digest << "\n";
        f << "has_subtitles " << _has_subtitles << "\n";
@@ -478,7 +461,7 @@ Film::read_metadata ()
 {
        boost::mutex::scoped_lock lm (_state_mutex);
        
-       ifstream f (file_locked("metadata").c_str());
+       ifstream f (file ("metadata").c_str());
        multimap<string, string> kv = read_key_value (f);
        for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
                string const k = i->first;
@@ -507,14 +490,10 @@ Film::read_metadata ()
                        _filters.push_back (Filter::from_id (v));
                } else if (k == "scaler") {
                        _scaler = Scaler::from_id (v);
-               } else if (k == "dcp_frames") {
-                       _dcp_frames = atoi (v.c_str ());
-               } else if (k == "dcp_trim_action") {
-                       if (v == "cut") {
-                               _dcp_trim_action = CUT;
-                       } else if (v == "black_out") {
-                               _dcp_trim_action = BLACK_OUT;
-                       }
+               } 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 (k == "dcp_ab") {
                        _dcp_ab = (v == "1");
                } else if (k == "selected_audio_stream") {
@@ -553,7 +532,7 @@ Film::read_metadata ()
                if (k == "thumb") {
                        int const n = atoi (v.c_str ());
                        /* Only add it to the list if it still exists */
-                       if (boost::filesystem::exists (thumb_file_for_frame_locked (n))) {
+                       if (boost::filesystem::exists (thumb_file_for_frame (n))) {
                                _thumbs.push_back (n);
                        }
                } else if (k == "width") {
@@ -561,7 +540,10 @@ Film::read_metadata ()
                } else if (k == "height") {
                        _size.height = atoi (v.c_str ());
                } else if (k == "length") {
-                       _length = atof (v.c_str ());
+                       int const vv = atoi (v.c_str ());
+                       if (vv) {
+                               _length = vv;
+                       }
                } else if (k == "audio_sample_rate") {
                        _audio_sample_rate = atoi (v.c_str ());
                } else if (k == "content_digest") {
@@ -589,22 +571,19 @@ Film::thumb_file (int n) const
        return thumb_file_for_frame (thumb_frame (n));
 }
 
-/** @param n A frame index within the Film.
+/** @param n A frame index within the Film's source.
  *  @return The path to the thumb's image file for this frame;
  *  we assume that it exists.
  */
 string
-Film::thumb_file_for_frame (int n) const
+Film::thumb_file_for_frame (SourceFrame n) const
 {
        return thumb_base_for_frame(n) + ".png";
 }
 
-string
-Film::thumb_file_for_frame_locked (int n) const
-{
-       return thumb_base_for_frame_locked(n) + ".png";
-}
-
+/** @param n Thumb index.
+ *  Must not be called with the _state_mutex locked.
+ */
 string
 Film::thumb_base (int n) const
 {
@@ -612,30 +591,25 @@ Film::thumb_base (int n) const
 }
 
 string
-Film::thumb_base_for_frame (int n) const
-{
-       boost::mutex::scoped_lock lm (_state_mutex);
-       return thumb_base_for_frame_locked (n);
-}
-
-string
-Film::thumb_base_for_frame_locked (int n) const
+Film::thumb_base_for_frame (SourceFrame n) const
 {
        stringstream s;
        s.width (8);
        s << setfill('0') << n;
        
        boost::filesystem::path p;
-       p /= dir_locked ("thumbs");
+       p /= dir ("thumbs");
        p /= s.str ();
                
        return p.string ();
 }
 
 /** @param n A thumb index.
- *  @return The frame within the Film that it is for.
+ *  @return The frame within the Film's source that it is for.
+ *
+ *  Must not be called with the _state_mutex locked.
  */
-int
+SourceFrame
 Film::thumb_frame (int n) const
 {
        boost::mutex::scoped_lock lm (_state_mutex);
@@ -658,13 +632,7 @@ Film::cropped_size (Size s) const
 string
 Film::dir (string d) const
 {
-       boost::mutex::scoped_lock lm (_state_mutex);
-       return dir_locked (d);
-}
-
-string
-Film::dir_locked (string d) const
-{
+       boost::mutex::scoped_lock lm (_directory_mutex);
        boost::filesystem::path p;
        p /= _directory;
        p /= d;
@@ -672,17 +640,13 @@ Film::dir_locked (string d) const
        return p.string ();
 }
 
-/** Given a file or directory name, return its full path within the Film's directory */
+/** Given a file or directory name, return its full path within the Film's directory.
+ *  _directory_mutex must not be locked on entry.
+ */
 string
 Film::file (string f) const
 {
-       boost::mutex::scoped_lock lm (_state_mutex);
-       return file_locked (f);
-}
-
-string
-Film::file_locked (string f) const
-{
+       boost::mutex::scoped_lock lm (_directory_mutex);
        boost::filesystem::path p;
        p /= _directory;
        p /= f;
@@ -700,7 +664,7 @@ Film::content_path () const
                return _content;
        }
 
-       return file_locked (_content);
+       return file (_content);
 }
 
 ContentType
@@ -709,7 +673,7 @@ Film::content_type () const
 #if BOOST_FILESYSTEM_VERSION == 3
        string ext = boost::filesystem::path(_content).extension().string();
 #else
-       string ext = filesystem::path(_content).extension();
+       string ext = boost::filesystem::path(_content).extension();
 #endif
 
        transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
@@ -728,24 +692,26 @@ Film::target_audio_sample_rate () const
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_sample_rate (audio_sample_rate());
 
+       DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
+
        /* Compensate for the fact that video will be rounded to the
           nearest integer number of frames per second.
        */
-       if (rint (frames_per_second()) != frames_per_second()) {
-               t *= _frames_per_second / rint (frames_per_second());
+       if (dfr.run_fast) {
+               t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
        }
 
        return rint (t);
 }
 
-int
+boost::optional<SourceFrame>
 Film::dcp_length () const
 {
-       if (dcp_frames()) {
-               return dcp_frames();
+       if (!length()) {
+               return boost::optional<SourceFrame> ();
        }
 
-       return length();
+       return length().get() - dcp_trim_start() - dcp_trim_end();
 }
 
 /** @return a DCI-compliant name for a DCP of this film */
@@ -818,8 +784,7 @@ Film::dci_name () const
                d << _studio << "_";
        }
 
-       boost::gregorian::date today = boost::gregorian::day_clock::local_day ();
-       d << boost::gregorian::to_iso_string (today) << "_";
+       d << boost::gregorian::to_iso_string (_dci_date) << "_";
 
        if (!_facility.empty ()) {
                d << _facility << "_";
@@ -896,37 +861,55 @@ Film::set_content (string c)
                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;
        }
-               
+
        /* Create a temporary decoder so that we can get information
           about the content.
        */
-       
 
-       shared_ptr<Options> o (new Options ("", "", ""));
-       o->out_size = Size (1024, 1024);
-       
-       shared_ptr<Decoder> d = decoder_factory (shared_from_this(), o, 0, 0);
-       
-       set_size (d->native_size ());
-       set_frames_per_second (d->frames_per_second ());
-       set_audio_sample_rate (d->audio_sample_rate ());
-       set_has_subtitles (d->has_subtitles ());
-       set_audio_streams (d->audio_streams ());
-       set_subtitle_streams (d->subtitle_streams ());
-       set_audio_stream (audio_streams().empty() ? -1 : 0);
-       set_subtitle_stream (subtitle_streams().empty() ? -1 : 0);
-       
-       signal_changed (CONTENT);
+       try {
+               shared_ptr<Options> o (new Options ("", "", ""));
+               o->out_size = Size (1024, 1024);
+               
+               shared_ptr<Decoder> d = decoder_factory (shared_from_this(), o, 0, 0);
+               
+               set_size (d->native_size ());
+               set_frames_per_second (d->frames_per_second ());
+               set_audio_sample_rate (d->audio_sample_rate ());
+               set_has_subtitles (d->has_subtitles ());
+               set_audio_streams (d->audio_streams ());
+               set_subtitle_streams (d->subtitle_streams ());
+               set_audio_stream (audio_streams().empty() ? -1 : 0);
+               set_subtitle_stream (subtitle_streams().empty() ? -1 : 0);
+               
+               {
+                       boost::mutex::scoped_lock lm (_state_mutex);
+                       _content = c;
+               }
+               
+               signal_changed (CONTENT);
+               
+               set_content_digest (md5_digest (content_path ()));
+               
+               examine_content ();
 
-       set_content_digest (md5_digest (content_path ()));
+       } catch (...) {
+
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _content = old_content;
+               throw;
+
+       }
 }
               
 void
@@ -1037,23 +1020,23 @@ Film::set_scaler (Scaler const * s)
 }
 
 void
-Film::set_dcp_frames (int f)
+Film::set_dcp_trim_start (int t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_frames = f;
+               _dcp_trim_start = t;
        }
-       signal_changed (DCP_FRAMES);
+       signal_changed (DCP_TRIM_START);
 }
 
 void
-Film::set_dcp_trim_action (TrimAction a)
+Film::set_dcp_trim_end (int t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_trim_action = a;
+               _dcp_trim_end = t;
        }
-       signal_changed (DCP_TRIM_ACTION);
+       signal_changed (DCP_TRIM_END);
 }
 
 void
@@ -1217,7 +1200,7 @@ Film::set_package_type (string p)
 }
 
 void
-Film::set_thumbs (vector<int> t)
+Film::set_thumbs (vector<SourceFrame> t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
@@ -1237,7 +1220,7 @@ Film::set_size (Size s)
 }
 
 void
-Film::set_length (int l)
+Film::set_length (SourceFrame l)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
@@ -1246,6 +1229,16 @@ Film::set_length (int l)
        signal_changed (LENGTH);
 }
 
+void
+Film::unset_length ()
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _length = boost::none;
+       }
+       signal_changed (LENGTH);
+}      
+
 void
 Film::set_audio_sample_rate (int r)
 {
@@ -1313,7 +1306,10 @@ Film::signal_changed (Property p)
                boost::mutex::scoped_lock lm (_state_mutex);
                _dirty = true;
        }
-       ui_signaller->emit (boost::bind (boost::ref (Changed), p));
+
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (Changed), p));
+       }
 }
 
 int
@@ -1326,3 +1322,9 @@ Film::audio_channels () const
        
        return _audio_streams[_audio_stream].channels ();
 }
+
+void
+Film::set_dci_date_today ()
+{
+       _dci_date = boost::gregorian::day_clock::local_day ();
+}