Add total-playlist loop option; move state of content list into the Playlist.
authorCarl Hetherington <cth@carlh.net>
Sun, 28 Apr 2013 00:20:55 +0000 (01:20 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 28 Apr 2013 00:20:55 +0000 (01:20 +0100)
src/lib/audio_mapping.cc
src/lib/film.cc
src/lib/film.h
src/lib/player.cc
src/lib/playlist.cc
src/lib/playlist.h
src/wx/film_editor.cc
src/wx/film_editor.h

index 83c748f1aada87d374894d3cb9ff5d1d31c4dca0..7e28aa5c41ea87e1c660badfc134f5334417ccf9 100644 (file)
@@ -115,7 +115,7 @@ AudioMapping::set_from_xml (ContentList const & content, shared_ptr<const cxml::
                        continue;
                }
 
-               shared_ptr<const AudioContent> ac = dynamic_pointer_cast<AudioContent> (*j);
+               shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (*j);
                assert (ac);
 
                add (AudioMapping::Channel (ac, (*i)->number_child<int> ("ContentIndex")), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")));
index 98c6c061061504a72c3821873f4d3a095c76d994..5481bd012a322d2390668ef8034fb6b14b17d912 100644 (file)
@@ -113,7 +113,8 @@ Film::Film (string d, bool must_exist)
 {
        set_dci_date_today ();
 
-       _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
+       _playlist->Changed.connect (bind (&Film::playlist_changed, this));
+       _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
@@ -156,7 +157,7 @@ Film::Film (Film const & o)
        : boost::enable_shared_from_this<Film> (o)
        /* note: the copied film shares the original's log */
        , _log               (o._log)
-       , _playlist          (new Playlist)
+       , _playlist          (new Playlist (o._playlist))
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
@@ -182,13 +183,7 @@ Film::Film (Film const & o)
        , _dci_date          (o._dci_date)
        , _dirty             (o._dirty)
 {
-       for (ContentList::const_iterator i = o._content.begin(); i != o._content.end(); ++i) {
-               _content.push_back ((*i)->clone ());
-       }
-       
-       _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
-       
-       _playlist->setup (_content);
+       _playlist->ContentChanged.connect (bind (&Film::playlist_content_changed, this, _1, _2));
 }
 
 string
@@ -320,7 +315,7 @@ Film::make_dcp ()
                throw MissingSettingError (_("format"));
        }
 
-       if (content().empty ()) {
+       if (_playlist->content().empty ()) {
                throw MissingSettingError (_("content"));
        }
 
@@ -405,8 +400,6 @@ Film::encoded_frames () const
 void
 Film::write_metadata () const
 {
-       ContentList the_content = content ();
-       
        boost::mutex::scoped_lock lm (_state_mutex);
        LocaleGuard lg;
 
@@ -460,10 +453,7 @@ Film::write_metadata () const
        root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate));
        root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
        _audio_mapping.as_xml (root->add_child("AudioMapping"));
-
-       for (ContentList::iterator i = the_content.begin(); i != the_content.end(); ++i) {
-               (*i)->as_xml (root->add_child ("Content"));
-       }
+       _playlist->as_xml (root->add_child ("Playlist"));
 
        doc.write_to_file_formatted (file ("metadata.xml"));
        
@@ -537,29 +527,10 @@ Film::read_metadata ()
        _dcp_frame_rate = f.number_child<int> ("DCPFrameRate");
        _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
 
-       list<shared_ptr<cxml::Node> > c = f.node_children ("Content");
-       for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
-
-               string const type = (*i)->string_child ("Type");
-               boost::shared_ptr<Content> c;
-               
-               if (type == "FFmpeg") {
-                       c.reset (new FFmpegContent (*i));
-               } else if (type == "ImageMagick") {
-                       c.reset (new ImageMagickContent (*i));
-               } else if (type == "Sndfile") {
-                       c.reset (new SndfileContent (*i));
-               }
-
-               _content.push_back (c);
-       }
-
-       /* This must come after we've loaded the content, as we're looking things up in _content */
-       _audio_mapping.set_from_xml (_content, f.node_child ("AudioMapping"));
+       _playlist->set_from_xml (f.node_child ("Playlist"));
+       _audio_mapping.set_from_xml (_playlist->content(), f.node_child ("AudioMapping"));
 
        _dirty = false;
-
-       _playlist->setup (_content);
 }
 
 libdcp::Size
