2018-07-20 Carl Hetherington <cth@carlh.net>
- * Remove Join function.
-
* Advanced configuration option to allow any container ratio.
* Support closed-caption creation (#725).
}
}
+AudioContent::AudioContent (Content* parent, vector<shared_ptr<Content> > c)
+ : ContentPart (parent)
+{
+ shared_ptr<AudioContent> ref = c[0]->audio;
+ DCPOMATIC_ASSERT (ref);
+
+ for (size_t i = 1; i < c.size(); ++i) {
+ if (c[i]->audio->gain() != ref->gain()) {
+ throw JoinError (_("Content to be joined must have the same audio gain."));
+ }
+
+ if (c[i]->audio->delay() != ref->delay()) {
+ throw JoinError (_("Content to be joined must have the same audio delay."));
+ }
+ }
+
+ _gain = ref->gain ();
+ _delay = ref->delay ();
+ _streams = ref->streams ();
+}
+
void
AudioContent::as_xml (xmlpp::Node* node) const
{
{
public:
explicit AudioContent (Content* parent);
+ AudioContent (Content* parent, std::vector<boost::shared_ptr<Content> >);
void as_xml (xmlpp::Node *) const;
std::string technical_summary () const;
_original_type = string_to_caption_type (node->optional_string_child("Type").get_value_or("open"));
}
+CaptionContent::CaptionContent (Content* parent, vector<shared_ptr<Content> > c)
+ : ContentPart (parent)
+{
+ shared_ptr<CaptionContent> ref = c[0]->caption;
+ DCPOMATIC_ASSERT (ref);
+ list<shared_ptr<Font> > ref_fonts = ref->fonts ();
+
+ for (size_t i = 1; i < c.size(); ++i) {
+
+ if (c[i]->caption->use() != ref->use()) {
+ throw JoinError (_("Content to be joined must have the same 'use subtitles' setting."));
+ }
+
+ if (c[i]->caption->burn() != ref->burn()) {
+ throw JoinError (_("Content to be joined must have the same 'burn subtitles' setting."));
+ }
+
+ if (c[i]->caption->x_offset() != ref->x_offset()) {
+ throw JoinError (_("Content to be joined must have the same subtitle X offset."));
+ }
+
+ if (c[i]->caption->y_offset() != ref->y_offset()) {
+ throw JoinError (_("Content to be joined must have the same subtitle Y offset."));
+ }
+
+ if (c[i]->caption->x_scale() != ref->x_scale()) {
+ throw JoinError (_("Content to be joined must have the same subtitle X scale."));
+ }
+
+ if (c[i]->caption->y_scale() != ref->y_scale()) {
+ throw JoinError (_("Content to be joined must have the same subtitle Y scale."));
+ }
+
+ if (c[i]->caption->line_spacing() != ref->line_spacing()) {
+ throw JoinError (_("Content to be joined must have the same subtitle line spacing."));
+ }
+
+ if ((c[i]->caption->fade_in() != ref->fade_in()) || (c[i]->caption->fade_out() != ref->fade_out())) {
+ throw JoinError (_("Content to be joined must have the same subtitle fades."));
+ }
+
+ if ((c[i]->caption->outline_width() != ref->outline_width())) {
+ throw JoinError (_("Content to be joined must have the same outline width."));
+ }
+
+ list<shared_ptr<Font> > fonts = c[i]->caption->fonts ();
+ if (fonts.size() != ref_fonts.size()) {
+ throw JoinError (_("Content to be joined must use the same fonts."));
+ }
+
+ list<shared_ptr<Font> >::const_iterator j = ref_fonts.begin ();
+ list<shared_ptr<Font> >::const_iterator k = fonts.begin ();
+
+ while (j != ref_fonts.end ()) {
+ if (**j != **k) {
+ throw JoinError (_("Content to be joined must use the same fonts."));
+ }
+ ++j;
+ ++k;
+ }
+ }
+
+ _use = ref->use ();
+ _burn = ref->burn ();
+ _x_offset = ref->x_offset ();
+ _y_offset = ref->y_offset ();
+ _x_scale = ref->x_scale ();
+ _y_scale = ref->y_scale ();
+ _language = ref->language ();
+ _fonts = ref_fonts;
+ _line_spacing = ref->line_spacing ();
+ _fade_in = ref->fade_in ();
+ _fade_out = ref->fade_out ();
+ _outline_width = ref->outline_width ();
+
+ connect_to_fonts ();
+}
/** _mutex must not be held on entry */
void
{
public:
explicit CaptionContent (Content* parent);
+ CaptionContent (Content* parent, std::vector<boost::shared_ptr<Content> >);
void as_xml (xmlpp::Node *) const;
std::string identifier () const;
_video_frame_rate = node->optional_number_child<double> ("VideoFrameRate");
}
+Content::Content (shared_ptr<const Film> film, vector<shared_ptr<Content> > c)
+ : _film (film)
+ , _position (c.front()->position ())
+ , _trim_start (c.front()->trim_start ())
+ , _trim_end (c.back()->trim_end ())
+ , _video_frame_rate (c.front()->video_frame_rate())
+ , _change_signals_frequent (false)
+{
+ for (size_t i = 0; i < c.size(); ++i) {
+ if (i > 0 && c[i]->trim_start() > ContentTime ()) {
+ throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
+ }
+
+ if (i < (c.size() - 1) && c[i]->trim_end () > ContentTime ()) {
+ throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
+ }
+
+ if (
+ (_video_frame_rate && !c[i]->_video_frame_rate) ||
+ (!_video_frame_rate && c[i]->_video_frame_rate)
+ ) {
+ throw JoinError (_("Content to be joined must have the same video frame rate"));
+ }
+
+ if (_video_frame_rate && fabs (_video_frame_rate.get() - c[i]->_video_frame_rate.get()) > VIDEO_FRAME_RATE_EPSILON) {
+ throw JoinError (_("Content to be joined must have the same video frame rate"));
+ }
+
+ for (size_t j = 0; j < c[i]->number_of_paths(); ++j) {
+ _paths.push_back (c[i]->path (j));
+ }
+ }
+}
+
void
Content::as_xml (xmlpp::Node* node, bool with_paths) const
{
Content (boost::shared_ptr<const Film>, DCPTime);
Content (boost::shared_ptr<const Film>, boost::filesystem::path);
Content (boost::shared_ptr<const Film>, cxml::ConstNodePtr);
+ Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
virtual ~Content () {}
/** Examine the content to establish digest, frame rates and any other
boost::filesystem::path _file;
};
+class JoinError : public std::runtime_error
+{
+public:
+ explicit JoinError (std::string s)
+ : std::runtime_error (s)
+ {}
+};
+
/** @class OpenFileError.
* @brief Indicates that some error occurred when trying to open a file.
*/
}
+FFmpegContent::FFmpegContent (shared_ptr<const Film> film, vector<shared_ptr<Content> > c)
+ : Content (film, c)
+{
+ vector<shared_ptr<Content> >::const_iterator i = c.begin ();
+
+ bool need_video = false;
+ bool need_audio = false;
+ bool need_caption = false;
+
+ if (i != c.end ()) {
+ need_video = static_cast<bool> ((*i)->video);
+ need_audio = static_cast<bool> ((*i)->audio);
+ need_caption = static_cast<bool> ((*i)->caption);
+ }
+
+ while (i != c.end ()) {
+ if (need_video != static_cast<bool> ((*i)->video)) {
+ throw JoinError (_("Content to be joined must all have or not have video"));
+ }
+ if (need_audio != static_cast<bool> ((*i)->audio)) {
+ throw JoinError (_("Content to be joined must all have or not have audio"));
+ }
+ if (need_caption != static_cast<bool> ((*i)->caption)) {
+ throw JoinError (_("Content to be joined must all have or not have captions"));
+ }
+ ++i;
+ }
+
+ if (need_video) {
+ video.reset (new VideoContent (this, c));
+ }
+ if (need_audio) {
+ audio.reset (new AudioContent (this, c));
+ }
+ if (need_caption) {
+ caption.reset (new CaptionContent (this, c));
+ }
+
+ shared_ptr<FFmpegContent> ref = dynamic_pointer_cast<FFmpegContent> (c[0]);
+ DCPOMATIC_ASSERT (ref);
+
+ for (size_t i = 0; i < c.size(); ++i) {
+ shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c[i]);
+ if (fc->caption && fc->caption->use() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) {
+ throw JoinError (_("Content to be joined must use the same subtitle stream."));
+ }
+ }
+
+ /* XXX: should probably check that more of the stuff below is the same in *this and ref */
+
+ _subtitle_streams = ref->subtitle_streams ();
+ _subtitle_stream = ref->subtitle_stream ();
+ _first_video = ref->_first_video;
+ _filters = ref->_filters;
+ _color_range = ref->_color_range;
+ _color_primaries = ref->_color_primaries;
+ _color_trc = ref->_color_trc;
+ _colorspace = ref->_colorspace;
+ _bits_per_pixel = ref->_bits_per_pixel;
+}
+
void
FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const
{
public:
FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
FFmpegContent (boost::shared_ptr<const Film>, cxml::ConstNodePtr, int version, std::list<std::string> &);
+ FFmpegContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
boost::shared_ptr<FFmpegContent> shared_from_this () {
return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
}
}
+VideoContent::VideoContent (Content* parent, vector<shared_ptr<Content> > c)
+ : ContentPart (parent)
+ , _length (0)
+ , _yuv (false)
+{
+ shared_ptr<VideoContent> ref = c[0]->video;
+ DCPOMATIC_ASSERT (ref);
+
+ for (size_t i = 1; i < c.size(); ++i) {
+
+ if (c[i]->video->size() != ref->size()) {
+ throw JoinError (_("Content to be joined must have the same picture size."));
+ }
+
+ if (c[i]->video->frame_type() != ref->frame_type()) {
+ throw JoinError (_("Content to be joined must have the same video frame type."));
+ }
+
+ if (c[i]->video->crop() != ref->crop()) {
+ throw JoinError (_("Content to be joined must have the same crop."));
+ }
+
+ if (c[i]->video->scale() != ref->scale()) {
+ throw JoinError (_("Content to be joined must have the same scale setting."));
+ }
+
+ if (c[i]->video->colour_conversion() != ref->colour_conversion()) {
+ throw JoinError (_("Content to be joined must have the same colour conversion."));
+ }
+
+ if (c[i]->video->fade_in() != ref->fade_in() || c[i]->video->fade_out() != ref->fade_out()) {
+ throw JoinError (_("Content to be joined must have the same fades."));
+ }
+
+ _length += c[i]->video->length ();
+
+ if (c[i]->video->yuv ()) {
+ _yuv = true;
+ }
+ }
+
+ _size = ref->size ();
+ _frame_type = ref->frame_type ();
+ _crop = ref->crop ();
+ _scale = ref->scale ();
+ _colour_conversion = ref->colour_conversion ();
+ _fade_in = ref->fade_in ();
+ _fade_out = ref->fade_out ();
+}
+
void
VideoContent::as_xml (xmlpp::Node* node) const
{
/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
{
public:
explicit VideoContent (Content* parent);
+ VideoContent (Content* parent, std::vector<boost::shared_ptr<Content> >);
void as_xml (xmlpp::Node *) const;
std::string technical_summary () const;
enum {
/* Start at 256 so we can have IDs on _cpl_menu from 1 to 255 */
ID_repeat = 256,
+ ID_join,
ID_find_missing,
ID_properties,
ID_re_examine,
, _pop_up_open (false)
{
_repeat = _menu->Append (ID_repeat, _("Repeat..."));
+ _join = _menu->Append (ID_join, _("Join"));
_find_missing = _menu->Append (ID_find_missing, _("Find missing..."));
_properties = _menu->Append (ID_properties, _("Properties..."));
_re_examine = _menu->Append (ID_re_examine, _("Re-examine..."));
_remove = _menu->Append (ID_remove, _("Remove"));
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::repeat, this), ID_repeat);
+ _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::join, this), ID_join);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::find_missing, this), ID_find_missing);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::properties, this), ID_properties);
_parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::re_examine, this), ID_re_examine);
}
}
+ _join->Enable (n > 1);
+
_find_missing->Enable (_content.size() == 1 && !_content.front()->paths_valid ());
_properties->Enable (_content.size() == 1);
_re_examine->Enable (!_content.empty ());
_views.clear ();
}
+void
+ContentMenu::join ()
+{
+ vector<shared_ptr<Content> > fc;
+ BOOST_FOREACH (shared_ptr<Content> i, _content) {
+ shared_ptr<FFmpegContent> f = dynamic_pointer_cast<FFmpegContent> (i);
+ if (f) {
+ fc.push_back (f);
+ }
+ }
+
+ DCPOMATIC_ASSERT (fc.size() > 1);
+
+ shared_ptr<Film> film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ try {
+ shared_ptr<FFmpegContent> joined (new FFmpegContent (film, fc));
+ film->remove_content (_content);
+ film->examine_and_add_content (joined);
+ } catch (JoinError& e) {
+ error_dialog (_parent, std_to_wx (e.what ()));
+ }
+}
+
void
ContentMenu::remove ()
{
private:
void repeat ();
+ void join ();
void find_missing ();
void properties ();
void re_examine ();
ContentList _content;
TimelineContentViewList _views;
wxMenuItem* _repeat;
+ wxMenuItem* _join;
wxMenuItem* _find_missing;
wxMenuItem* _properties;
wxMenuItem* _re_examine;