Basic support for selection of audio / subtitle streams.
[dcpomatic.git] / src / lib / film_state.cc
index 343e9ebb4f4c4305e9f26e60b393c618d9fd973f..0d0bd6313667ca5e14c5d80322f600ea32868582 100644 (file)
 #include <iomanip>
 #include <sstream>
 #include <boost/filesystem.hpp>
+#include <boost/date_time.hpp>
 #include "film_state.h"
 #include "scaler.h"
 #include "filter.h"
 #include "format.h"
 #include "dcp_content_type.h"
 #include "util.h"
+#include "exceptions.h"
 
 using namespace std;
 using namespace boost;
@@ -47,6 +49,7 @@ FilmState::write_metadata (ofstream& f) const
 {
        /* User stuff */
        f << "name " << name << "\n";
+       f << "use_dci_name " << use_dci_name << "\n";
        f << "content " << content << "\n";
        if (dcp_content_type) {
                f << "dcp_content_type " << dcp_content_type->pretty_name () << "\n";
@@ -55,10 +58,10 @@ FilmState::write_metadata (ofstream& f) const
        if (format) {
                f << "format " << format->as_metadata () << "\n";
        }
-       f << "left_crop " << left_crop << "\n";
-       f << "right_crop " << right_crop << "\n";
-       f << "top_crop " << top_crop << "\n";
-       f << "bottom_crop " << bottom_crop << "\n";
+       f << "left_crop " << crop.left << "\n";
+       f << "right_crop " << crop.right << "\n";
+       f << "top_crop " << crop.top << "\n";
+       f << "bottom_crop " << crop.bottom << "\n";
        for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
                f << "filter " << (*i)->id () << "\n";
        }
@@ -76,9 +79,20 @@ FilmState::write_metadata (ofstream& f) const
        }
        
        f << "dcp_ab " << (dcp_ab ? "1" : "0") << "\n";
+       f << "selected_audio_stream " << audio_stream << "\n";
        f << "audio_gain " << audio_gain << "\n";
        f << "audio_delay " << audio_delay << "\n";
        f << "still_duration " << still_duration << "\n";
+       f << "with_subtitles " << with_subtitles << "\n";
+       f << "subtitle_offset " << subtitle_offset << "\n";
+       f << "subtitle_scale " << subtitle_scale << "\n";
+       f << "audio_language " << audio_language << "\n";
+       f << "subtitle_language " << subtitle_language << "\n";
+       f << "territory " << territory << "\n";
+       f << "rating " << rating << "\n";
+       f << "studio " << studio << "\n";
+       f << "facility " << facility << "\n";
+       f << "package_type " << package_type << "\n";
 
        /* Cached stuff; this is information about our content; we could
           look it up each time, but that's slow.
@@ -93,6 +107,16 @@ FilmState::write_metadata (ofstream& f) const
        f << "audio_sample_rate " << audio_sample_rate << "\n";
        f << "audio_sample_format " << audio_sample_format_to_string (audio_sample_format) << "\n";
        f << "content_digest " << content_digest << "\n";
+       f << "selected_subtitle_stream " << subtitle_stream << "\n";
+       f << "has_subtitles " << has_subtitles << "\n";
+
+       for (vector<Stream>::const_iterator i = audio_streams.begin(); i != audio_streams.end(); ++i) {
+               f << "audio_stream " << i->to_string () << "\n";
+       }
+
+       for (vector<Stream>::const_iterator i = subtitle_streams.begin(); i != subtitle_streams.end(); ++i) {
+               f << "subtitle_stream " << i->to_string () << "\n";
+       }
 }
 
 /** Read state from a key / value pair.
@@ -105,6 +129,8 @@ FilmState::read_metadata (string k, string v)
        /* 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 == "dcp_content_type") {
@@ -114,13 +140,13 @@ FilmState::read_metadata (string k, string v)
        } else if (k == "format") {
                format = Format::from_metadata (v);
        } else if (k == "left_crop") {
-               left_crop = atoi (v.c_str ());
+               crop.left = atoi (v.c_str ());
        } else if (k == "right_crop") {
-               right_crop = atoi (v.c_str ());
+               crop.right = atoi (v.c_str ());
        } else if (k == "top_crop") {
-               top_crop = atoi (v.c_str ());
+               crop.top = atoi (v.c_str ());
        } else if (k == "bottom_crop") {
-               bottom_crop = atoi (v.c_str ());
+               crop.bottom = atoi (v.c_str ());
        } else if (k == "filter") {
                filters.push_back (Filter::from_id (v));
        } else if (k == "scaler") {
@@ -135,12 +161,36 @@ FilmState::read_metadata (string k, string v)
                }
        } else if (k == "dcp_ab") {
                dcp_ab = (v == "1");
+       } else if (k == "selected_audio_stream") {
+               audio_stream = atoi (v.c_str ());
        } 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 == "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 == "selected_subtitle_stream") {
+               subtitle_stream = atoi (v.c_str ());
+       } else if (k == "audio_language") {
+               audio_language = v;
+       } else if (k == "subtitle_language") {
+               subtitle_language = v;
+       } else if (k == "territory") {
+               territory = v;
+       } else if (k == "rating") {
+               rating = v;
+       } else if (k == "studio") {
+               studio = v;
+       } else if (k == "facility") {
+               facility = v;
+       } else if (k == "package_type") {
+               package_type = v;
        }
        
        /* Cached stuff */