@@ -766,10 +737,11 @@ Film::set_trust_content_headers (bool t)
        
        signal_changed (TRUST_CONTENT_HEADERS);
 
-       if (!_trust_content_headers && !content().empty()) {
+       
+       ContentList content = _playlist->content ();
+       if (!_trust_content_headers && !content.empty()) {
                /* We just said that we don't trust the content's header */
-               ContentList c = content ();
-               for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+               for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
                        examine_content (*i);
                }
        }
@@ -1023,7 +995,6 @@ Film::signal_changed (Property p)
 
        switch (p) {
        case Film::CONTENT:
-               _playlist->setup (content ());
                set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
                set_audio_mapping (_playlist->default_audio_mapping ());
                break;
@@ -1104,73 +1075,35 @@ Film::player () const
        return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
 }
 
+ContentList
+Film::content () const
+{
+       return _playlist->content ();
+}
+
 void
 Film::add_content (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content.push_back (c);
-       }
-
-       signal_changed (CONTENT);
-
+       _playlist->add (c);
        examine_content (c);
 }
 
 void
 Film::remove_content (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               ContentList::iterator i = find (_content.begin(), _content.end(), c);
-               if (i != _content.end ()) {
-                       _content.erase (i);
-               }
-       }
-
-       signal_changed (CONTENT);
+       _playlist->remove (c);
 }
 
 void
 Film::move_content_earlier (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               ContentList::iterator i = find (_content.begin(), _content.end(), c);
-               if (i == _content.begin () || i == _content.end()) {
-                       return;
-               }
-
-               ContentList::iterator j = i;
-               --j;
-
-               swap (*i, *j);
-       }
-
-       signal_changed (CONTENT);
+       _playlist->move_earlier (c);
 }
 
 void
 Film::move_content_later (shared_ptr<Content> c)
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               ContentList::iterator i = find (_content.begin(), _content.end(), c);
-               if (i == _content.end()) {
-                       return;
-               }
-
-               ContentList::iterator j = i;
-               ++j;
-               if (j == _content.end ()) {
-                       return;
-               }
-
-               swap (*i, *j);
-       }
-
-       signal_changed (CONTENT);
-
+       _playlist->move_later (c);
 }
 
 ContentAudioFrame
@@ -1221,26 +1154,10 @@ Film::content_length () const
        return _playlist->content_length ();
 }
 
