#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"
content = std::make_shared<VideoMXFContent>(node, film_directory, version);
} else if (type == "AtmosMXF") {
content = std::make_shared<AtmosMXFContent>(node, film_directory, version);
+ } else if (type == "FCPXML") {
+ content = std::make_shared<FCPXMLContent>(node, film_directory, version, notes);
}
return content;
throw KDMAsContentError ();
}
single = std::make_shared<DCPSubtitleContent>(path);
+ } else if (ext == ".fcpxml") {
+ single = std::make_shared<FCPXMLContent>(path);
} else if (ext == ".mxf" && dcp::SMPTETextAsset::valid_mxf(path)) {
single = std::make_shared<DCPSubtitleContent>(path);
} else if (ext == ".mxf" && VideoMXFContent::valid_mxf(path)) {
#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"
return make_shared<AtmosMXFDecoder>(film, amc);
}
+ if (auto c = dynamic_pointer_cast<const FCPXMLContent>(content)) {
+ return make_shared<FCPXMLDecoder>(film, c);
+ }
+
return {};
}
};
+class FCPXMLError : public std::runtime_error
+{
+public:
+ explicit FCPXMLError(std::string s)
+ : std::runtime_error(s)
+ {}
+};
+
+
#endif
--- /dev/null
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "fcpxml.h"
+#include <dcp/raw_convert.h>
+#include <libcxml/cxml.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+
+
+#include "i18n.h"
+
+
+using std::map;
+using std::string;
+using std::vector;
+
+
+static dcpomatic::ContentTime
+convert_time(string const& time)
+{
+ vector<string> 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<int64_t>(parts[0]) * dcpomatic::ContentTime::HZ / dcp::raw_convert<int64_t>(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<string, boost::filesystem::path> 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;
+}
--- /dev/null
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_time.h"
+#include <vector>
+
+
+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<Video> video;
+};
+
+
+Sequence load(boost::filesystem::path xml_file);
+
+
+}
+}
--- /dev/null
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "fcpxml.h"
+#include "fcpxml_content.h"
+#include "text_content.h"
+#include <dcp/raw_convert.h>
+
+#include "i18n.h"
+
+
+using std::list;
+using std::make_shared;
+using std::shared_ptr;
+using std::string;
+using boost::optional;
+
+
+FCPXMLContent::FCPXMLContent(boost::filesystem::path path)
+ : Content(path)
+{
+ text.push_back(make_shared<TextContent>(this, TextType::OPEN_SUBTITLE, TextType::OPEN_SUBTITLE));
+}
+
+
+FCPXMLContent::FCPXMLContent(cxml::ConstNodePtr node, optional<boost::filesystem::path> film_directory, int version, list<string>& notes)
+ : Content(node, film_directory)
+{
+ text = TextContent::from_xml(this, node, version, notes);
+}
+
+
+
+void
+FCPXMLContent::examine(shared_ptr<const Film> film, shared_ptr<Job> job)
+{
+ Content::examine(film, job);
+
+ auto sequence = dcpomatic::fcpxml::load(path(0));
+
+ boost::mutex::scoped_lock lm(_mutex);
+ only_text()->set_use(true);
+ if (!sequence.video.empty()) {
+ _length = sequence.video.back().period.to;
+ }
+}
+
+
+dcpomatic::DCPTime
+FCPXMLContent::full_length(shared_ptr<const Film> film) const
+{
+ FrameRateChange const frc(film, shared_from_this());
+ return { _length, frc };
+}
+
+
+dcpomatic::DCPTime
+FCPXMLContent::approximate_length() const
+{
+ return { _length, {} };
+}
+
+
+string
+FCPXMLContent::summary() const
+{
+ return path_summary() + " " + _("[subtitles]");
+}
+
+
+string
+FCPXMLContent::technical_summary() const
+{
+ return Content::technical_summary() + " - " + _("FCP XML subtitles");
+}
+
+
+void
+FCPXMLContent::as_xml(xmlpp::Element* element, bool with_paths, PathBehaviour path_behaviour, optional<boost::filesystem::path> film_directory) const
+{
+ cxml::add_child(element, "Type", "FCPXML");
+ Content::as_xml(element, with_paths, path_behaviour, film_directory);
+
+ if (only_text()) {
+ only_text()->as_xml(element);
+ }
+
+ cxml::add_child(element, "Length", dcp::raw_convert<string>(_length.get()));
+}
+
--- /dev/null
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_FCPXML_CONTENT_H
+#define DCPOMATIC_FCPXML_CONTENT_H
+
+
+#include "content.h"
+
+
+
+class FCPXMLContent : public Content
+{
+public:
+ FCPXMLContent(boost::filesystem::path path);
+ FCPXMLContent(cxml::ConstNodePtr node, boost::optional<boost::filesystem::path> film_directory, int version, std::list<std::string>& notes);
+
+ std::shared_ptr<FCPXMLContent> shared_from_this() {
+ return std::dynamic_pointer_cast<FCPXMLContent>(Content::shared_from_this());
+ }
+
+ std::shared_ptr<const FCPXMLContent> shared_from_this() const {
+ return std::dynamic_pointer_cast<const FCPXMLContent>(Content::shared_from_this());
+ }
+
+ void examine(std::shared_ptr<const Film> film, std::shared_ptr<Job>) override;
+ std::string summary() const override;
+ std::string technical_summary() const override;
+ void as_xml(xmlpp::Element*, bool with_paths, PathBehaviour path_behaviour, boost::optional<boost::filesystem::path> film_directory) const override;
+ dcpomatic::DCPTime full_length(std::shared_ptr<const Film> film) const override;
+ dcpomatic::DCPTime approximate_length() const override;
+
+private:
+ dcpomatic::ContentTime _length;
+};
+
+
+
+#endif
--- /dev/null
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "fcpxml_content.h"
+#include "fcpxml_decoder.h"
+#include "ffmpeg_image_proxy.h"
+#include "guess_crop.h"
+#include "image.h"
+#include "rect.h"
+#include "text_decoder.h"
+#include <dcp/array_data.h>
+
+
+
+using std::make_shared;
+using std::shared_ptr;
+using std::weak_ptr;
+
+
+FCPXMLDecoder::FCPXMLDecoder(weak_ptr<const Film> film, shared_ptr<const FCPXMLContent> content)
+ : Decoder(film)
+ , _fcpxml_content(content)
+ , _sequence(dcpomatic::fcpxml::load(content->path(0)))
+{
+ text.push_back(make_shared<TextDecoder>(this, content->only_text()));
+ update_position();
+}
+
+
+bool
+FCPXMLDecoder::pass()
+{
+ if (_next >= static_cast<int>(_sequence.video.size())) {
+ return true;
+ }
+
+ auto const png_data = dcp::ArrayData(_sequence.parent / _sequence.video[_next].source);
+ auto const full_image = FFmpegImageProxy(png_data).image(Image::Alignment::PADDED).image;
+ auto const crop = guess_crop_by_alpha(full_image);
+ auto const cropped_image = full_image->crop(crop);
+
+ auto rectangle = dcpomatic::Rect<double>{
+ static_cast<double>(crop.left) / full_image->size().width,
+ static_cast<double>(crop.top) / full_image->size().height,
+ static_cast<double>(cropped_image->size().width) / full_image->size().width,
+ static_cast<double>(cropped_image->size().height) / full_image->size().height
+ };
+
+ only_text()->emit_bitmap(_sequence.video[_next].period, cropped_image, rectangle);
+
+ ++_next;
+
+ update_position();
+ return false;
+}
+
+
+void
+FCPXMLDecoder::seek(dcpomatic::ContentTime time, bool accurate)
+{
+ /* It's worth back-tracking a little here as decoding is cheap and it's nice if we don't miss
+ too many subtitles when seeking.
+ */
+ time -= dcpomatic::ContentTime::from_seconds(5);
+ if (time < dcpomatic::ContentTime()) {
+ time = {};
+ }
+
+ Decoder::seek(time, accurate);
+
+ _next = 0;
+ while (_next < static_cast<int>(_sequence.video.size()) && _sequence.video[_next].period.from < time) {
+ ++_next;
+ }
+
+ update_position();
+}
+
+
+void
+FCPXMLDecoder::update_position()
+{
+ if (_next < static_cast<int>(_sequence.video.size())) {
+ only_text()->maybe_set_position(_sequence.video[_next].period.from);
+ }
+}
+
--- /dev/null
+/*
+ Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_FCPXML_DECODER_H
+#define DCPOMATIC_FCPXML_DECODER_H
+
+
+#include "decoder.h"
+#include "fcpxml.h"
+
+
+class FCPXMLContent;
+
+
+class FCPXMLDecoder : public Decoder
+{
+public:
+ FCPXMLDecoder(std::weak_ptr<const Film> film, std::shared_ptr<const FCPXMLContent> content);
+
+ bool pass() override;
+ void seek(dcpomatic::ContentTime time, bool accurate) override;
+
+private:
+ void update_position();
+
+ std::shared_ptr<const FCPXMLContent> _fcpxml_content;
+ dcpomatic::fcpxml::Sequence _sequence;
+ int _next = 0;
+};
+
+
+#endif
examine_ffmpeg_subtitles_job.cc
exceptions.cc
export_config.cc
+ fcpxml.cc
+ fcpxml_content.cc
+ fcpxml_decoder.cc
frame_info.cc
file_group.cc
file_log.cc
#include "lib/dcp_subtitle_content.h"
#include "lib/dcp_subtitle_decoder.h"
#include "lib/decoder_factory.h"
+#include "lib/fcpxml_content.h"
#include "lib/ffmpeg_content.h"
#include "lib/ffmpeg_subtitle_stream.h"
#include "lib/film.h"
auto sc = std::dynamic_pointer_cast<const StringTextFileContent>(i);
auto dc = std::dynamic_pointer_cast<const DCPContent>(i);
auto dsc = std::dynamic_pointer_cast<const DCPSubtitleContent>(i);
+ auto fcp = std::dynamic_pointer_cast<const FCPXMLContent>(i);
if (fc) {
if (!fc->text.empty()) {
++ffmpeg_subs;
} else if (dc || dsc) {
++dcp_subs;
++any_subs;
- } else if (sc) {
+ } else if (sc || fcp) {
/* XXX: in the future there could be bitmap subs from DCPs */
++any_subs;
}