diff options
Diffstat (limited to 'src')
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); } |