-/** Unfortunately this is needed as the GUI has FFmpeg-specific controls */
-shared_ptr<FFmpegContent>
-Film::ffmpeg () const
-{
-       boost::mutex::scoped_lock lm (_state_mutex);
-       
-       for (ContentList::const_iterator i = _content.begin (); i != _content.end(); ++i) {
-               shared_ptr<FFmpegContent> f = boost::dynamic_pointer_cast<FFmpegContent> (*i);
-               if (f) {
-                       return f;
-               }
-       }
-
-       return shared_ptr<FFmpegContent> ();
-}
-
 vector<FFmpegSubtitleStream>
 Film::ffmpeg_subtitle_streams () const
 {
-       shared_ptr<FFmpegContent> f = ffmpeg ();
+       shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
        if (f) {
                return f->subtitle_streams ();
        }
@@ -1251,7 +1168,7 @@ Film::ffmpeg_subtitle_streams () const
 boost::optional<FFmpegSubtitleStream>
 Film::ffmpeg_subtitle_stream () const
 {
-       shared_ptr<FFmpegContent> f = ffmpeg ();
+       shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
        if (f) {
                return f->subtitle_stream ();
        }
@@ -1262,7 +1179,7 @@ Film::ffmpeg_subtitle_stream () const
 vector<FFmpegAudioStream>
 Film::ffmpeg_audio_streams () const
 {
-       shared_ptr<FFmpegContent> f = ffmpeg ();
+       shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
        if (f) {
                return f->audio_streams ();
        }
@@ -1273,7 +1190,7 @@ Film::ffmpeg_audio_streams () const
 boost::optional<FFmpegAudioStream>
 Film::ffmpeg_audio_stream () const
 {
-       shared_ptr<FFmpegContent> f = ffmpeg ();
+       shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
        if (f) {
                return f->audio_stream ();
        }
@@ -1284,7 +1201,7 @@ Film::ffmpeg_audio_stream () const
 void
 Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s)
 {
-       shared_ptr<FFmpegContent> f = ffmpeg ();
+       shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
        if (f) {
                f->set_subtitle_stream (s);
        }
@@ -1293,7 +1210,7 @@ Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s)
 void
 Film::set_ffmpeg_audio_stream (FFmpegAudioStream s)
 {
-       shared_ptr<FFmpegContent> f = ffmpeg ();
+       shared_ptr<FFmpegContent> f = _playlist->ffmpeg ();
        if (f) {
                f->set_audio_stream (s);
        }
@@ -1311,7 +1228,7 @@ Film::set_audio_mapping (AudioMapping m)
 }
 
 void
-Film::content_changed (boost::weak_ptr<Content> c, int p)
+Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
        if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
                set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
@@ -1323,3 +1240,21 @@ Film::content_changed (boost::weak_ptr<Content> c, int p)
                ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
        }
 }
+
+void
+Film::playlist_changed ()
+{
+       signal_changed (CONTENT);
+}      
+
+int
+Film::loop () const
+{
+       return _playlist->loop ();
+}
+
+void
+Film::set_loop (int c)
+{
+       _playlist->set_loop (c);
+}
index 43b5d1a173406cf02905ff013a758d27ce21195c..f4d7cde677a78518f064e2624379cae310064c78 100644 (file)
@@ -106,6 +106,8 @@ public:
 
        /* Proxies for some Playlist methods */
 
+       ContentList content () const;
+
        ContentAudioFrame audio_length () const;
        int audio_channels () const;
        int audio_frame_rate () const;
@@ -125,6 +127,9 @@ public:
        void set_ffmpeg_subtitle_stream (FFmpegSubtitleStream);
        void set_ffmpeg_audio_stream (FFmpegAudioStream);
 
+       void set_loop (int);
+       int loop () const;
+
        enum TrimType {
                CPL,
                ENCODE
@@ -138,8 +143,9 @@ public:
                NAME,
                USE_DCI_NAME,
                TRUST_CONTENT_HEADERS,
-               /** The content list has changed (i.e. content has been added, moved around or removed) */
+               /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */
                CONTENT,
+               LOOP,
                DCP_CONTENT_TYPE,
                FORMAT,
                CROP,
@@ -184,11 +190,6 @@ public:
                return _trust_content_headers;
        }
 
-       ContentList content () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content;
-       }
-
        DCPContentType const * dcp_content_type () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _dcp_content_type;
@@ -336,8 +337,8 @@ private:
        void analyse_audio_finished ();
        std::string video_state_identifier () const;
        void read_metadata ();
-       void content_changed (boost::weak_ptr<Content>, int);
-       boost::shared_ptr<FFmpegContent> ffmpeg () const;
+       void playlist_changed ();
+       void playlist_content_changed (boost::weak_ptr<Content>, int);
        void setup_default_audio_mapping ();
        std::string filename_safe_name () const;
 
@@ -359,7 +360,6 @@ private:
        /** True if a auto-generated DCI-compliant name should be used for our DCP */
        bool _use_dci_name;
        bool _trust_content_headers;
-       ContentList _content;
        /** The type of content that this Film represents (feature, trailer etc.) */
        DCPContentType const * _dcp_content_type;
        /** The format to present this Film in (flat, scope, etc.) */
