summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2026-02-28 09:06:44 +0100
committerCarl Hetherington <cth@carlh.net>2026-02-28 09:08:46 +0100
commitbcf62b5e4a268554b7960e5f739b24f9eafb38a5 (patch)
tree904fb5959dcd2401daba2d42c53d3a0736604bff /src
parentbb04b67f593c1da37b76af9507f3e28e9ab15f74 (diff)
WIP: use fraction for DCP frame rate.archive-frame-rates
This will allow archival rates to be expressed precisely.
Diffstat (limited to 'src')
-rw-r--r--src/lib/create_cli.cc18
-rw-r--r--src/lib/create_cli.h2
-rw-r--r--src/lib/dcp_video.cc2
-rw-r--r--src/lib/dcp_video.h10
-rw-r--r--src/lib/dcpomatic_time.h22
-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.h10
-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.cc20
-rw-r--r--src/lib/referenced_reel_asset.cc2
14 files changed, 71 insertions, 44 deletions
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_video.cc b/src/lib/dcp_video.cc
index 775298091..a1dfd7c49 100644
--- a/src/lib/dcp_video.cc
+++ b/src/lib/dcp_video.cc
@@ -79,7 +79,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)
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..a388b6dc6 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>
@@ -178,6 +179,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 +200,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
@@ -252,6 +269,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/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.h b/src/lib/film.h
index 142f6c374..694c2dbb4 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -151,8 +151,8 @@ public:
ContentList content() const;
dcpomatic::DCPTime length() const;
- int best_video_frame_rate() const;
- FrameRateChange active_frame_rate_change(dcpomatic::DCPTime) const;
+ dcp::Fraction best_video_frame_rate() const;
+ dcp::Fraction active_frame_rate_change(dcpomatic::DCPTime) const;
std::pair<double, double> speed_up_range(int dcp_frame_rate) const;
dcp::DecryptedKDM make_kdm(boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::LocalTime until) 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,7 +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(int rate, bool user_explicit = true);
+ void set_video_frame_rate(dcp::Fraction rate, bool user_explicit = true);
void set_audio_channels(int);
void set_three_d(bool);
void set_isdcf_date_today();
@@ -534,7 +534,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..73d31ce5b 100644
--- a/src/lib/hints.cc
+++ b/src/lib/hints.cc
@@ -181,13 +181,8 @@ 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()) {
@@ -196,16 +191,13 @@ Hints::check_frame_rate()
}
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));
+ 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));
break;
}
}
@@ -624,7 +616,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/referenced_reel_asset.cc b/src/lib/referenced_reel_asset.cc
index 5ef3b9ae7..62b7c4c93 100644
--- a/src/lib/referenced_reel_asset.cc
+++ b/src/lib/referenced_reel_asset.cc
@@ -83,7 +83,7 @@ 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());
/* 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(std::abs(dcp->video_frame_rate().get() - frame_rate.as_float()) < 1e-3);
Frame const trim_start = dcp->trim_start().frames_round(frame_rate);
Frame const trim_end = dcp->trim_end().frames_round(frame_rate);