From 54e2644ed4dcc4faf5135e60926cfdda14886fea Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Fri, 6 Dec 2024 17:46:24 +0100 Subject: Support FCP XML files containing subtitles (#2909). --- src/lib/content_factory.cc | 5 +++ src/lib/decoder_factory.cc | 6 +++ src/lib/exceptions.h | 9 ++++ src/lib/fcpxml.cc | 82 ++++++++++++++++++++++++++++++++++ src/lib/fcpxml.h | 55 +++++++++++++++++++++++ src/lib/fcpxml_content.cc | 108 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/fcpxml_content.h | 57 ++++++++++++++++++++++++ src/lib/fcpxml_decoder.cc | 105 +++++++++++++++++++++++++++++++++++++++++++ src/lib/fcpxml_decoder.h | 50 +++++++++++++++++++++ src/lib/wscript | 3 ++ 10 files changed, 480 insertions(+) create mode 100644 src/lib/fcpxml.cc create mode 100644 src/lib/fcpxml.h create mode 100644 src/lib/fcpxml_content.cc create mode 100644 src/lib/fcpxml_content.h create mode 100644 src/lib/fcpxml_decoder.cc create mode 100644 src/lib/fcpxml_decoder.h (limited to 'src/lib') diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc index 9f464896a..fa3f0876c 100644 --- a/src/lib/content_factory.cc +++ b/src/lib/content_factory.cc @@ -29,6 +29,7 @@ #include "dcp_content.h" #include "dcp_subtitle_content.h" #include "dcpomatic_log.h" +#include "fcpxml_content.h" #include "ffmpeg_audio_stream.h" #include "ffmpeg_content.h" #include "film.h" @@ -97,6 +98,8 @@ content_factory(cxml::ConstNodePtr node, boost::optional(node, film_directory, version); } else if (type == "AtmosMXF") { content = std::make_shared(node, film_directory, version); + } else if (type == "FCPXML") { + content = std::make_shared(node, film_directory, version, notes); } return content; @@ -178,6 +181,8 @@ content_factory (boost::filesystem::path path) throw KDMAsContentError (); } single = std::make_shared(path); + } else if (ext == ".fcpxml") { + single = std::make_shared(path); } else if (ext == ".mxf" && dcp::SMPTETextAsset::valid_mxf(path)) { single = std::make_shared(path); } else if (ext == ".mxf" && VideoMXFContent::valid_mxf(path)) { diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc index ea0eda83d..31156ab3f 100644 --- a/src/lib/decoder_factory.cc +++ b/src/lib/decoder_factory.cc @@ -25,6 +25,8 @@ #include "dcp_decoder.h" #include "dcp_subtitle_content.h" #include "dcp_subtitle_decoder.h" +#include "fcpxml_content.h" +#include "fcpxml_decoder.h" #include "ffmpeg_content.h" #include "ffmpeg_decoder.h" #include "image_content.h" @@ -92,5 +94,9 @@ decoder_factory (shared_ptr film, shared_ptr content, return make_shared(film, amc); } + if (auto c = dynamic_pointer_cast(content)) { + return make_shared(film, c); + } + return {}; } diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 3069d792e..c1bb8f6b6 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -542,4 +542,13 @@ public: }; +class FCPXMLError : public std::runtime_error +{ +public: + explicit FCPXMLError(std::string s) + : std::runtime_error(s) + {} +}; + + #endif diff --git a/src/lib/fcpxml.cc b/src/lib/fcpxml.cc new file mode 100644 index 000000000..49798381c --- /dev/null +++ b/src/lib/fcpxml.cc @@ -0,0 +1,82 @@ +/* + Copyright (C) 2024 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "fcpxml.h" +#include +#include +#include +#include + + +#include "i18n.h" + + +using std::map; +using std::string; +using std::vector; + + +static dcpomatic::ContentTime +convert_time(string const& time) +{ + vector parts; + boost::algorithm::split(parts, time, boost::is_any_of("/")); + + if (parts.size() != 2 || parts[1].empty() || parts[1][parts[1].length() - 1] != 's') { + throw FCPXMLError(String::compose("Unexpected time format %1", time)); + } + + return dcpomatic::ContentTime{dcp::raw_convert(parts[0]) * dcpomatic::ContentTime::HZ / dcp::raw_convert(parts[1])}; +} + + +dcpomatic::fcpxml::Sequence +dcpomatic::fcpxml::load(boost::filesystem::path xml_file) +{ + cxml::Document doc("fcpxml"); + doc.read_file(xml_file); + + auto project = doc.node_child("project"); + + map assets; + for (auto asset: project->node_child("resources")->node_children("asset")) { + assets[asset->string_attribute("name")] = asset->string_attribute("src"); + } + + auto sequence = Sequence{xml_file.parent_path()}; + for (auto video: project->node_child("sequence")->node_child("spine")->node_children("video")) { + auto name = video->string_attribute("name"); + auto iter = assets.find(name); + if (iter == assets.end()) { + throw FCPXMLError(String::compose(_("Video refers to missing asset %1"), name)); + } + + auto start = convert_time(video->string_attribute("offset")); + sequence.video.push_back( + { + iter->second, + dcpomatic::ContentTimePeriod(start, start + convert_time(video->string_attribute("duration"))) + } + ); + } + + return sequence; +} diff --git a/src/lib/fcpxml.h b/src/lib/fcpxml.h new file mode 100644 index 000000000..00137ca32 --- /dev/null +++ b/src/lib/fcpxml.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2024 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "dcpomatic_time.h" +#include + + +namespace dcpomatic { +namespace fcpxml { + + +class Video +{ +public: + boost::filesystem::path source; ///< filename of PNG relative to Sequence::parent + dcpomatic::ContentTimePeriod period; +}; + + + +class Sequence +{ +public: + Sequence(boost::filesystem::path parent_) + : parent(parent_) + {} + + boost::filesystem::path parent; ///< directory containing the PNG files + std::vector