index b2122458784fa2bc9251b4baa54e2d06ecd47b89..d62d8f8b6408bf66b95ddb7eb08f1c51bea3aae0 100644 (file)
@@ -229,67 +229,69 @@ Player::setup_decoders ()
 
        double video_so_far = 0;
        double audio_so_far = 0;
-       
-       list<shared_ptr<const VideoContent> > vc = _playlist->video ();
-       for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) {
-               
-               shared_ptr<const VideoContent> video_content;
-               shared_ptr<const AudioContent> audio_content;
-               shared_ptr<VideoDecoder> video_decoder;
-               shared_ptr<AudioDecoder> audio_decoder;
-               
-               /* XXX: into content? */
-               
-               shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
-               if (fc) {
-                       shared_ptr<FFmpegDecoder> fd (
-                               new FFmpegDecoder (
-                                       _film, fc, _video,
-                                       _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
-                                       _subtitles
-                                       )
-                               );
+
+       for (int l = 0; l < _playlist->loop(); ++l) {
+               list<shared_ptr<const VideoContent> > vc = _playlist->video ();
+               for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) {
                        
-                       video_content = fc;
-                       audio_content = fc;
-                       video_decoder = fd;
-                       audio_decoder = fd;
-               }
-               
-               shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
-               if (ic) {
-                       video_content = ic;
-                       video_decoder.reset (new ImageMagickDecoder (_film, ic));
+                       shared_ptr<const VideoContent> video_content;
+                       shared_ptr<const AudioContent> audio_content;
+                       shared_ptr<VideoDecoder> video_decoder;
+                       shared_ptr<AudioDecoder> audio_decoder;
+                       
+                       /* XXX: into content? */
+                       
+                       shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+                       if (fc) {
+                               shared_ptr<FFmpegDecoder> fd (
+                                       new FFmpegDecoder (
+                                               _film, fc, _video,
+                                               _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
+                                               _subtitles
+                                               )
+                                       );
+                               
+                               video_content = fc;
+                               audio_content = fc;
+                               video_decoder = fd;
+                               audio_decoder = fd;
+                       }
+                       
+                       shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
+                       if (ic) {
+                               video_content = ic;
+                               video_decoder.reset (new ImageMagickDecoder (_film, ic));
+                       }
+                       
+                       video_decoder->connect_video (shared_from_this ());
+                       _video_decoders.push_back (video_decoder);
+                       _video_start.push_back (video_so_far);
+                       video_so_far += video_content->video_length() / video_content->video_frame_rate();
+                       
+                       if (audio_decoder && _playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
+                               audio_decoder->Audio.connect (bind (&Player::process_audio, this, audio_content, _1, _2));
+                               _audio_decoders.push_back (audio_decoder);
+                               _audio_start.push_back (audio_so_far);
+                               audio_so_far += double(audio_content->audio_length()) / audio_content->audio_frame_rate();
+                       }
                }
                
-               video_decoder->connect_video (shared_from_this ());
-               _video_decoders.push_back (video_decoder);
-               _video_start.push_back (video_so_far);
-               video_so_far += video_content->video_length() / video_content->video_frame_rate();
-
-               if (audio_decoder && _playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
-                       audio_decoder->Audio.connect (bind (&Player::process_audio, this, audio_content, _1, _2));
-                       _audio_decoders.push_back (audio_decoder);
-                       _audio_start.push_back (audio_so_far);
-                       audio_so_far += double(audio_content->audio_length()) / audio_content->audio_frame_rate();
-               }
-       }
-       
-       _video_decoder = 0;
-       _sequential_audio_decoder = 0;
-
-       if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+               _video_decoder = 0;
+               _sequential_audio_decoder = 0;
                
-               list<shared_ptr<const AudioContent> > ac = _playlist->audio ();
-               for (list<shared_ptr<const AudioContent> >::iterator i = ac.begin(); i != ac.end(); ++i) {
+               if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
                        
-                       shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
-                       assert (sc);
-                       
-                       shared_ptr<AudioDecoder> d (new SndfileDecoder (_film, sc));
-                       d->Audio.connect (bind (&Player::process_audio, this, sc, _1, _2));
-                       _audio_decoders.push_back (d);
-                       _audio_start.push_back (audio_so_far);
+                       list<shared_ptr<const AudioContent> > ac = _playlist->audio ();
+                       for (list<shared_ptr<const AudioContent> >::iterator i = ac.begin(); i != ac.end(); ++i) {
+                               
+                               shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
+                               assert (sc);
+                               
+                               shared_ptr<AudioDecoder> d (new SndfileDecoder (_film, sc));
+                               d->Audio.connect (bind (&Player::process_audio, this, sc, _1, _2));
+                               _audio_decoders.push_back (d);
+                               _audio_start.push_back (audio_so_far);
+                       }
                }
        }
 }
