summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/config.h2
-rw-r--r--src/lib/copy_dcp_details_to_film.cc4
-rw-r--r--src/lib/create_cli.cc18
-rw-r--r--src/lib/create_cli.h2
-rw-r--r--src/lib/dcp_content.cc22
-rw-r--r--src/lib/dcp_content.h13
-rw-r--r--src/lib/dcp_examiner.cc3
-rw-r--r--src/lib/dcp_examiner.h5
-rw-r--r--src/lib/dcp_video.cc10
-rw-r--r--src/lib/dcp_video.h10
-rw-r--r--src/lib/dcpomatic_time.h55
-rw-r--r--src/lib/encode_cli.cc2
-rw-r--r--src/lib/ffmpeg_file_encoder.cc10
-rw-r--r--src/lib/ffmpeg_file_encoder.h4
-rw-r--r--src/lib/ffmpeg_film_encoder.cc2
-rw-r--r--src/lib/ffmpeg_film_encoder.h2
-rw-r--r--src/lib/film.cc51
-rw-r--r--src/lib/film.h7
-rw-r--r--src/lib/frame_rate_change.cc14
-rw-r--r--src/lib/frame_rate_change.h5
-rw-r--r--src/lib/grok/context.h2
-rw-r--r--src/lib/hints.cc25
-rw-r--r--src/lib/image_content.cc2
-rw-r--r--src/lib/make_dcp.cc2
-rw-r--r--src/lib/mpeg2_encoder.cc6
-rw-r--r--src/lib/player.cc4
-rw-r--r--src/lib/playlist.cc10
-rw-r--r--src/lib/playlist.h6
-rw-r--r--src/lib/reel_writer.cc23
-rw-r--r--src/lib/referenced_reel_asset.cc6
-rw-r--r--src/lib/render_text.cc10
-rw-r--r--src/lib/render_text.h2
-rw-r--r--src/lib/subtitle_film_encoder.cc8
-rw-r--r--src/lib/types.h3
-rw-r--r--src/lib/util.cc18
-rw-r--r--src/lib/util.h1
-rw-r--r--src/wx/dcp_timeline.cc4
-rw-r--r--src/wx/standard_controls.cc2
-rw-r--r--src/wx/timecode.h9
39 files changed, 272 insertions, 112 deletions
diff --git a/src/lib/config.h b/src/lib/config.h
index c37829146..49e865d38 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -185,7 +185,7 @@ public:
return _tms_password;
}
- std::list<int> allowed_dcp_frame_rates() const {
+ std::list<dcp::Fraction> allowed_dcp_frame_rates() const {
return _allowed_dcp_frame_rates;
}
diff --git a/src/lib/copy_dcp_details_to_film.cc b/src/lib/copy_dcp_details_to_film.cc
index cff2ea51c..80af61a64 100644
--- a/src/lib/copy_dcp_details_to_film.cc
+++ b/src/lib/copy_dcp_details_to_film.cc
@@ -57,8 +57,8 @@ copy_dcp_settings_to_film(shared_ptr<const DCPContent> dcp, shared_ptr<Film> fil
film->set_container(Ratio::nearest_from_ratio(size->ratio()));
}
film->set_resolution(dcp->resolution());
- DCPOMATIC_ASSERT(dcp->video_frame_rate());
- film->set_video_frame_rate(*dcp->video_frame_rate());
+ DCPOMATIC_ASSERT(dcp->rational_video_frame_rate());
+ film->set_video_frame_rate(*dcp->rational_video_frame_rate());
}
if (dcp->audio) {
diff --git a/src/lib/create_cli.cc b/src/lib/create_cli.cc
index 32834be23..1bc463371 100644
--- a/src/lib/create_cli.cc
+++ b/src/lib/create_cli.cc
@@ -177,7 +177,7 @@ CreateCLI::CreateCLI(int argc, char* argv[])
optional<string> dcp_content_type_string;
string container_ratio_string;
optional<string> standard_string;
- int dcp_frame_rate_int = 0;
+ string dcp_frame_rate_string;
string template_name_string;
int64_t video_bit_rate_int = 0;
optional<int> audio_channels;
@@ -257,7 +257,7 @@ CreateCLI::CreateCLI(int argc, char* argv[])
argument_option(i, argc, argv, "-t", "--template", &claimed, &error, &template_name_string);
/* See comment below about --cpl */
argument_option(i, argc, argv, "-c", "--dcp-content-type", &claimed, &error, &dcp_content_type_string, string_to_string);
- argument_option(i, argc, argv, "-f", "--dcp-frame-rate", &claimed, &error, &dcp_frame_rate_int);
+ argument_option(i, argc, argv, "-f", "--dcp-frame-rate", &claimed, &error, &dcp_frame_rate_string);
argument_option(i, argc, argv, "", "--container-ratio", &claimed, &error, &container_ratio_string);
argument_option(i, argc, argv, "-s", "--still-length", &claimed, &error, &still_length, string_to_nonzero_int);
argument_option(i, argc, argv, "", "--auto-crop-threshold", &claimed, &error, &auto_crop_threshold, string_to_int);
@@ -356,8 +356,18 @@ CreateCLI::CreateCLI(int argc, char* argv[])
_template_name = template_name_string;
}
- if (dcp_frame_rate_int) {
- dcp_frame_rate = dcp_frame_rate_int;
+ if (!dcp_frame_rate_string.empty()) {
+ vector<string> parts;
+ boost::split(parts, dcp_frame_rate_string, boost::is_any_of("/"));
+ if (parts.empty() || parts.size() > 2) {
+ error = fmt::format("{}: unrecognised DCP frame rate '{}'", argv[0], dcp_frame_rate_string);
+ return;
+ }
+ if (parts.size() == 1) {
+ dcp_frame_rate = dcp::Fraction(dcp::raw_convert<int>(parts[0]), 1);
+ } else {
+ dcp_frame_rate = dcp::Fraction(dcp::raw_convert<int>(parts[0]), dcp::raw_convert<int>(parts[1]));
+ }
}
if (video_bit_rate_int) {
diff --git a/src/lib/create_cli.h b/src/lib/create_cli.h
index 00abf85e5..b3cc8074d 100644
--- a/src/lib/create_cli.h
+++ b/src/lib/create_cli.h
@@ -52,7 +52,7 @@ public:
};
bool version;
- boost::optional<int> dcp_frame_rate;
+ boost::optional<dcp::Fraction> dcp_frame_rate;
boost::optional<int> still_length;
boost::optional<int> auto_crop_threshold;
boost::optional<boost::filesystem::path> config_dir;
diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc
index 98c49fae4..7d3eed69e 100644
--- a/src/lib/dcp_content.cc
+++ b/src/lib/dcp_content.cc
@@ -91,6 +91,22 @@ DCPContent::DCPContent(cxml::ConstNodePtr node, boost::optional<boost::filesyste
text = TextContent::from_xml(this, node, version, notes);
atmos = AtmosContent::from_xml(this, node);
+ if (auto rational = node->optional_string_child("RationalVideoFrameRate")) {
+ _rational_video_frame_rate = dcp::Fraction(*rational);
+ } else if (auto const vfr = video_frame_rate()) {
+ optional<dcp::Fraction> best;
+ float best_error = FLT_MAX;
+ for (auto rate: vector<dcp::Fraction>{dcp::Fraction{16, 1}, {200, 11}, {20, 1}, {240, 11}, {24, 1}, {25, 1}, {30, 1}, {48, 1}, {60, 1}}) {
+ auto const error = std::abs(rate.as_float() - *vfr);
+ if (error < best_error) {
+ best = rate;
+ best_error = error;
+ }
+ }
+
+ _rational_video_frame_rate = *best;
+ }
+
if (video && audio) {
audio->set_stream(
make_shared<AudioStream>(
@@ -281,6 +297,7 @@ DCPContent::examine(shared_ptr<Job> job, bool tolerant)
video = make_shared<VideoContent>(this);
}
video->take_from_examiner(examiner);
+ _rational_video_frame_rate = examiner->rational_video_frame_rate();
set_default_colour_conversion();
}
@@ -425,6 +442,9 @@ DCPContent::as_xml(xmlpp::Element* element, bool with_paths, PathBehaviour path_
if (video) {
video->as_xml(element);
+ if (rational_video_frame_rate()) {
+ cxml::add_text_child(element, "RationalVideoFrameRate", _rational_video_frame_rate->as_string());
+ }
}
if (audio) {
@@ -737,7 +757,7 @@ DCPContent::can_reference_anything(shared_ptr<const Film> film, string& why_not)
}
/* And the same frame rate */
- if (!video_frame_rate() || (lrint(video_frame_rate().get()) != film->video_frame_rate())) {
+ if (!rational_video_frame_rate() || (rational_video_frame_rate().get() != film->video_frame_rate())) {
/// TRANSLATORS: this string will follow "Cannot reference this DCP: "
why_not = _("it has a different frame rate to the film.");
return false;
diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h
index a76028a67..aecc60149 100644
--- a/src/lib/dcp_content.h
+++ b/src/lib/dcp_content.h
@@ -211,10 +211,12 @@ public:
Resolution resolution() const;
std::vector<dcp::Rating> ratings() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _ratings;
}
std::vector<std::string> content_versions() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _content_versions;
}
@@ -224,29 +226,39 @@ public:
* and they have different langauges, this could be wrong.
*/
boost::optional<dcp::LanguageTag> audio_language() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _audio_language;
}
void check_font_ids();
boost::optional<std::string> chain() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _chain;
}
boost::optional<std::string> distributor() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _distributor;
}
boost::optional<std::string> facility() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _facility;
}
boost::optional<dcp::Luminance> luminance() const {
+ boost::mutex::scoped_lock lm(_mutex);
return _luminance;
}
std::list<dcpomatic::DCPTimePeriod> reels(std::shared_ptr<const Film> film) const;
+ boost::optional<dcp::Fraction> rational_video_frame_rate() const {
+ boost::mutex::scoped_lock lm(_mutex);
+ return _rational_video_frame_rate;
+ }
+
private:
friend struct reels_test5;
@@ -279,6 +291,7 @@ private:
*/
EnumIndexedVector<bool, TextType> _reference_text;
+ boost::optional<dcp::Fraction> _rational_video_frame_rate;
boost::optional<dcp::Standard> _standard;
boost::optional<VideoEncoding> _video_encoding;
boost::optional<dcp::ContentKind> _content_kind;
diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc
index e5e42a923..d662eff9e 100644
--- a/src/lib/dcp_examiner.cc
+++ b/src/lib/dcp_examiner.cc
@@ -166,6 +166,7 @@ DCPExaminer::DCPExaminer(shared_ptr<const DCPContent> content, bool tolerant)
float const fr = float(frac.numerator) / frac.denominator;
if (!_video_frame_rate) {
_video_frame_rate = fr;
+ _rational_video_frame_rate = frac;
} else if (_video_frame_rate.get() != fr) {
throw DCPError(_("Mismatched frame rates in DCP"));
}
@@ -284,7 +285,7 @@ DCPExaminer::DCPExaminer(shared_ptr<const DCPContent> content, bool tolerant)
if (reel->main_markers ()) {
auto edit_rate = reel->main_markers()->edit_rate().numerator;
for (auto const& marker: reel->main_markers()->get()) {
- _markers[marker.first] = reel_time + dcpomatic::ContentTime::from_frames(marker.second.as_editable_units_floor(edit_rate), edit_rate);
+ _markers[marker.first] = reel_time + dcpomatic::ContentTime::from_frames(marker.second, edit_rate);
}
}
diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h
index c3a916bff..d86aa998c 100644
--- a/src/lib/dcp_examiner.h
+++ b/src/lib/dcp_examiner.h
@@ -49,6 +49,10 @@ public:
return _video_frame_rate;
}
+ boost::optional<dcp::Fraction> rational_video_frame_rate() const {
+ return _rational_video_frame_rate;
+ }
+
boost::optional<dcp::Size> video_size() const override {
return _video_size;
}
@@ -225,6 +229,7 @@ public:
private:
boost::optional<double> _video_frame_rate;
+ boost::optional<dcp::Fraction> _rational_video_frame_rate;
boost::optional<dcp::Size> _video_size;
Frame _video_length = 0;
boost::optional<int> _audio_channels;
diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc
index 775298091..ceaefdcb8 100644
--- a/src/lib/dcp_video.cc
+++ b/src/lib/dcp_video.cc
@@ -46,6 +46,7 @@
#include <dcp/openjpeg_image.h>
#include <dcp/rgb_xyz.h>
#include <dcp/j2k_transcode.h>
+#include <dcp/raw_convert.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <libxml++/libxml++.h>
@@ -67,6 +68,7 @@ using std::cout;
using std::make_shared;
using std::shared_ptr;
using std::string;
+using std::vector;
using dcp::ArrayData;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
@@ -79,7 +81,7 @@ using namespace boost::placeholders;
* @param bit_rate Video bit rate to use.
*/
DCPVideo::DCPVideo(
- shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int64_t bit_rate, Resolution r
+ shared_ptr<const PlayerVideo> frame, int index, dcp::Fraction dcp_fps, int64_t bit_rate, Resolution r
)
: _frame(frame)
, _index(index)
@@ -94,7 +96,7 @@ DCPVideo::DCPVideo(shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml::N
: _frame(frame)
{
_index = node->number_child<int>("Index");
- _frames_per_second = node->number_child<int>("FramesPerSecond");
+ _frames_per_second = dcp::Fraction(node->string_child("FramesPerSecond"));
_video_bit_rate = node->number_child<int64_t>("VideoBitRate");
_resolution = Resolution(node->optional_number_child<int>("Resolution").get_value_or(static_cast<int>(Resolution::TWO_K)));
}
@@ -168,7 +170,7 @@ DCPVideo::encode_locally() const
enc = dcp::compress_j2k(
xyz,
_video_bit_rate,
- _frames_per_second,
+ _frames_per_second.as_float(),
_frame->eyes() == Eyes::LEFT || _frame->eyes() == Eyes::RIGHT,
_resolution == Resolution::FOUR_K,
comment.empty() ? "libdcp" : comment
@@ -281,7 +283,7 @@ void
DCPVideo::add_metadata(xmlpp::Element* el) const
{
cxml::add_text_child(el, "Index", fmt::to_string(_index));
- cxml::add_text_child(el, "FramesPerSecond", fmt::to_string(_frames_per_second));
+ cxml::add_text_child(el, "FramesPerSecond", _frames_per_second.as_string());
cxml::add_text_child(el, "VideoBitRate", fmt::to_string(_video_bit_rate));
cxml::add_text_child(el, "Resolution", fmt::to_string(int(_resolution)));
_frame->add_metadata(el);
diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h
index 6afbedfad..b3e73de4a 100644
--- a/src/lib/dcp_video.h
+++ b/src/lib/dcp_video.h
@@ -49,7 +49,7 @@ class PlayerVideo;
class DCPVideo
{
public:
- DCPVideo(std::shared_ptr<const PlayerVideo>, int index, int dcp_fps, int64_t bit_rate, Resolution r);
+ DCPVideo(std::shared_ptr<const PlayerVideo>, int index, dcp::Fraction dcp_fps, int64_t bit_rate, Resolution r);
DCPVideo(std::shared_ptr<const PlayerVideo>, cxml::ConstNodePtr);
DCPVideo(DCPVideo const&) = default;
@@ -76,10 +76,10 @@ private:
void add_metadata(xmlpp::Element *) const;
std::shared_ptr<const PlayerVideo> _frame;
- int _index; ///< frame index within the DCP's intrinsic duration
- int _frames_per_second; ///< Frames per second that we will use for the DCP
- int64_t _video_bit_rate; ///< Video bit rate to use
- Resolution _resolution; ///< Resolution (2K or 4K)
+ int _index; ///< frame index within the DCP's intrinsic duration
+ dcp::Fraction _frames_per_second; ///< Frames per second that we will use for the DCP
+ int64_t _video_bit_rate; ///< Video bit rate to use
+ Resolution _resolution; ///< Resolution (2K or 4K)
};
#endif
diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h
index 0d15028c6..13cc598b5 100644
--- a/src/lib/dcpomatic_time.h
+++ b/src/lib/dcpomatic_time.h
@@ -30,6 +30,7 @@
#include "dcpomatic_assert.h"
#include "frame_rate_change.h"
+#include <dcp/types.h>
#include <boost/optional.hpp>
#include <stdint.h>
#include <cmath>
@@ -104,6 +105,16 @@ public:
+ from_frames(hmsf.f, fps);
}
+ /** @param hmsf Hours, minutes, seconds, frames.
+ * @param fps Frame rate
+ */
+ Time(HMSF const& hmsf, dcp::Fraction fps) {
+ *this = from_seconds(hmsf.h * 3600)
+ + from_seconds(hmsf.m * 60)
+ + from_seconds(hmsf.s)
+ + from_frames(hmsf.f, fps.as_float());
+ }
+
Type get() const {
return _t;
}
@@ -170,6 +181,10 @@ public:
return Time<S, O>(llrint(HZ * frames_ceil(r) / r));
}
+ Time<S, O> ceil(dcp::Fraction r) const {
+ return Time<S, O>(llrint(HZ * frames_ceil(r) * r.denominator / r.numerator));
+ }
+
Time<S, O> floor(double r) const {
return Time<S, O>(llrint(HZ * frames_floor(r) / r));
}
@@ -178,6 +193,10 @@ public:
return Time<S, O>(llrint(HZ * frames_round(r) / r));
}
+ Time<S, O> round(dcp::Fraction r) const {
+ return Time<S, O>(llrint(HZ * frames_round(r) * r.denominator / r.numerator));
+ }
+
double seconds() const {
return double(_t) / HZ;
}
@@ -195,11 +214,23 @@ public:
return llrint(_t * double(r) / HZ);
}
+ int64_t frames_round(dcp::Fraction r) const {
+ /* We must cast to double here otherwise if T is integer
+ the calculation will round down before we get the chance
+ to llrint().
+ */
+ return llrint(_t * double(r.numerator) / (HZ * r.denominator));
+ }
+
template <typename T>
int64_t frames_floor(T r) const {
return ::floor(_t * r / HZ);
}
+ int64_t frames_floor(dcp::Fraction r) const {
+ return ::floor(_t * r.numerator / (HZ * r.denominator));
+ }
+
template <typename T>
int64_t frames_ceil(T r) const {
/* We must cast to double here otherwise if T is integer
@@ -233,6 +264,25 @@ public:
return hmsf;
}
+ HMSF split(dcp::Fraction r) const
+ {
+ /* Do this calculation with frames so that we can round
+ to a frame boundary at the start rather than the end.
+ */
+ auto ff = frames_round(r);
+ HMSF hmsf;
+
+ hmsf.h = ff / (3600 * r.as_float());
+ ff -= static_cast<int64_t>(hmsf.h) * 3600 * r.as_float();
+ hmsf.m = ff / (60 * r.as_float());
+ ff -= static_cast<int64_t>(hmsf.m) * 60 * r.as_float();
+ hmsf.s = ff / r.as_float();
+ ff -= static_cast<int64_t>(hmsf.s) * r.as_float();
+
+ hmsf.f = static_cast<int>(ff);
+ return hmsf;
+ }
+
template <typename T>
std::string timecode(T r) const {
auto hmsf = split(r);
@@ -252,6 +302,11 @@ public:
return Time<S, O>(f * HZ / r);
}
+ static Time<S, O> from_frames(int64_t f, dcp::Fraction r) {
+ DCPOMATIC_ASSERT(r.numerator > 0);
+ return Time<S, O>(f * HZ * r.denominator / r.numerator);
+ }
+
static Time<S, O> delta() {
return Time<S, O>(1);
}
diff --git a/src/lib/encode_cli.cc b/src/lib/encode_cli.cc
index 80136d5ed..2336bdbfc 100644
--- a/src/lib/encode_cli.cc
+++ b/src/lib/encode_cli.cc
@@ -117,7 +117,7 @@ print_dump(function<void (string)> out, shared_ptr<Film> film)
out(fmt::format("{} at {}\n", film->container().container_nickname(), film->resolution() == Resolution::TWO_K ? "2K" : "4K"));
out(fmt::format("{}Mbit/s\n", film->video_bit_rate(film->video_encoding()) / 1000000));
out(fmt::format("Duration {}\n", film->length().timecode(film->video_frame_rate())));
- out(fmt::format("Output {}fps {} {}kHz\n", film->video_frame_rate(), film->three_d() ? "3D" : "2D", film->audio_frame_rate() / 1000));
+ out(fmt::format("Output {}fps {} {}kHz\n", film->video_frame_rate().as_float(), film->three_d() ? "3D" : "2D", film->audio_frame_rate() / 1000));
out(fmt::format("{} {}\n", film->interop() ? "Inter-Op" : "SMPTE", film->encrypted() ? "encrypted" : "unencrypted"));
for (auto c: film->content()) {
diff --git a/src/lib/ffmpeg_file_encoder.cc b/src/lib/ffmpeg_file_encoder.cc
index cfd82c7dc..97fe25000 100644
--- a/src/lib/ffmpeg_file_encoder.cc
+++ b/src/lib/ffmpeg_file_encoder.cc
@@ -220,7 +220,7 @@ private:
FFmpegFileEncoder::FFmpegFileEncoder(
dcp::Size video_frame_size,
- int video_frame_rate,
+ dcp::Fraction video_frame_rate,
int audio_frame_rate,
int channels,
ExportFormat format,
@@ -339,7 +339,7 @@ FFmpegFileEncoder::setup_video()
_video_codec_context->global_quality = 0;
_video_codec_context->width = _video_frame_size.width;
_video_codec_context->height = _video_frame_size.height;
- _video_codec_context->time_base = (AVRational) { 1, _video_frame_rate };
+ _video_codec_context->time_base = (AVRational) { _video_frame_rate.denominator, _video_frame_rate.numerator };
_video_codec_context->pix_fmt = _pixel_format;
_video_codec_context->flags |= AV_CODEC_FLAG_QSCALE | AV_CODEC_FLAG_GLOBAL_HEADER;
@@ -401,7 +401,7 @@ FFmpegFileEncoder::flush()
throw EncodeError(N_("avcodec_receive_packet"), N_("FFmpegFileEncoder::flush"), r);
} else {
packet->stream_index = _video_stream_index;
- packet->duration = _video_stream->time_base.den / _video_frame_rate;
+ packet->duration = _video_stream->time_base.den * _video_frame_rate.denominator / _video_frame_rate.numerator;
av_interleaved_write_frame(_format_context, packet.get());
}
@@ -459,7 +459,7 @@ FFmpegFileEncoder::video(shared_ptr<PlayerVideo> video, DCPTime time)
throw EncodeError(N_("avcodec_receive_packet"), N_("FFmpegFileEncoder::video"), r);
} else if (r >= 0) {
packet->stream_index = _video_stream_index;
- packet->duration = _video_stream->time_base.den / _video_frame_rate;
+ packet->duration = _video_stream->time_base.den * _video_frame_rate.denominator / _video_frame_rate.numerator;
av_interleaved_write_frame(_format_context, packet.get());
}
}
@@ -475,7 +475,7 @@ FFmpegFileEncoder::audio(shared_ptr<AudioBuffers> audio)
int frame_size = _audio_streams[0]->frame_size();
if (frame_size == 0) {
/* codec has AV_CODEC_CAP_VARIABLE_FRAME_SIZE */
- frame_size = _audio_frame_rate / _video_frame_rate;
+ frame_size = _audio_frame_rate * _video_frame_rate.denominator / _video_frame_rate.numerator;
}
while (_pending_audio->frames() >= frame_size) {
diff --git a/src/lib/ffmpeg_file_encoder.h b/src/lib/ffmpeg_file_encoder.h
index a975d4519..b4289be6f 100644
--- a/src/lib/ffmpeg_file_encoder.h
+++ b/src/lib/ffmpeg_file_encoder.h
@@ -59,7 +59,7 @@ class FFmpegFileEncoder
public:
FFmpegFileEncoder(
dcp::Size video_frame_size,
- int video_frame_rate,
+ dcp::Fraction video_frame_rate,
int audio_frame_rate,
int channels,
ExportFormat,
@@ -99,7 +99,7 @@ private:
boost::filesystem::path _output;
dcp::Size _video_frame_size;
- int _video_frame_rate;
+ dcp::Fraction _video_frame_rate;
int _audio_frame_rate;
int64_t _audio_frames = 0;
diff --git a/src/lib/ffmpeg_film_encoder.cc b/src/lib/ffmpeg_film_encoder.cc
index 9a9cba79c..602524fdf 100644
--- a/src/lib/ffmpeg_film_encoder.cc
+++ b/src/lib/ffmpeg_film_encoder.cc
@@ -247,7 +247,7 @@ FFmpegFilmEncoder::frames_done() const
FFmpegFilmEncoder::FileEncoderSet::FileEncoderSet(
dcp::Size video_frame_size,
- int video_frame_rate,
+ dcp::Fraction video_frame_rate,
int audio_frame_rate,
int channels,
ExportFormat format,
diff --git a/src/lib/ffmpeg_film_encoder.h b/src/lib/ffmpeg_film_encoder.h
index 2e06b770e..a1b1656ac 100644
--- a/src/lib/ffmpeg_film_encoder.h
+++ b/src/lib/ffmpeg_film_encoder.h
@@ -57,7 +57,7 @@ private:
public:
FileEncoderSet(
dcp::Size video_frame_size,
- int video_frame_rate,
+ dcp::Fraction video_frame_rate,
int audio_frame_rate,
int channels,
ExportFormat,
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 750b2b41c..a76eba470 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -145,8 +145,10 @@ static constexpr char assets_file[] = "assets.xml";
* VideoContent scale expressed just as "guess" or "custom"
* 38 -> 39
* Fade{In,Out} -> VideoFade{In,Out}
+ * 39 -> 40
+ * VideoFrameRate becomes rational.
*/
-int const Film::current_state_version = 39;
+int const Film::current_state_version = 40;
/** Construct a Film object in a given directory.
@@ -172,7 +174,7 @@ Film::Film(optional<boost::filesystem::path> dir)
, _encrypt_sound(false)
, _encrypt_text(false)
, _context_id(dcp::make_uuid())
- , _video_frame_rate(24)
+ , _video_frame_rate(24, 1)
, _audio_channels(6)
, _three_d(false)
, _sequence(true)
@@ -411,7 +413,7 @@ Film::metadata(bool with_content_paths) const
cxml::add_text_child(root, "Resolution", resolution_to_string(_resolution));
cxml::add_text_child(root, "J2KVideoBitRate", fmt::to_string(_video_bit_rate[VideoEncoding::JPEG2000]));
cxml::add_text_child(root, "MPEG2VideoBitRate", fmt::to_string(_video_bit_rate[VideoEncoding::MPEG2]));
- cxml::add_text_child(root, "VideoFrameRate", fmt::to_string(_video_frame_rate));
+ cxml::add_text_child(root, "VideoFrameRate", _video_frame_rate.as_string());
cxml::add_text_child(root, "AudioFrameRate", fmt::to_string(_audio_frame_rate));
cxml::add_text_child(root, "ISDCFDate", boost::gregorian::to_iso_string(_isdcf_date));
cxml::add_text_child(root, "AudioChannels", fmt::to_string(_audio_channels));
@@ -598,7 +600,19 @@ Film::read_metadata(optional<boost::filesystem::path> path)
_video_bit_rate[VideoEncoding::JPEG2000] = f.number_child<int64_t>("J2KVideoBitRate");
}
_video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("MPEG2VideoBitRate").get_value_or(5000000);
- _video_frame_rate = f.number_child<int>("VideoFrameRate");
+
+ {
+ auto vfr = f.string_child("VideoFrameRate");
+ vector<string> parts;
+ boost::split(parts, vfr, boost::is_any_of(" "));
+ DCPOMATIC_ASSERT(parts.size() == 1 || parts.size() == 2);
+ if (parts.size() == 1) {
+ _video_frame_rate = dcp::Fraction(dcp::raw_convert<int>(parts[0]), 1);
+ } else {
+ _video_frame_rate = dcp::Fraction(dcp::raw_convert<int>(parts[0]), dcp::raw_convert<int>(parts[1]));
+ }
+ }
+
_audio_frame_rate = f.optional_number_child<int>("AudioFrameRate").get_value_or(48000);
auto encrypted = f.optional_bool_child("Encrypted").get_value_or(false);
_encrypt_picture = f.optional_bool_child("EncryptPicture").get_value_or(encrypted);
@@ -1020,8 +1034,8 @@ Film::isdcf_name(bool if_created_now) const
isdcf_name += fmt::format("-{}fl", std::round(_luminance->value_in_foot_lamberts() * 10));
}
- if (video_frame_rate() != 24) {
- isdcf_name += "-" + fmt::to_string(video_frame_rate());
+ if (video_frame_rate() != dcp::Fraction(24, 1)) {
+ isdcf_name += "-" + fmt::to_string(std::round(video_frame_rate().as_float()));
}
isdcf_name += "_" + container().isdcf_name();
@@ -1244,12 +1258,13 @@ Film::set_video_bit_rate(VideoEncoding encoding, int64_t bit_rate)
_video_bit_rate[encoding] = bit_rate;
}
+
/** @param f New frame rate.
* @param user_explicit true if this comes from a direct user instruction, false if it is from
* DCP-o-matic being helpful.
*/
void
-Film::set_video_frame_rate(int f, bool user_explicit)
+Film::set_video_frame_rate(dcp::Fraction f, bool user_explicit)
{
if (_video_frame_rate == f) {
return;
@@ -1262,6 +1277,18 @@ Film::set_video_frame_rate(int f, bool user_explicit)
}
}
+
+/** @param f New frame rate.
+ * @param user_explicit true if this comes from a direct user instruction, false if it is from
+ * DCP-o-matic being helpful.
+ */
+void
+Film::set_video_frame_rate(int f, bool user_explicit)
+{
+ set_video_frame_rate({f, 1}, user_explicit);
+}
+
+
void
Film::set_audio_channels(int c)
{
@@ -1673,12 +1700,12 @@ Film::length() const
return max(DCPTime::from_seconds(1), _playlist->length(shared_from_this()).ceil(video_frame_rate()));
}
-int
+dcp::Fraction
Film::best_video_frame_rate() const
{
/* Don't default to anything above 30fps (make the user select that explicitly) */
auto best = _playlist->best_video_frame_rate();
- if (best > 30) {
+ if (best.fraction() > 30) {
best /= 2;
}
return best;
@@ -2122,7 +2149,11 @@ Film::reels_for_type(ReelType type) const
/* Integer-divide reel length by the size of one frame to give the number of frames per reel,
* making sure we don't go less than 1s long.
*/
- Frame const reel_in_frames = max(_reel_length / ((video_bit_rate(video_encoding()) / video_frame_rate()) / 8), static_cast<Frame>(video_frame_rate()));
+ auto const minimum = std::ceil(video_frame_rate().as_float());
+ Frame const reel_in_frames = max(
+ _reel_length / ((video_bit_rate(video_encoding()) * video_frame_rate().denominator / video_frame_rate().numerator) / 8),
+ static_cast<int64_t>(minimum)
+ );
while (current < len) {
DCPTime end = min(len, current + DCPTime::from_frames(reel_in_frames, video_frame_rate()));
periods.emplace_back(current, end);
diff --git a/src/lib/film.h b/src/lib/film.h
index 142f6c374..6942ac600 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -151,7 +151,7 @@ public:
ContentList content() const;
dcpomatic::DCPTime length() const;
- int best_video_frame_rate() const;
+ dcp::Fraction best_video_frame_rate() const;
FrameRateChange active_frame_rate_change(dcpomatic::DCPTime) const;
std::pair<double, double> speed_up_range(int dcp_frame_rate) const;
@@ -253,7 +253,7 @@ public:
}
/** @return The frame rate of the DCP */
- int video_frame_rate() const {
+ dcp::Fraction video_frame_rate() const {
return _video_frame_rate;
}
@@ -406,6 +406,7 @@ public:
void set_encrypt_sound(bool);
void set_encrypt_text(bool);
void set_video_bit_rate(VideoEncoding encoding, int64_t);
+ void set_video_frame_rate(dcp::Fraction rate, bool user_explicit = true);
void set_video_frame_rate(int rate, bool user_explicit = true);
void set_audio_channels(int);
void set_three_d(bool);
@@ -534,7 +535,7 @@ private:
/** bit rate for encoding video using in bits per second */
EnumIndexedVector<int64_t, VideoEncoding> _video_bit_rate;
/** Frames per second to run our DCP at */
- int _video_frame_rate;
+ dcp::Fraction _video_frame_rate;
/** The date that we should use in a ISDCF name */
boost::gregorian::date _isdcf_date;
/** Number of audio channels requested for the DCP */
diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc
index 48f5b9881..8f6397d56 100644
--- a/src/lib/frame_rate_change.cc
+++ b/src/lib/frame_rate_change.cc
@@ -22,6 +22,7 @@
#include "content.h"
#include "film.h"
#include "frame_rate_change.h"
+#include <dcp/types.h>
#include <cmath>
#include "i18n.h"
@@ -32,29 +33,30 @@ using std::string;
FrameRateChange::FrameRateChange()
+ : dcp(24, 1)
{
}
-FrameRateChange::FrameRateChange(double source_, int dcp_)
+FrameRateChange::FrameRateChange(double source_, dcp::Fraction dcp_)
: source(source_)
, dcp(dcp_)
{
- if (fabs(source / 2.0 - dcp) < fabs(source - dcp)) {
+ if (fabs(source / 2.0 - dcp.as_float()) < fabs(source - dcp.as_float())) {
/* The difference between source and DCP frame rate will be lower
(i.e. better) if we skip.
*/
skip = true;
- } else if (fabs(source * 2 - dcp) < fabs(source - dcp)) {
+ } else if (fabs(source * 2 - dcp.as_float()) < fabs(source - dcp.as_float())) {
/* The difference between source and DCP frame rate would be better
if we repeated each frame once; it may be better still if we
repeated more than once. Work out the required repeat.
*/
- repeat = round(dcp / source);
+ repeat = round(dcp.as_float() / source);
}
- speed_up = dcp / (source * factor());
+ speed_up = dcp.as_float() / (source * factor());
auto about_equal = [](double a, double b) {
return (fabs(a - b) < VIDEO_FRAME_RATE_EPSILON);
@@ -95,7 +97,7 @@ FrameRateChange::description() const
}
if (change_speed) {
- double const pc = dcp * 100 / (source * factor());
+ double const pc = dcp.as_float() * 100 / (source * factor());
char buffer[256];
snprintf(buffer, sizeof(buffer), _("DCP will run at %.1f%% of the content speed.\n"), pc);
description += buffer;
diff --git a/src/lib/frame_rate_change.h b/src/lib/frame_rate_change.h
index 84c4c340d..9b01a0e1a 100644
--- a/src/lib/frame_rate_change.h
+++ b/src/lib/frame_rate_change.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_FRAME_RATE_CHANGE_H
+#include <dcp/types.h>
#include <memory>
#include <string>
@@ -35,7 +36,7 @@ class FrameRateChange
{
public:
FrameRateChange();
- FrameRateChange(double, int);
+ FrameRateChange(double, dcp::Fraction);
FrameRateChange(std::shared_ptr<const Film> film, std::shared_ptr<const Content> content);
FrameRateChange(std::shared_ptr<const Film> film, Content const * content);
@@ -51,7 +52,7 @@ public:
}
double source = 24;
- int dcp = 24;
+ dcp::Fraction dcp;
/** true to skip every other frame */
bool skip = false;
diff --git a/src/lib/grok/context.h b/src/lib/grok/context.h
index b31867cf6..106c4e828 100644
--- a/src/lib/grok/context.h
+++ b/src/lib/grok/context.h
@@ -226,7 +226,7 @@ public:
12,
device,
_dcpomatic_context->film->resolution() == Resolution::FOUR_K,
- _dcpomatic_context->film->video_frame_rate(),
+ std::round(_dcpomatic_context->film->video_frame_rate().as_float()),
_dcpomatic_context->film->video_bit_rate(VideoEncoding::JPEG2000),
grok.licence_server,
grok.licence)) {
diff --git a/src/lib/hints.cc b/src/lib/hints.cc
index 99882ec0e..ba1936479 100644
--- a/src/lib/hints.cc
+++ b/src/lib/hints.cc
@@ -181,32 +181,21 @@ Hints::check_high_video_bit_rate()
void
Hints::check_frame_rate()
{
- auto f = film();
- switch (f->video_frame_rate()) {
- case 24:
- /* Fine */
- break;
- case 25:
- {
+ auto const vfr = film()->video_frame_rate();
+ if (vfr == dcp::Fraction(25, 1)) {
/* You might want to go to 24 */
string base = fmt::format(_("You are set up for a DCP at a frame rate of {} fps. This frame rate is not supported by all projectors. You may want to consider changing your frame rate to {} fps."), 25, 24);
- if (f->interop()) {
+ if (film()->interop()) {
base += " ";
base += _("If you do use 25fps you should change your DCP standard to SMPTE.");
}
hint(base);
- break;
- }
- case 30:
+ } else if (vfr == dcp::Fraction(30, 1)) {
/* 30fps: we can't really offer any decent solutions */
hint(_("You are set up for a DCP frame rate of 30fps, which is not supported by all projectors. Be aware that you may have compatibility problems."));
- break;
- case 48:
- case 50:
- case 60:
+ } else if (vfr == dcp::Fraction(48, 1) || vfr == dcp::Fraction(50, 1) || vfr == dcp::Fraction(60, 1)) {
/* You almost certainly want to go to half frame rate */
- hint(fmt::format(_("You are set up for a DCP at a frame rate of {} fps. This frame rate is not supported by all projectors. It is advisable to change the DCP frame rate to {} fps."), f->video_frame_rate(), f->video_frame_rate() / 2));
- break;
+ hint(fmt::format(_("You are set up for a DCP at a frame rate of {} fps. This frame rate is not supported by all projectors. It is advisable to change the DCP frame rate to {} fps."), vfr.numerator, vfr.numerator / 2));
}
}
@@ -624,7 +613,7 @@ Hints::open_subtitle(PlayerText text, DCPTimePeriod period)
hint(_("It is advisable to put your first subtitle at least 4 seconds after the start of the DCP to make sure it is seen."));
}
- int const vfr = film()->video_frame_rate();
+ auto const vfr = film()->video_frame_rate();
if (period.duration().frames_round(vfr) < 15 && !_short_subtitle) {
_short_subtitle = true;
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
index 437508f4f..84eae1636 100644
--- a/src/lib/image_content.cc
+++ b/src/lib/image_content.cc
@@ -148,7 +148,7 @@ void
ImageContent::prepare_for_add_to_film(shared_ptr<const Film> film)
{
if (still()) {
- video->set_length(Config::instance()->default_still_length() * video_frame_rate().get_value_or(film->video_frame_rate()));
+ video->set_length(Config::instance()->default_still_length() * video_frame_rate().get_value_or(film->video_frame_rate().as_float()));
}
}
diff --git a/src/lib/make_dcp.cc b/src/lib/make_dcp.cc
index ca330672f..e1390266c 100644
--- a/src/lib/make_dcp.cc
+++ b/src/lib/make_dcp.cc
@@ -86,7 +86,7 @@ make_dcp(shared_ptr<Film> film, TranscodeJob::ChangedBehaviour behaviour)
for (auto content: film->content()) {
LOG_GENERAL("Content: {}", content->technical_summary());
}
- LOG_GENERAL("DCP video rate {} fps", film->video_frame_rate());
+ LOG_GENERAL("DCP video rate {} fps", film->video_frame_rate().as_float());
LOG_GENERAL("Video bit rate {}", film->video_bit_rate(film->video_encoding()));
auto tj = make_shared<DCPTranscodeJob>(film, behaviour);
diff --git a/src/lib/mpeg2_encoder.cc b/src/lib/mpeg2_encoder.cc
index d7c570988..228463d97 100644
--- a/src/lib/mpeg2_encoder.cc
+++ b/src/lib/mpeg2_encoder.cc
@@ -32,9 +32,9 @@ using std::shared_ptr;
MPEG2Encoder::MPEG2Encoder(shared_ptr<const Film> film, Writer& writer)
: VideoEncoder(film, writer)
- , _transcoder(film->frame_size(), film->video_frame_rate(), film->video_bit_rate(VideoEncoding::MPEG2))
+ , _transcoder(film->frame_size(), film->video_frame_rate().numerator, film->video_bit_rate(VideoEncoding::MPEG2))
{
-
+ DCPOMATIC_ASSERT(film->video_frame_rate().denominator == 1);
}
@@ -43,7 +43,7 @@ MPEG2Encoder::encode(shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time)
{
auto image = pv->image(force(AV_PIX_FMT_YUV420P), VideoRange::VIDEO, false);
- dcp::FFmpegImage ffmpeg_image(time.get() * _film->video_frame_rate() / dcpomatic::DCPTime::HZ);
+ dcp::FFmpegImage ffmpeg_image(time.get() * _film->video_frame_rate().numerator / dcpomatic::DCPTime::HZ);
DCPOMATIC_ASSERT(image->size() == ffmpeg_image.size());
diff --git a/src/lib/player.cc b/src/lib/player.cc
index c6ef90395..b8c85a8cd 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -927,7 +927,7 @@ Player::open_texts_for_frame(DCPTime time) const
}
list<PositionImage> texts;
- int const vfr = film->video_frame_rate();
+ auto const vfr = film->video_frame_rate();
for (auto type: { TextType::OPEN_SUBTITLE, TextType::OPEN_CAPTION }) {
for (
@@ -1002,7 +1002,7 @@ Player::emit_video_until(DCPTime time)
auto const age_threshold = [this, film](pair<shared_ptr<PlayerVideo>, dcpomatic::DCPTime> video) {
if (auto content = video.first->content().lock()) {
- return one_video_frame() * (std::ceil(film->video_frame_rate() / content->video_frame_rate().get_value_or(24)) + 1);
+ return one_video_frame() * (std::ceil(film->video_frame_rate().as_float() / content->video_frame_rate().get_value_or(24)) + 1);
} else {
return one_video_frame() * 2;
}
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
index 9a292ce27..b85859a6f 100644
--- a/src/lib/playlist.cc
+++ b/src/lib/playlist.cc
@@ -550,7 +550,7 @@ Playlist::text_end(shared_ptr<const Film> film) const
FrameRateChange
-Playlist::active_frame_rate_change(DCPTime t, int dcp_video_frame_rate) const
+Playlist::active_frame_rate_change(DCPTime t, dcp::Fraction dcp_video_frame_rate) const
{
auto cont = content();
for (ContentList::const_reverse_iterator i = cont.rbegin(); i != cont.rend(); ++i) {
@@ -567,12 +567,12 @@ Playlist::active_frame_rate_change(DCPTime t, int dcp_video_frame_rate) const
return FrameRateChange((*i)->video_frame_rate().get(), dcp_video_frame_rate);
} else {
/* No specified rate so just use the DCP one */
- return FrameRateChange(dcp_video_frame_rate, dcp_video_frame_rate);
+ return FrameRateChange(dcp_video_frame_rate.as_float(), dcp_video_frame_rate);
}
}
}
- return FrameRateChange(dcp_video_frame_rate, dcp_video_frame_rate);
+ return FrameRateChange(dcp_video_frame_rate.as_float(), dcp_video_frame_rate);
}
@@ -720,7 +720,7 @@ Playlist::content_summary(shared_ptr<const Film> film, DCPTimePeriod period) con
pair<double, double>
-Playlist::speed_up_range(int dcp_video_frame_rate) const
+Playlist::speed_up_range(dcp::Fraction dcp_video_frame_rate) const
{
pair<double, double> range(DBL_MAX, -DBL_MAX);
@@ -733,7 +733,7 @@ Playlist::speed_up_range(int dcp_video_frame_rate) const
range.first = min(range.first, frc.speed_up);
range.second = max(range.second, frc.speed_up);
} else {
- FrameRateChange const frc(dcp_video_frame_rate, dcp_video_frame_rate);
+ FrameRateChange const frc(dcp_video_frame_rate.as_float(), dcp_video_frame_rate);
range.first = min(range.first, frc.speed_up);
range.second = max(range.second, frc.speed_up);
}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
index 6c1524003..1de25bfe1 100644
--- a/src/lib/playlist.h
+++ b/src/lib/playlist.h
@@ -71,12 +71,12 @@ public:
boost::optional<dcpomatic::DCPTime> start() const;
int64_t required_disk_space(std::shared_ptr<const Film> film, int64_t video_bit_rate, int audio_channels, int audio_frame_rate) const;
- int best_video_frame_rate() const;
+ dcp::Fraction best_video_frame_rate() const;
dcpomatic::DCPTime video_end(std::shared_ptr<const Film> film) const;
dcpomatic::DCPTime text_end(std::shared_ptr<const Film> film) const;
- FrameRateChange active_frame_rate_change(dcpomatic::DCPTime, int dcp_frame_rate) const;
+ FrameRateChange active_frame_rate_change(dcpomatic::DCPTime, dcp::Fraction dcp_frame_rate) const;
std::string content_summary(std::shared_ptr<const Film> film, dcpomatic::DCPTimePeriod period) const;
- std::pair<double, double> speed_up_range(int dcp_video_frame_rate) const;
+ std::pair<double, double> speed_up_range(dcp::Fraction dcp_video_frame_rate) const;
void set_sequence(bool);
void maybe_sequence(std::shared_ptr<const Film> film);
diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc
index b85b53305..ba78114f3 100644
--- a/src/lib/reel_writer.cc
+++ b/src/lib/reel_writer.cc
@@ -140,7 +140,7 @@ ReelWriter::ReelWriter(
if (_first_nonexistent_frame < period.duration().frames_round(film()->video_frame_rate())) {
/* No existing asset, or an incomplete one */
- auto const rate = dcp::Fraction(film()->video_frame_rate(), 1);
+ auto const rate = film()->video_frame_rate();
auto setup = [this](shared_ptr<dcp::PictureAsset> asset) {
asset->set_size(film()->frame_size());
@@ -214,7 +214,7 @@ ReelWriter::ReelWriter(
if (film()->audio_channels()) {
auto lang = film()->audio_language();
_sound_asset = make_shared<dcp::SoundAsset>(
- dcp::Fraction(film()->video_frame_rate(), 1),
+ film()->video_frame_rate(),
film()->audio_frame_rate(),
film()->audio_channels(),
lang ? *lang : dcp::LanguageTag("en-US"),
@@ -314,7 +314,7 @@ void
ReelWriter::write(shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadata)
{
if (!_atmos_asset) {
- _atmos_asset = metadata.create(dcp::Fraction(film()->video_frame_rate(), 1));
+ _atmos_asset = metadata.create(film()->video_frame_rate());
if (film()->encrypt_sound()) {
_atmos_asset->set_key(film()->key());
}
@@ -446,7 +446,7 @@ maybe_add_text(
reel_asset = make_shared<Interop>(
type,
interop,
- dcp::Fraction(film->video_frame_rate(), 1),
+ film->video_frame_rate(),
picture_duration,
0
);
@@ -463,7 +463,7 @@ maybe_add_text(
reel_asset = make_shared<SMPTE>(
type,
smpte,
- dcp::Fraction(film->video_frame_rate(), 1),
+ film->video_frame_rate(),
picture_duration,
0
);
@@ -681,11 +681,10 @@ ReelWriter::create_reel_markers(shared_ptr<dcp::Reel> reel) const
}
if (!reel_markers.empty()) {
- auto ma = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(film()->video_frame_rate(), 1), reel->duration());
+ auto ma = make_shared<dcp::ReelMarkersAsset>(film()->video_frame_rate(), reel->duration());
for (auto const& i: reel_markers) {
DCPTime relative = i.second - _period.from;
- auto hmsf = relative.split(film()->video_frame_rate());
- ma->set(i.first, dcp::Time(hmsf.h, hmsf.m, hmsf.s, hmsf.f, film()->video_frame_rate()));
+ ma->set(i.first, relative.frames_round(film()->video_frame_rate()));
}
reel->add(ma);
}
@@ -818,9 +817,13 @@ ReelWriter::empty_text_asset(TextType type, optional<DCPTextTrack> track, bool w
} else if (track && track->language) {
s->set_language(dcp::LanguageTag(track->language->as_string()));
}
- s->set_edit_rate(dcp::Fraction(film()->video_frame_rate(), 1));
+ s->set_edit_rate(film()->video_frame_rate());
s->set_reel_number(_reel_index + 1);
- s->set_time_code_rate(film()->video_frame_rate());
+ if (film()->video_frame_rate().denominator == 1) {
+ s->set_time_code_rate(film()->video_frame_rate().numerator);
+ } else {
+ s->set_time_code_rate(10 * film()->video_frame_rate().as_float());
+ }
s->set_start_time(dcp::Time());
if (film()->encrypt_text()) {
s->set_key(film()->key());
diff --git a/src/lib/referenced_reel_asset.cc b/src/lib/referenced_reel_asset.cc
index 5ef3b9ae7..ef7785e97 100644
--- a/src/lib/referenced_reel_asset.cc
+++ b/src/lib/referenced_reel_asset.cc
@@ -43,7 +43,7 @@ using namespace dcpomatic;
static void
-maybe_add_asset (list<ReferencedReelAsset>& a, shared_ptr<dcp::ReelAsset> r, Frame reel_trim_start, Frame reel_trim_end, DCPTime from, int const ffr)
+maybe_add_asset(list<ReferencedReelAsset>& a, shared_ptr<dcp::ReelAsset> r, Frame reel_trim_start, Frame reel_trim_end, DCPTime from, dcp::Fraction const ffr)
{
DCPOMATIC_ASSERT (r);
r->set_entry_point (r->entry_point().get_value_or(0) + reel_trim_start);
@@ -81,9 +81,9 @@ get_referenced_reel_assets(shared_ptr<const Film> film, shared_ptr<const Playlis
}
auto const frame_rate = film->video_frame_rate();
- DCPOMATIC_ASSERT (dcp->video_frame_rate());
+ DCPOMATIC_ASSERT(dcp->rational_video_frame_rate());
/* We should only be referencing if the DCP rate is the same as the film rate */
- DCPOMATIC_ASSERT (std::round(dcp->video_frame_rate().get()) == frame_rate);
+ DCPOMATIC_ASSERT(*dcp->rational_video_frame_rate() == frame_rate);
Frame const trim_start = dcp->trim_start().frames_round(frame_rate);
Frame const trim_end = dcp->trim_end().frames_round(frame_rate);
diff --git a/src/lib/render_text.cc b/src/lib/render_text.cc
index 3dcc317fa..c5e0d4247 100644
--- a/src/lib/render_text.cc
+++ b/src/lib/render_text.cc
@@ -208,7 +208,7 @@ create_surface(shared_ptr<Image> image)
static float
-calculate_fade_factor(StringText const& first, DCPTime time, int frame_rate)
+calculate_fade_factor(StringText const& first, DCPTime time, dcp::Fraction frame_rate)
{
float fade_factor = 1;
@@ -338,7 +338,7 @@ struct Layout
* at the same time and with the same fade in/out.
*/
static Layout
-setup_layout(vector<StringText> subtitles, dcp::Size target, DCPTime time, int frame_rate)
+setup_layout(vector<StringText> subtitles, dcp::Size target, DCPTime time, dcp::Fraction frame_rate)
{
DCPOMATIC_ASSERT(!subtitles.empty());
auto const& first = subtitles.front();
@@ -379,7 +379,7 @@ border_width_for_subtitle(StringText const& subtitle, dcp::Size target)
* at the same time and with the same fade in/out.
*/
static PositionImage
-render_line(vector<StringText> subtitles, dcp::Size target, DCPTime time, int frame_rate)
+render_line(vector<StringText> subtitles, dcp::Size target, DCPTime time, dcp::Fraction frame_rate)
{
/* XXX: this method can only handle italic / bold changes mid-line,
nothing else yet.
@@ -464,7 +464,7 @@ render_line(vector<StringText> subtitles, dcp::Size target, DCPTime time, int fr
* @param frame_rate DCP frame rate.
*/
vector<PositionImage>
-render_text(vector<StringText> subtitles, dcp::Size target, DCPTime time, int frame_rate)
+render_text(vector<StringText> subtitles, dcp::Size target, DCPTime time, dcp::Fraction frame_rate)
{
vector<StringText> pending;
vector<PositionImage> images;
@@ -501,7 +501,7 @@ bounding_box(vector<StringText> subtitles, dcp::Size target, optional<dcp::Subti
auto const& subtitle = pending.front();
auto standard = override_standard.get_value_or(subtitle.valign_standard);
/* We can provide dummy values for time and frame rate here as they are only used to calculate fades */
- auto layout = setup_layout(pending, target, DCPTime(), 24);
+ auto layout = setup_layout(pending, target, DCPTime(), {24, 1});
int const x = x_position(subtitle.h_align(), subtitle.h_position(), target.width, layout.size.width);
auto const border_width = border_width_for_subtitle(subtitle, target);
int const y = y_position(standard, subtitle.v_align(), subtitle.v_position(), target.height, layout.baseline_to_bottom(border_width), layout.size.height);
diff --git a/src/lib/render_text.h b/src/lib/render_text.h
index ff34dc10d..e073bc007 100644
--- a/src/lib/render_text.h
+++ b/src/lib/render_text.h
@@ -33,7 +33,7 @@ namespace dcpomatic {
std::string marked_up(std::vector<StringText> subtitles, int target_height, float fade_factor, std::string font_name);
-std::vector<PositionImage> render_text(std::vector<StringText>, dcp::Size, dcpomatic::DCPTime, int);
+std::vector<PositionImage> render_text(std::vector<StringText>, dcp::Size, dcpomatic::DCPTime, dcp::Fraction);
std::vector<dcpomatic::Rect<int>> bounding_box(std::vector<StringText> subtitles, dcp::Size target, boost::optional<dcp::SubtitleStandard> override_standard = boost::none);
diff --git a/src/lib/subtitle_film_encoder.cc b/src/lib/subtitle_film_encoder.cc
index 4a29ebcfa..4c4a97b5a 100644
--- a/src/lib/subtitle_film_encoder.cc
+++ b/src/lib/subtitle_film_encoder.cc
@@ -177,9 +177,13 @@ SubtitleFilmEncoder::text(PlayerText subs, TextType type, optional<DCPTextTrack>
} else if (track->language) {
s->set_language(track->language.get());
}
- s->set_edit_rate(dcp::Fraction(_film->video_frame_rate(), 1));
+ s->set_edit_rate(_film->video_frame_rate());
s->set_reel_number(_reel_index + 1);
- s->set_time_code_rate(_film->video_frame_rate());
+ if (_film->video_frame_rate().denominator == 1) {
+ s->set_time_code_rate(_film->video_frame_rate().numerator);
+ } else {
+ s->set_time_code_rate(10 * _film->video_frame_rate().as_float());
+ }
s->set_start_time(dcp::Time());
if (_film->encrypted()) {
s->set_key(_film->key());
diff --git a/src/lib/types.h b/src/lib/types.h
index cb6d46369..fdc58c0cc 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -44,8 +44,9 @@ class FFmpegContent;
* 64 - first version used
* 65 - v2.16.0 - checksums added to communication
* 66 - v2.17.x - J2KBandwidth -> VideoBitRate in metadata
+ * 67 - v2.19.x - FramesPerSecond in XML description became a fraction
*/
-#define SERVER_LINK_VERSION (64+2)
+#define SERVER_LINK_VERSION (64+3)
/** A film of F seconds at f FPS will be Ff frames;
Consider some delta FPS d, so if we run the same
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 4cb4ab963..01a84f31d 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -172,6 +172,24 @@ time_to_hmsf(DCPTime time, Frame rate)
return buffer;
}
+
+string
+time_to_hmsf(DCPTime time, dcp::Fraction rate)
+{
+ Frame f = time.frames_round(rate);
+ int s = f / rate.as_float();
+ f -= (s * rate.as_float());
+ int m = s / 60;
+ s -= m * 60;
+ int h = m / 60;
+ m -= h * 60;
+
+ char buffer[64];
+ snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%02d", h, m, s, static_cast<int>(f));
+ return buffer;
+}
+
+
/** @param s Number of seconds.
* @return String containing an approximate description of s (e.g. "about 2 hours")
*/
diff --git a/src/lib/util.h b/src/lib/util.h
index aa003ff00..29841978d 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -62,6 +62,7 @@ class TextDecoder;
extern std::string seconds_to_hms(int);
extern std::string time_to_hmsf(dcpomatic::DCPTime time, Frame rate);
+extern std::string time_to_hmsf(dcpomatic::DCPTime time, dcp::Fraction rate);
extern std::string seconds_to_approximate_hms(int);
extern double seconds(struct timeval);
extern void dcpomatic_setup();
diff --git a/src/wx/dcp_timeline.cc b/src/wx/dcp_timeline.cc
index d8b9c7115..1d58fc45f 100644
--- a/src/wx/dcp_timeline.cc
+++ b/src/wx/dcp_timeline.cc
@@ -65,7 +65,7 @@ enum {
class ReelBoundary
{
public:
- ReelBoundary(wxWindow* parent, wxGridBagSizer* sizer, int index, DCPTime maximum, int fps, DCPTimeline& timeline, bool editable)
+ ReelBoundary(wxWindow* parent, wxGridBagSizer* sizer, int index, DCPTime maximum, dcp::Fraction fps, DCPTimeline& timeline, bool editable)
: _label(new wxStaticText(parent, wxID_ANY, wxString::Format(_("Reel %d to reel %d"), index + 1, index + 2)))
, _timecode(new Timecode<DCPTime>(parent, true))
, _index(index)
@@ -133,7 +133,7 @@ private:
Timecode<dcpomatic::DCPTime>* _timecode = nullptr;
int _index = 0;
DCPTimelineReelMarkerView _view;
- int _fps;
+ dcp::Fraction _fps;
};
diff --git a/src/wx/standard_controls.cc b/src/wx/standard_controls.cc
index 03934e482..ead226c10 100644
--- a/src/wx/standard_controls.cc
+++ b/src/wx/standard_controls.cc
@@ -67,7 +67,7 @@ StandardControls::play_clicked ()
void
StandardControls::check_play_state ()
{
- if (!_film || _film->video_frame_rate() == 0) {
+ if (!_film) {
return;
}
diff --git a/src/wx/timecode.h b/src/wx/timecode.h
index 96609155f..6a8043e2b 100644
--- a/src/wx/timecode.h
+++ b/src/wx/timecode.h
@@ -81,7 +81,8 @@ public:
}
- void set (T t, float fps)
+ template <class F>
+ void set(T t, F fps)
{
auto const hmsf = t.split (fps);
@@ -93,7 +94,8 @@ public:
checked_set (_fixed, t.timecode (fps));
}
- void set_hint (T t, float fps)
+ template <class F>
+ void set_hint(T t, F fps)
{
auto hmsf = t.split (fps);
@@ -124,7 +126,8 @@ public:
value_or_hint(_frames) };
}
- T get (float fps) const
+ template <class F>
+ T get(F fps) const
{
return T(get(), fps);
}