Revert "Remove join function; the code is long and I don't think anybody"
authorCarl Hetherington <cth@carlh.net>
Sat, 21 Jul 2018 20:47:25 +0000 (21:47 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 21 Jul 2018 20:47:25 +0000 (21:47 +0100)
It turns out Carsten uses it :)

This reverts commit bd5e8b83a3a18787241982efdae809d4db21f65d.

14 files changed:
ChangeLog
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/caption_content.cc
src/lib/caption_content.h
src/lib/content.cc
src/lib/content.h
src/lib/exceptions.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/video_content.cc
src/lib/video_content.h
src/wx/content_menu.cc
src/wx/content_menu.h

index ec5c163d4b36e1b3bbebfc3102777aa0425f3173..58269eed37c986e895be8ec60d066594c00b84ba 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,5 @@
 2018-07-20  Carl Hetherington  <cth@carlh.net>
 
-       * Remove Join function.
-
        * Advanced configuration option to allow any container ratio.
 
        * Support closed-caption creation (#725).
index d4ce6c2435397940e5b5bb0598864e0ace333a4e..a252e4b5afae12c01c0f216afa2089144fa275cd 100644 (file)
@@ -91,6 +91,27 @@ AudioContent::AudioContent (Content* parent, cxml::ConstNodePtr node)
        }
 }
 
+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
 {
index d73c73735fa796a319cb14fcf3fdda7b2a538ad3..a1f5ba8a0aecbe05b8c6d6b450fdb54dfbe15f80 100644 (file)
@@ -44,6 +44,7 @@ class AudioContent : public ContentPart
 {
 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;
index d44fb55c5502a8faabc61fe122e90d20671c0f5e..0d13568e20ee3347999c64497e93b2539f958d0b 100644 (file)
@@ -228,6 +228,83 @@ CaptionContent::CaptionContent (Content* parent, cxml::ConstNodePtr node, int ve
        _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
index 4152dc533678e195b7cc5be62d01a7f1699e7f26..767fc7234446728ab6ff4a66c9693eea711584d4 100644 (file)
@@ -59,6 +59,7 @@ class CaptionContent : public ContentPart
 {
 public:
        explicit CaptionContent (Content* parent);
+       CaptionContent (Content* parent, std::vector<boost::shared_ptr<Content> >);
 
        void as_xml (xmlpp::Node *) const;
        std::string identifier () const;
index 13c5794fe9eb46d087ba5faebf2881ed5424540a..629672b7317d8e2252cab36f528f2e69b9329f57 100644 (file)
@@ -104,6 +104,40 @@ Content::Content (shared_ptr<const Film> film, cxml::ConstNodePtr node)
        _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
 {
index 2a249011a55f4d889383b8a76702d8daaf71893d..d84f636fb5c35465a5f2cb0057359eb204408cb6 100644 (file)
@@ -67,6 +67,7 @@ public:
        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
index 769857fd33d985ed12a37b8d5d018b14fd659cfe..eceafa10501d656a55ef78961590ecaff72108b7 100644 (file)
@@ -81,6 +81,14 @@ private:
        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.
  */
index ddf4548b41959fe1c8eac9ea304a9d85da77c201..db999df204afba0b978d5181402551f675688f01 100644 (file)
@@ -128,6 +128,67 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> film, cxml::ConstNodePtr no
 
 }
 
+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
 {
index 21f5d4680f06be421d6d413488045933c70ab1ff..64f6f9ff8632a275cf05f2233b2faf344a2aadf6 100644 (file)
@@ -48,6 +48,7 @@ class FFmpegContent : public Content
 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 ());
index 0dba555251465b1b77ed5af9326f3b0246c52a5e..d3ba6c1ab1f3d50d89806290b8eb8ed12b2b54aa 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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.
 
@@ -154,6 +154,56 @@ VideoContent::VideoContent (Content* parent, cxml::ConstNodePtr node, int versio
        }
 }
 
+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
 {
index 1fc87bb9bf84283fcba3b46cc2e1d76b030f5304..774210c13ff191eb37521b6be328a110d79035d1 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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.
 
@@ -52,6 +52,7 @@ class VideoContent : public ContentPart, public boost::enable_shared_from_this<V
 {
 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;
index 07ccaded207c659f8d704e57ad94817d37c79c44..36187e00f307849308577fec31f512e7ee06171a 100644 (file)
@@ -54,6 +54,7 @@ using boost::dynamic_pointer_cast;
 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,
@@ -69,6 +70,7 @@ ContentMenu::ContentMenu (wxWindow* p)
        , _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..."));
@@ -81,6 +83,7 @@ ContentMenu::ContentMenu (wxWindow* p)
        _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);
@@ -111,6 +114,8 @@ ContentMenu::popup (weak_ptr<Film> film, ContentList c, TimelineContentViewList
                }
        }
 
+       _join->Enable (n > 1);
+
        _find_missing->Enable (_content.size() == 1 && !_content.front()->paths_valid ());
        _properties->Enable (_content.size() == 1);
        _re_examine->Enable (!_content.empty ());
@@ -185,6 +190,33 @@ ContentMenu::repeat ()
        _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 ()
 {
index 1faf16de60e0bd5712873fd946a6be3dee2b2207..a38109b0738e84914139a38ca0350c47b6c54604 100644 (file)
@@ -40,6 +40,7 @@ public:
 
 private:
        void repeat ();
+       void join ();
        void find_missing ();
        void properties ();
        void re_examine ();
@@ -58,6 +59,7 @@ private:
        ContentList _content;
        TimelineContentViewList _views;
        wxMenuItem* _repeat;
+       wxMenuItem* _join;
        wxMenuItem* _find_missing;
        wxMenuItem* _properties;
        wxMenuItem* _re_examine;