index 3c69ae15f4599b2ee08c2c87f59ade6ba6138f37..216244bd0ef309fd470de5c25744136b06b2ecd3 100644 (file)
@@ -17,6 +17,7 @@
 
 */
 
+#include <libcxml/cxml.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/lexical_cast.hpp>
 #include "playlist.h"
@@ -26,6 +27,7 @@
 #include "ffmpeg_decoder.h"
 #include "ffmpeg_content.h"
 #include "imagemagick_decoder.h"
+#include "imagemagick_content.h"
 #include "job.h"
 
 using std::list;
@@ -41,12 +43,24 @@ using boost::lexical_cast;
 
 Playlist::Playlist ()
        : _audio_from (AUDIO_FFMPEG)
+       , _loop (1)
 {
 
 }
 
+Playlist::Playlist (shared_ptr<const Playlist> other)
+       : _audio_from (other->_audio_from)
+       , _loop (other->_loop)
+{
+       for (ContentList::const_iterator i = other->_content.begin(); i != other->_content.end(); ++i) {
+               _content.push_back ((*i)->clone ());
+       }
+
+       setup ();
+}
+
 void
-Playlist::setup (ContentList content)
+Playlist::setup ()
 {
        _audio_from = AUDIO_FFMPEG;
 
@@ -59,7 +73,7 @@ Playlist::setup (ContentList content)
        
        _content_connections.clear ();
 
-       for (ContentList::const_iterator i = content.begin(); i != content.end(); ++i) {
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
 
                /* Video is video */
                shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
@@ -114,7 +128,7 @@ Playlist::audio_length () const
                break;
        }
 
-       return len;
+       return len * _loop;
 }
 
 /** @return number of audio channels */
@@ -182,7 +196,7 @@ Playlist::video_length () const
                len += (*i)->video_length ();
        }
        
-       return len;
+       return len * _loop;
 }
 
 bool
@@ -258,6 +272,8 @@ Playlist::audio_digest () const
                }
        }
 
+       t += lexical_cast<string> (_loop);
+
        return md5_digest (t.c_str(), t.length());
 }
 
@@ -274,6 +290,8 @@ Playlist::video_digest () const
                }
        }
 
+       t += lexical_cast<string> (_loop);
+
        return md5_digest (t.c_str(), t.length());
 }
 
@@ -288,3 +306,109 @@ Playlist::content_length () const
                ContentVideoFrame (audio_length() * vfr / afr)
                );
 }