@@ -164,11 +214,12 @@ FilmState::read_metadata (string k, string v)
                audio_sample_format = audio_sample_format_from_string (v);
        } else if (k == "content_digest") {
                content_digest = v;
-       }
-       
-       /* Itsy bitsy hack: compute digest here if don't have one (for backwards compatibility) */
-       if (content_digest.empty() && !content.empty()) {
-               content_digest = md5_digest (content_path ());
+       } else if (k == "has_subtitles") {
+               has_subtitles = (v == "1");
+       } else if (k == "audio_stream") {
+               audio_streams.push_back (Stream (v));
+       } else if (k == "subtitle_stream") {
+               subtitle_streams.push_back (Stream (v));
        }
 }
 
@@ -188,10 +239,22 @@ FilmState::thumb_file (int n) const
  */
 string
 FilmState::thumb_file_for_frame (int n) const
+{
+       return thumb_base_for_frame(n) + ".png";
+}
+
+string
+FilmState::thumb_base (int n) const
+{
+       return thumb_base_for_frame (thumb_frame (n));
+}
+
+string
+FilmState::thumb_base_for_frame (int n) const
 {
        stringstream s;
        s.width (8);
-       s << setfill('0') << n << ".tiff";
+       s << setfill('0') << n;
        
        filesystem::path p;
        p /= dir ("thumbs");
@@ -214,8 +277,8 @@ FilmState::thumb_frame (int n) const
 Size
 FilmState::cropped_size (Size s) const
 {
-       s.width -= left_crop + right_crop;
-       s.height -= top_crop + bottom_crop;
+       s.width -= crop.left + crop.right;
+       s.height -= crop.top + crop.bottom;
        return s;
 }
 
@@ -256,13 +319,150 @@ ContentType
 FilmState::content_type () const
 {
 #if BOOST_FILESYSTEM_VERSION == 3
-       string const ext = filesystem::path(content).extension().string();
+       string ext = filesystem::path(content).extension().string();
 #else
-       string const ext = filesystem::path(content).extension();
+       string ext = filesystem::path(content).extension();
 #endif
+
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+       
        if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
                return STILL;
        }
 
        return VIDEO;
 }
+
+/** @return Number of bytes per sample of a single channel */
+int
+FilmState::bytes_per_sample () const
+{
+       switch (audio_sample_format) {
+       case AV_SAMPLE_FMT_S16:
+               return 2;
+       default:
+               return 0;
+       }
+
+       return 0;
+}
+
+int
+FilmState::target_sample_rate () const
+{
+       /* Resample to a DCI-approved sample rate */
+       double t = dcp_audio_sample_rate (audio_sample_rate);
+
+       /* 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);
+       }
+
+       return rint (t);
+}
+
+int
+FilmState::dcp_length () const
+{
+       if (dcp_frames) {
+               return dcp_frames;
+       }
+
+       return length;
+}
+
+/** @return a DCI-compliant name for a DCP of this film */
+string
+FilmState::dci_name () const
+{
+       stringstream d;
+
+       string fixed_name = to_upper_copy (name);
+       for (size_t i = 0; i < fixed_name.length(); ++i) {
+               if (fixed_name[i] == ' ') {
+                       fixed_name[i] = '-';
+               }
+       }
+
+       /* Spec is that the name part should be maximum 14 characters, as I understand it */
+       if (fixed_name.length() > 14) {
+               fixed_name = fixed_name.substr (0, 14);
+       }
+
+       d << fixed_name << "_";
+
+       if (dcp_content_type) {
+               d << dcp_content_type->dci_name() << "_";
+       }
+
+       if (format) {
+               d << format->dci_name() << "_";
+       }
+
+       if (!audio_language.empty ()) {
+               d << audio_language;
+               if (with_subtitles) {
+                       if (!subtitle_language.empty ()) {
+                               d << "-" << subtitle_language;
+                       } else {
+                               d << "-XX";
+                       }
+               }
+                       
+               d << "_";
+       }
+
+       if (!territory.empty ()) {
+               d << territory;
+               if (!rating.empty ()) {
+                       d << "-" << rating;
+               }
+               d << "_";
+       }
+
+       switch (audio_channels) {
+       case 1:
+               d << "10_";
+               break;
+       case 2:
+               d << "20_";
+               break;
+       case 6:
+               d << "51_";
+               break;
+       }
+
+       d << "2K_";
+
+       if (!studio.empty ()) {
+               d << studio << "_";
+       }
+
+       gregorian::date today = gregorian::day_clock::local_day ();
+       d << gregorian::to_iso_string (today) << "_";
+
+       if (!facility.empty ()) {
+               d << facility << "_";
+       }
+
+       if (!package_type.empty ()) {
+               d << package_type;
+       }
+
+       return d.str ();
+}
+
+/** @return name to give the DCP */
+string
+FilmState::dcp_name () const
+{
+       if (use_dci_name) {
+               return dci_name ();
+       }
+
+       return name;
+}
+
+