, _tms_path (".")
, _sound_processor (SoundProcessor::from_id ("dolby_cp750"))
{
+ _allowed_dcp_frame_rates.push_back (24);
+ _allowed_dcp_frame_rates.push_back (25);
+ _allowed_dcp_frame_rates.push_back (30);
+
ifstream f (file().c_str ());
string line;
while (getline (f, line)) {
return _sound_processor;
}
+ std::list<int> allowed_dcp_frame_rates () const {
+ return _allowed_dcp_frame_rates;
+ }
+
/** @param n New number of local encoding threads */
void set_num_local_encoding_threads (int n) {
_num_local_encoding_threads = n;
void set_tms_password (std::string p) {
_tms_password = p;
}
-
+
+ void set_allowed_dcp_frame_rates (std::list<int> const & r) {
+ _allowed_dcp_frame_rates = r;
+ }
+
void write () const;
static Config* instance ();
std::string _tms_password;
/** Our sound processor */
SoundProcessor const * _sound_processor;
+ std::list<int> _allowed_dcp_frame_rates;
/** Singleton instance, or 0 */
static Config* _instance;
}
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()));
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.
*/
- int const mult = dfr.skip ? 2 : 1;
- if (dfr.run_fast) {
- t *= _frames_per_second * mult / dfr.frames_per_second;
+ if (dfr.change_speed) {
+ t *= _frames_per_second * dfr.factor() / dfr.frames_per_second;
}
return rint (t);
#include <iomanip>
#include <iostream>
#include <fstream>
+#include <climits>
#ifdef DVDOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
#include "dcp_content_type.h"
#include "filter.h"
#include "sound_processor.h"
+#include "config.h"
using namespace std;
using namespace boost;
return (fabs (a - b) < 1e-4);
}
+class FrameRateCandidate
+{
+public:
+ FrameRateCandidate (float source_, int dcp_)
+ : source (source_)
+ , dcp (dcp_)
+ {}
+
+ bool skip () const {
+ return !about_equal (source, dcp) && source > dcp;
+ }
+
+ bool repeat () const {
+ return !about_equal (source, dcp) && source < dcp;
+ }
+
+ float source;
+ int dcp;
+};
+
/** @param fps Arbitrary source frames-per-second value */
-DCPFrameRate::DCPFrameRate (float fps)
- : frames_per_second (rint (fps))
- , skip (false)
- , repeat (false)
- , run_fast (false)
-{
- if (about_equal (fps, 50)) {
- /* XXX: not sure about this; just run at 50?
- Ring Peter Jackson.
- */
- frames_per_second = 25;
- skip = true;
- } else if (fps >= (27.5 / 2) && fps <= (32.5 / 2)) {
- frames_per_second = 30;
- repeat = true;
- } else if (fps >= (24.5 / 2) && fps <= (27.5 / 2)) {
- frames_per_second = 25;
- repeat = true;
- } else if (fps >= (20 / 2) && fps <= (24.5 / 2)) {
- frames_per_second = 24;
- repeat = true;
+/** XXX: this could be slow-ish */
+DCPFrameRate::DCPFrameRate (float source_fps)
+{
+ list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
+
+ /* Work out what rates we could manage, including those achieved by using skip / repeat. */
+ list<FrameRateCandidate> candidates;
+
+ /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
+ for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+ candidates.push_back (FrameRateCandidate (*i, *i));
}
+
+ /* Then the skip/repeat ones */
+ for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+ candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
+ candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
+ }
+
+ /* Pick the best one, bailing early if we hit an exact match */
+ float error = numeric_limits<float>::max ();
+ boost::optional<FrameRateCandidate> best;
+ list<FrameRateCandidate>::iterator i = candidates.begin();
+ while (i != candidates.end()) {
+
+ if (about_equal (i->source, source_fps)) {
+ best = *i;
+ break;
+ }
+
+ float const e = fabs (i->source - source_fps);
+ if (e < error) {
+ error = e;
+ best = *i;
+ }
+
+ ++i;
+ }
+
+ if (!best) {
+ throw EncodeError ("cannot find a suitable DCP frame rate for this source");
+ }
+
+ frames_per_second = best->dcp;
+ skip = best->skip ();
+ repeat = best->repeat ();
+ change_speed = !about_equal (source_fps * factor(), frames_per_second);
}
/** @param An arbitrary sampling rate.
struct DCPFrameRate
{
DCPFrameRate (float);
+
+ /** @return factor by which to multiply a source frame rate
+ to get the effective rate after any skip or repeat has happened.
+ */
+ float factor () const {
+ if (skip) {
+ return 0.5;
+ } else if (repeat) {
+ return 2;
+ }
+
+ return 1;
+ }
/** frames per second for the DCP */
int frames_per_second;
bool skip;
/** true to repeat every frame once */
bool repeat;
- /** true if this DCP will run its video faster than the source
- * without taking into account `skip' and `repeat'.
- * (i.e. run_fast will be true if
+ /** true if this DCP will run its video faster or slower than the source
+ * without taking into account `repeat'.
+ * (e.g. change_speed will be true if
* source is 29.97fps, DCP is 30fps
* source is 14.50fps, DCP is 30fps
* but not if
* source is 15.00fps, DCP is 30fps
* source is 12.50fps, DCP is 25fps)
*/
- bool run_fast;
+ bool change_speed;
};
enum ContentType {
BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
}
+/* Test the constructor of DCPFrameRate */
+BOOST_AUTO_TEST_CASE (dcp_frame_rate_test)
+{
+ /* Run some tests with a limited range of allowed rates */
+
+ std::list<int> afr;
+ afr.push_back (24);
+ afr.push_back (25);
+ afr.push_back (30);
+ Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+ DCPFrameRate dfr = DCPFrameRate (60);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.skip, true);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (50);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.skip, true);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (48);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+ BOOST_CHECK_EQUAL (dfr.skip, true);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (30);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (29.97);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (25);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (24);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (14.5);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (12.6);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (12.4);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (12);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ /* Now add some more rates and see if it will use them
+ in preference to skip/repeat.
+ */
+
+ afr.push_back (48);
+ afr.push_back (50);
+ afr.push_back (60);
+ Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+ dfr = DCPFrameRate (60);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 60);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (50);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 50);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (48);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 48);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+}
+
BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
{
shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");