+
+void
+Playlist::set_from_xml (shared_ptr<const cxml::Node> node)
+{
+       list<shared_ptr<cxml::Node> > c = node->node_children ("Content");
+       for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+
+               string const type = (*i)->string_child ("Type");
+               boost::shared_ptr<Content> c;
+               
+               if (type == "FFmpeg") {
+                       c.reset (new FFmpegContent (*i));
+               } else if (type == "ImageMagick") {
+                       c.reset (new ImageMagickContent (*i));
+               } else if (type == "Sndfile") {
+                       c.reset (new SndfileContent (*i));
+               }
+
+               _content.push_back (c);
+       }
+
+       _loop = node->number_child<int> ("Loop");
+}
+
+void
+Playlist::as_xml (xmlpp::Node* node)
+{
+       for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
+               (*i)->as_xml (node->add_child ("Content"));
+       }
+
+       node->add_child("Loop")->add_child_text(lexical_cast<string> (_loop));
+}
+
+void
+Playlist::add (shared_ptr<Content> c)
+{
+       _content.push_back (c);
+       setup ();
+}
+
+void
+Playlist::remove (shared_ptr<Content> c)
+{
+       ContentList::iterator i = find (_content.begin(), _content.end(), c);
+       if (i != _content.end ()) {
+               _content.erase (i);
+       }
+
+       setup ();
+}
+
+void
+Playlist::move_earlier (shared_ptr<Content> c)
+{
+       ContentList::iterator i = find (_content.begin(), _content.end(), c);
+       if (i == _content.begin () || i == _content.end()) {
+               return;
+       }
+
+       ContentList::iterator j = i;
+       --j;
+
+       swap (*i, *j);
+
+       setup ();
+}
+
+void
+Playlist::move_later (shared_ptr<Content> c)
+{
+       ContentList::iterator i = find (_content.begin(), _content.end(), c);
+       if (i == _content.end()) {
+               return;
+       }
+
+       ContentList::iterator j = i;
+       ++j;
+       if (j == _content.end ()) {
+               return;
+       }
+
+       swap (*i, *j);
+
+       setup ();
+}
+
+void
+Playlist::set_loop (int l)
+{
+       _loop = l;
+       Changed ();
+}
+               
+shared_ptr<FFmpegContent>
+Playlist::ffmpeg () const
+{
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
+               if (fc) {
+                       return fc;
+               }
+       }
+
+       return shared_ptr<FFmpegContent> ();
+}
index 85bde64ff7e741b10f8c1bc5b3d1d64c3c898a8e..935bbb2bdddd759cebd869340bbb68561ab7c142 100644 (file)
@@ -51,8 +51,15 @@ class Playlist
 {
 public:
        Playlist ();
+       Playlist (boost::shared_ptr<const Playlist>);
 
-       void setup (ContentList);
+       void as_xml (xmlpp::Node *);
+       void set_from_xml (boost::shared_ptr<const cxml::Node>);
+
+       void add (boost::shared_ptr<Content>);
+       void remove (boost::shared_ptr<Content>);
+       void move_earlier (boost::shared_ptr<Content>);
+       void move_later (boost::shared_ptr<Content>);
 
        ContentAudioFrame audio_length () const;
        int audio_channels () const;
@@ -75,6 +82,12 @@ public:
                return _audio_from;
        }
 
+       ContentList content () const {
+               return _content;
+       }
+
+       boost::shared_ptr<FFmpegContent> ffmpeg () const;
+
        std::list<boost::shared_ptr<const VideoContent> > video () const {
                return _video;
        }
@@ -86,15 +99,24 @@ public:
        std::string audio_digest () const;
        std::string video_digest () const;
 
+       int loop () const {
+               return _loop;
+       }
+       
+       void set_loop (int l);
+
        mutable boost::signals2::signal<void ()> Changed;
        mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
        
 private:
+       void setup ();
        void content_changed (boost::weak_ptr<Content>, int);
 
        /** where we should get our audio from */
        AudioFrom _audio_from;
 
+       /** all our content */
+       ContentList _content;
        /** all our content which contains video */
        std::list<boost::shared_ptr<const VideoContent> > _video;
        /** all our content which contains audio.  This may contain the same objects
@@ -102,5 +124,7 @@ private:
         */
        std::list<boost::shared_ptr<const AudioContent> > _audio;
 
+       int _loop;
+
        std::list<boost::signals2::connection> _content_connections;
 };
index b6be39e9b5d90e3a8bf6afd8007422e6c49c95b5..4f08953b9537ffce7eef4d2185cbd779727aca91 100644 (file)
@@ -214,6 +214,8 @@ FilmEditor::connect_to_widgets ()
        _content_edit->Connect           (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_edit_clicked), 0, this);
        _content_earlier->Connect        (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this);
        _content_later->Connect          (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this);
