diff options
| author | Carl Hetherington <cth@carlh.net> | 2026-02-28 09:06:44 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2026-02-28 09:08:46 +0100 |
| commit | bcf62b5e4a268554b7960e5f739b24f9eafb38a5 (patch) | |
| tree | 904fb5959dcd2401daba2d42c53d3a0736604bff /src/lib | |
| parent | bb04b67f593c1da37b76af9507f3e28e9ab15f74 (diff) | |
WIP: use fraction for DCP frame rate.archive-frame-rates
This will allow archival rates to be expressed precisely.
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/create_cli.cc | 18 | ||||
| -rw-r--r-- | src/lib/create_cli.h | 2 | ||||
| -rw-r--r-- | src/lib/dcp_video.cc | 2 | ||||
| -rw-r--r-- | src/lib/dcp_video.h | 10 | ||||
| -rw-r--r-- | src/lib/dcpomatic_time.h | 22 | ||||
| -rw-r--r-- | src/lib/ffmpeg_file_encoder.h | 4 | ||||
| -rw-r--r-- | src/lib/ffmpeg_film_encoder.cc | 2 | ||||
| -rw-r--r-- | src/lib/ffmpeg_film_encoder.h | 2 | ||||
| -rw-r--r-- | src/lib/film.h | 10 | ||||
| -rw-r--r-- | src/lib/frame_rate_change.cc | 14 | ||||
| -rw-r--r-- | src/lib/frame_rate_change.h | 5 | ||||
| -rw-r--r-- | src/lib/grok/context.h | 2 | ||||
| -rw-r--r-- | src/lib/hints.cc | 20 | ||||
| -rw-r--r-- | src/lib/referenced_reel_asset.cc | 2 |
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); |