+       _loop_content->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this);
+       _loop_count->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::loop_count_changed), 0, this);
        _left_crop->Connect              (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
        _right_crop->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
        _top_crop->Connect               (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
@@ -369,8 +371,18 @@ FilmEditor::make_content_panel ()
                 _content_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
         }
 
+       wxBoxSizer* h = new wxBoxSizer (wxHORIZONTAL);
+       _loop_content = new wxCheckBox (_content_panel, wxID_ANY, _("Loop everything"));
+       h->Add (_loop_content, 0, wxALL, 6);
+       _loop_count = new wxSpinCtrl (_content_panel, wxID_ANY);
+       h->Add (_loop_count, 0, wxALL, 6);
+       add_label_to_sizer (h, _content_panel, _("times"));
+       _content_sizer->Add (h, 0, wxALL, 6);
+
        _content_information = new wxTextCtrl (_content_panel, wxID_ANY, wxT ("\n\n\n\n"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE);
        _content_sizer->Add (_content_information, 1, wxEXPAND | wxALL, 6);
+
+       _loop_count->SetRange (2, 1024);
 }
 
 void
@@ -620,6 +632,11 @@ FilmEditor::film_changed (Film::Property p)
                setup_show_audio_sensitivity ();
                setup_length ();
                break;
+       case Film::LOOP:
+               checked_set (_loop_content, _film->loop() > 1);
+               checked_set (_loop_count, _film->loop());
+               setup_loop_sensitivity ();
+               break;
        case Film::TRUST_CONTENT_HEADERS:
                checked_set (_trust_content_headers, _film->trust_content_headers ());
                break;
@@ -890,6 +907,7 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
        film_changed (Film::CONTENT);
+       film_changed (Film::LOOP);
        film_changed (Film::TRUST_CONTENT_HEADERS);
        film_changed (Film::DCP_CONTENT_TYPE);
        film_changed (Film::FORMAT);
@@ -1491,3 +1509,27 @@ FilmEditor::trim_type_changed (wxCommandEvent &)
 {
        _film->set_trim_type (_trim_type->GetSelection () == 0 ? Film::CPL : Film::ENCODE);
 }
+
+void
+FilmEditor::loop_content_toggled (wxCommandEvent &)
+{
+       if (_loop_content->GetValue ()) {
+               _film->set_loop (_loop_count->GetValue ());
+       } else {
+               _film->set_loop (1);
+       }
+               
+       setup_loop_sensitivity ();
+}
+
+void
+FilmEditor::loop_count_changed (wxCommandEvent &)
+{
+       _film->set_loop (_loop_count->GetValue ());
+}
+
+void
+FilmEditor::setup_loop_sensitivity ()
+{
+       _loop_count->Enable (_loop_content->GetValue ());
+}
index ffffc1e76de853ffb03b045ff2c84d96517bb34e..5944633a8cbbe13b7ba91db070a0b66fe0384a19 100644 (file)
@@ -93,6 +93,8 @@ private:
        void dcp_frame_rate_changed (wxCommandEvent &);
        void best_dcp_frame_rate_clicked (wxCommandEvent &);
        void edit_filters_clicked (wxCommandEvent &);
+       void loop_content_toggled (wxCommandEvent &);
+       void loop_count_changed (wxCommandEvent &);
 
        /* Handle changes to the model */
        void film_changed (Film::Property);
@@ -113,6 +115,7 @@ private:
        void setup_length ();
        void setup_content_information ();
        void setup_content_button_sensitivity ();
+       void setup_loop_sensitivity ();
        
        void active_jobs_changed (bool);
        boost::shared_ptr<Content> selected_content ();
@@ -143,6 +146,8 @@ private:
        wxButton* _content_earlier;
        wxButton* _content_later;
        wxTextCtrl* _content_information;
+       wxCheckBox* _loop_content;
+       wxSpinCtrl* _loop_count;
        wxButton* _edit_dci_button;
        wxChoice* _format;
        wxStaticText* _format_description;