Merge branch '1.0' into 1.0-vob
authorCarl Hetherington <cth@carlh.net>
Fri, 29 Nov 2013 23:01:31 +0000 (23:01 +0000)
committerCarl Hetherington <cth@carlh.net>
Fri, 29 Nov 2013 23:01:31 +0000 (23:01 +0000)
65 files changed:
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/content.cc
src/lib/content.h
src/lib/content_factory.cc
src/lib/content_factory.h
src/lib/exceptions.h
src/lib/ffmpeg.cc
src/lib/ffmpeg.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/file_group.cc [new file with mode: 0644]
src/lib/file_group.h [new file with mode: 0644]
src/lib/film.cc
src/lib/image_content.cc [new file with mode: 0644]
src/lib/image_content.h [new file with mode: 0644]
src/lib/image_decoder.cc [new file with mode: 0644]
src/lib/image_decoder.h [new file with mode: 0644]
src/lib/image_examiner.cc [new file with mode: 0644]
src/lib/image_examiner.h [new file with mode: 0644]
src/lib/moving_image.h [deleted file]
src/lib/moving_image_content.cc [deleted file]
src/lib/moving_image_content.h [deleted file]
src/lib/moving_image_decoder.cc [deleted file]
src/lib/moving_image_decoder.h [deleted file]
src/lib/moving_image_examiner.cc [deleted file]
src/lib/moving_image_examiner.h [deleted file]
src/lib/player.cc
src/lib/playlist.cc
src/lib/playlist.h
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/still_image.h [deleted file]
src/lib/still_image_content.cc [deleted file]
src/lib/still_image_content.h [deleted file]
src/lib/still_image_decoder.cc [deleted file]
src/lib/still_image_decoder.h [deleted file]
src/lib/still_image_examiner.cc [deleted file]
src/lib/still_image_examiner.h [deleted file]
src/lib/subtitle_content.cc
src/lib/subtitle_content.h
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/wscript
src/tools/dcpomatic.cc
src/wx/audio_dialog.cc
src/wx/content_menu.cc
src/wx/content_menu.h
src/wx/film_editor.cc
src/wx/film_viewer.cc
src/wx/hints_dialog.cc
src/wx/timeline.cc
src/wx/timing_panel.cc
test/black_fill_test.cc
test/file_group_test.cc [new file with mode: 0644]
test/scaling_test.cc
test/stream_test.cc
test/util_test.cc
test/wscript

index e0eaacb919137f74a89756f61714da9d38eaf0f4..04823d1e6f12a0aa653a6c6a0f3a3c9ae57af29c 100644 (file)
 #include "analyse_audio_job.h"
 #include "job_manager.h"
 #include "film.h"
+#include "exceptions.h"
+
+#include "i18n.h"
 
 using std::string;
+using std::vector;
 using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::dynamic_pointer_cast;
@@ -60,6 +64,28 @@ AudioContent::AudioContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod
        _audio_delay = node->number_child<int> ("AudioDelay");
 }
 
+AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
+       : Content (f, c)
+{
+       shared_ptr<AudioContent> ref = dynamic_pointer_cast<AudioContent> (c[0]);
+       assert (ref);
+       
+       for (size_t i = 0; i < c.size(); ++i) {
+               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c[i]);
+
+               if (ac->audio_gain() != ref->audio_gain()) {
+                       throw JoinError (_("Content to be joined must have the same audio gain."));
+               }
+
+               if (ac->audio_delay() != ref->audio_delay()) {
+                       throw JoinError (_("Content to be joined must have the same audio delay."));
+               }
+       }
+
+       _audio_gain = ref->audio_gain ();
+       _audio_delay = ref->audio_delay ();
+}
+
 void
 AudioContent::as_xml (xmlpp::Node* node) const
 {
index 73919105d8a339cab8e8f93df6a61f77bf146534..b100d7abad18f4f83e5f724a92b8ca322a490d60 100644 (file)
@@ -46,6 +46,7 @@ public:
        AudioContent (boost::shared_ptr<const Film>, Time);
        AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
 
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
index a41261998671d3083f54c3fd5c921ddeba7de89e..f09012765a7cbe7616d1e2ae40a870981b74604a 100644 (file)
 #include "util.h"
 #include "content_factory.h"
 #include "ui_signaller.h"
+#include "exceptions.h"
+#include "film.h"
+
+#include "i18n.h"
 
 using std::string;
 using std::stringstream;
 using std::set;
+using std::list;
+using std::cout;
+using std::vector;
 using boost::shared_ptr;
 using boost::lexical_cast;
 
@@ -37,6 +44,16 @@ int const ContentProperty::LENGTH = 402;
 int const ContentProperty::TRIM_START = 403;
 int const ContentProperty::TRIM_END = 404;
 
+Content::Content (shared_ptr<const Film> f)
+       : _film (f)
+       , _position (0)
+       , _trim_start (0)
+       , _trim_end (0)
+       , _change_signals_frequent (false)
+{
+
+}
+
 Content::Content (shared_ptr<const Film> f, Time p)
        : _film (f)
        , _position (p)
@@ -49,32 +66,58 @@ Content::Content (shared_ptr<const Film> f, Time p)
 
 Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
        : _film (f)
-       , _path (p)
        , _position (0)
        , _trim_start (0)
        , _trim_end (0)
        , _change_signals_frequent (false)
 {
-
+       _paths.push_back (p);
 }
 
 Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
        : _film (f)
        , _change_signals_frequent (false)
 {
-       _path = node->string_child ("Path");
+       list<cxml::NodePtr> path_children = node->node_children ("Path");
+       for (list<cxml::NodePtr>::const_iterator i = path_children.begin(); i != path_children.end(); ++i) {
+               _paths.push_back ((*i)->content ());
+       }
        _digest = node->string_child ("Digest");
        _position = node->number_child<Time> ("Position");
        _trim_start = node->number_child<Time> ("TrimStart");
        _trim_end = node->number_child<Time> ("TrimEnd");
 }
 
+Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
+       : _film (f)
+       , _position (c.front()->position ())
+       , _trim_start (c.front()->trim_start ())
+       , _trim_end (c.back()->trim_end ())
+       , _change_signals_frequent (false)
+{
+       for (size_t i = 0; i < c.size(); ++i) {
+               if (i > 0 && c[i]->trim_start ()) {
+                       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 ()) {
+                       throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
+               }
+
+               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) const
 {
        boost::mutex::scoped_lock lm (_mutex);
-       
-       node->add_child("Path")->add_child_text (_path.string());
+
+       for (vector<boost::filesystem::path>::const_iterator i = _paths.begin(); i != _paths.end(); ++i) {
+               node->add_child("Path")->add_child_text (i->string ());
+       }
        node->add_child("Digest")->add_child_text (_digest);
        node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
        node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
@@ -85,15 +128,10 @@ void
 Content::examine (shared_ptr<Job> job)
 {
        boost::mutex::scoped_lock lm (_mutex);
-       boost::filesystem::path p = _path;
+       vector<boost::filesystem::path> p = _paths;
        lm.unlock ();
        
-       string d;
-       if (boost::filesystem::is_regular_file (p)) {
-               d = md5_digest (p);
-       } else {
-               d = md5_digest_directory (p, job);
-       }
+       string const d = md5_digest (p, job);
 
        lm.lock ();
        _digest = d;
@@ -153,13 +191,13 @@ Content::clone () const
        xmlpp::Document doc;
        xmlpp::Node* node = doc.create_root_node ("Content");
        as_xml (node);
-       return content_factory (film, cxml::NodePtr(new cxml::Node (node)));
+       return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::state_version);
 }
 
 string
 Content::technical_summary () const
 {
-       return String::compose ("%1 %2 %3", path(), digest(), position());
+       return String::compose ("%1 %2 %3", path_summary(), digest(), position());
 }
 
 Time
@@ -196,14 +234,34 @@ Content::identifier () const
 bool
 Content::path_valid () const
 {
-       return boost::filesystem::exists (_path);
+       for (vector<boost::filesystem::path>::const_iterator i = _paths.begin(); i != _paths.end(); ++i) {
+               if (!boost::filesystem::exists (*i)) {
+                       return false;
+               }
+       }
+
+       return true;
 }
 
 void
 Content::set_path (boost::filesystem::path path)
 {
-       _path = path;
+       _paths.clear ();
+       _paths.push_back (path);
        signal_changed (ContentProperty::PATH);
 }
 
-       
+string
+Content::path_summary () const
+{
+       /* XXX: should handle multiple paths more gracefully */
+
+       assert (number_of_paths ());
+
+       string s = path(0).filename().string ();
+       if (number_of_paths() > 1) {
+               s += " ...";
+       }
+
+       return s;
+}
index 626e270bdfedd07e5d4b3897a5cf81873cc4823e..9cf6d866ab04906b3288188792c66f9403b8592e 100644 (file)
@@ -48,9 +48,11 @@ public:
 class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
 {
 public:
+       Content (boost::shared_ptr<const Film>);
        Content (boost::shared_ptr<const Film>, Time);
        Content (boost::shared_ptr<const Film>, boost::filesystem::path);
        Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
        virtual ~Content () {}
        
        virtual void examine (boost::shared_ptr<Job>);
@@ -64,12 +66,24 @@ public:
        boost::shared_ptr<Content> clone () const;
 
        void set_path (boost::filesystem::path);
-       
-       boost::filesystem::path path () const {
+
+       std::string path_summary () const;
+
+       std::vector<boost::filesystem::path> paths () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _paths;
+       }
+
+       size_t number_of_paths () const {
                boost::mutex::scoped_lock lm (_mutex);
-               return _path;
+               return _paths.size ();
        }
 
+       boost::filesystem::path path (size_t i) const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _paths[i];
+       }
+       
        bool path_valid () const;
 
        /** @return MD5 digest of the content's file(s) */
@@ -126,9 +140,10 @@ protected:
        */
        mutable boost::mutex _mutex;
 
+       /** Paths of our data files */
+       std::vector<boost::filesystem::path> _paths;
+       
 private:
-       /** Path of a file or a directory containing files */
-       boost::filesystem::path _path;
        std::string _digest;
        Time _position;
        Time _trim_start;
index ed9a9e7699da3c22fcef43a854b5c4b9c2edf316..bab22b8eb19e14b6179fd27696710c6b630df70c 100644 (file)
@@ -19,8 +19,7 @@
 
 #include <libcxml/cxml.h>
 #include "ffmpeg_content.h"
-#include "still_image_content.h"
-#include "moving_image_content.h"
+#include "image_content.h"
 #include "sndfile_content.h"
 #include "util.h"
 
@@ -28,20 +27,18 @@ using std::string;
 using boost::shared_ptr;
 
 shared_ptr<Content>
-content_factory (shared_ptr<const Film> film, cxml::NodePtr node)
+content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version)
 {
        string const type = node->string_child ("Type");
 
        boost::shared_ptr<Content> content;
        
        if (type == "FFmpeg") {
-               content.reset (new FFmpegContent (film, node));
-       } else if (type == "StillImage") {
-               content.reset (new StillImageContent (film, node));
-       } else if (type == "MovingImage") {
-               content.reset (new MovingImageContent (film, node));
+               content.reset (new FFmpegContent (film, node, version));
+       } else if (type == "Image") {
+               content.reset (new ImageContent (film, node, version));
        } else if (type == "Sndfile") {
-               content.reset (new SndfileContent (film, node));
+               content.reset (new SndfileContent (film, node, version));
        }
 
        return content;
@@ -53,7 +50,7 @@ content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
        shared_ptr<Content> content;
                
        if (valid_image_file (path)) {
-               content.reset (new StillImageContent (film, path));
+               content.reset (new ImageContent (film, path));
        } else if (SndfileContent::valid_file (path)) {
                content.reset (new SndfileContent (film, path));
        } else {
index d2f75051a2bd0ef2ff47a32c959adc500da93695..071d925e034bbc19309671ede4c07dcd8014dcf0 100644 (file)
@@ -19,5 +19,5 @@
 
 class Film;
 
-extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, cxml::NodePtr);
+extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, cxml::NodePtr, int);
 extern boost::shared_ptr<Content> content_factory (boost::shared_ptr<const Film>, boost::filesystem::path);
index b04d973dc7a62197571ce7d6697917f2023b23e0..f4631c09b67310bcf75492daca3b2b58dfd5e835 100644 (file)
@@ -104,7 +104,14 @@ private:
        /** name of the file that this exception concerns */
        boost::filesystem::path _file;
 };
-       
+
+class JoinError : public StringError
+{
+public:
+       JoinError (std::string s)
+               : StringError (s)
+       {}
+};
 
 /** @class OpenFileError.
  *  @brief Indicates that some error occurred when trying to open a file.
index dbafba0f03a8bfe8e741e3a2f7a08c3e95ec7b2d..e85a2c44e4912200bf5ebf2013a454b44ddc1e22 100644 (file)
@@ -37,14 +37,16 @@ using boost::lexical_cast;
 
 boost::mutex FFmpeg::_mutex;
 
-/** @param long_probe true to do a long probe of the file looking for streams */
-FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c, bool long_probe)
+FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
        : _ffmpeg_content (c)
+       , _avio_buffer (0)
+       , _avio_buffer_size (4096)
+       , _avio_context (0)
        , _format_context (0)
        , _frame (0)
        , _video_stream (-1)
 {
-       setup_general (long_probe);
+       setup_general ();
        setup_video ();
        setup_audio ();
 }
@@ -65,22 +67,38 @@ FFmpeg::~FFmpeg ()
        avformat_close_input (&_format_context);
 }
 
+static int
+avio_read_wrapper (void* data, uint8_t* buffer, int amount)
+{
+       return reinterpret_cast<FFmpeg*>(data)->avio_read (buffer, amount);
+}
+
+static int64_t
+avio_seek_wrapper (void* data, int64_t offset, int whence)
+{
+       return reinterpret_cast<FFmpeg*>(data)->avio_seek (offset, whence);
+}
+
 void
-FFmpeg::setup_general (bool long_probe)
+FFmpeg::setup_general ()
 {
        av_register_all ();
 
+       _file_group.set_paths (_ffmpeg_content->paths ());
+       _avio_buffer = static_cast<uint8_t*> (av_malloc (_avio_buffer_size));
+       _avio_context = avio_alloc_context (_avio_buffer, _avio_buffer_size, 0, this, avio_read_wrapper, 0, avio_seek_wrapper);
+       _format_context = avformat_alloc_context ();
+       _format_context->pb = _avio_context;
+       
        AVDictionary* options = 0;
-       if (long_probe) {
-               /* These durations are in microseconds, and represent how far into the content file
-                  we will look for streams.
-               */
-               av_dict_set (&options, "analyzeduration", lexical_cast<string> (5 * 60 * 1e6).c_str(), 0);
-               av_dict_set (&options, "probesize", lexical_cast<string> (5 * 60 * 1e6).c_str(), 0);
-       }
+       /* These durations are in microseconds, and represent how far into the content file
+          we will look for streams.
+       */
+       av_dict_set (&options, "analyzeduration", lexical_cast<string> (5 * 60 * 1e6).c_str(), 0);
+       av_dict_set (&options, "probesize", lexical_cast<string> (5 * 60 * 1e6).c_str(), 0);
        
-       if (avformat_open_input (&_format_context, _ffmpeg_content->path().string().c_str(), 0, &options) < 0) {
-               throw OpenFileError (_ffmpeg_content->path().string ());
+       if (avformat_open_input (&_format_context, 0, 0, &options) < 0) {
+               throw OpenFileError (_ffmpeg_content->path(0).string ());
        }
 
        if (avformat_find_stream_info (_format_context, 0) < 0) {
@@ -155,5 +173,21 @@ FFmpeg::video_codec_context () const
 AVCodecContext *
 FFmpeg::audio_codec_context () const
 {
-       return _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
+       return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
+}
+
+int
+FFmpeg::avio_read (uint8_t* buffer, int const amount)
+{
+       return _file_group.read (buffer, amount);
+}
+
+int64_t
+FFmpeg::avio_seek (int64_t const pos, int whence)
+{
+       if (whence == AVSEEK_SIZE) {
+               return _file_group.length ();
+       }
+       
+       return _file_group.seek (pos, whence);
 }
index d5f4db2916c67e45fb014bb676397579a1cb8ef4..760918437d403dd0624984e31a07fb0bdddff583 100644 (file)
@@ -26,6 +26,7 @@
 extern "C" {
 #include <libavcodec/avcodec.h>
 }
+#include "file_group.h"
 
 struct AVFilterGraph;
 struct AVCodecContext;
@@ -35,29 +36,39 @@ struct AVFrame;
 struct AVBufferContext;
 struct AVCodec;
 struct AVStream;
+struct AVIOContext;
 
 class FFmpegContent;
 
 class FFmpeg
 {
 public:
-       FFmpeg (boost::shared_ptr<const FFmpegContent>, bool);
+       FFmpeg (boost::shared_ptr<const FFmpegContent>);
        virtual ~FFmpeg ();
 
        boost::shared_ptr<const FFmpegContent> ffmpeg_content () const {
                return _ffmpeg_content;
        }
 
+       int avio_read (uint8_t *, int);
+       int64_t avio_seek (int64_t, int);
+
 protected:
        AVCodecContext* video_codec_context () const;
        AVCodecContext* audio_codec_context () const;
        
        boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
 
+       uint8_t* _avio_buffer;
+       int _avio_buffer_size;
+       AVIOContext* _avio_context;
+       FileGroup _file_group;
+       
        AVFormatContext* _format_context;
        AVPacket _packet;
        AVFrame* _frame;
 
+       /** Index of video stream within AVFormatContext */
        int _video_stream;
 
        /* It would appear (though not completely verified) that one must have
@@ -67,7 +78,7 @@ protected:
        static boost::mutex _mutex;
 
 private:
-       void setup_general (bool);
+       void setup_general ();
        void setup_video ();
        void setup_audio ();
 };
index a6f9e6ac89140fb967ec7e1fa51aedd77978698f..9533315a517e2a0501796bfe3d6f1522f55ff27a 100644 (file)
@@ -17,6 +17,9 @@
 
 */
 
+extern "C" {
+#include <libavformat/avformat.h>
+}
 #include <libcxml/cxml.h>
 #include "ffmpeg_content.h"
 #include "ffmpeg_examiner.h"
@@ -26,6 +29,7 @@
 #include "filter.h"
 #include "film.h"
 #include "log.h"
+#include "exceptions.h"
 
 #include "i18n.h"
 
@@ -37,6 +41,7 @@ using std::cout;
 using std::pair;
 using boost::shared_ptr;
 using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
 
 int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
 int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
@@ -53,7 +58,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path
 
 }
 
-FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
        : Content (f, node)
        , VideoContent (f, node)
        , AudioContent (f, node)
@@ -61,7 +66,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::N
 {
        list<cxml::NodePtr> c = node->node_children ("SubtitleStream");
        for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
-               _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
+               _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i, version)));
                if ((*i)->optional_number_child<int> ("Selected")) {
                        _subtitle_stream = _subtitle_streams.back ();
                }
@@ -69,7 +74,7 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::N
 
        c = node->node_children ("AudioStream");
        for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
-               _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i)));
+               _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i, version)));
                if ((*i)->optional_number_child<int> ("Selected")) {
                        _audio_stream = _audio_streams.back ();
                }
@@ -83,6 +88,33 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::N
        _first_video = node->optional_number_child<double> ("FirstVideo");
 }
 
+FFmpegContent::FFmpegContent (shared_ptr<const Film> f, vector<boost::shared_ptr<Content> > c)
+       : Content (f, c)
+       , VideoContent (f, c)
+       , AudioContent (f, c)
+       , SubtitleContent (f, c)
+{
+       shared_ptr<FFmpegContent> ref = dynamic_pointer_cast<FFmpegContent> (c[0]);
+       assert (ref);
+
+       for (size_t i = 0; i < c.size(); ++i) {
+               shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c[i]);
+               if (f->with_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) {
+                       throw JoinError (_("Content to be joined must use the same subtitle stream."));
+               }
+
+               if (*(fc->_audio_stream.get()) != *(ref->_audio_stream.get())) {
+                       throw JoinError (_("Content to be joined must use the same audio stream."));
+               }
+       }
+
+       _subtitle_streams = ref->subtitle_streams ();
+       _subtitle_stream = ref->subtitle_stream ();
+       _audio_streams = ref->audio_streams ();
+       _audio_stream = ref->audio_stream ();
+       _first_video = ref->_first_video;
+}
+
 void
 FFmpegContent::as_xml (xmlpp::Node* node) const
 {
@@ -167,7 +199,7 @@ string
 FFmpegContent::summary () const
 {
        /* Get the string() here so that the name does not have quotes around it */
-       return String::compose (_("%1 [movie]"), path().filename().string());
+       return String::compose (_("%1 [movie]"), path_summary ());
 }
 
 string
@@ -299,17 +331,51 @@ operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
        return a.id == b.id;
 }
 
+bool
+operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+{
+       return a.id != b.id;
+}
+
 bool
 operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
 {
        return a.id == b.id;
 }
 
-FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
-       : mapping (node->node_child ("Mapping"))
+bool
+operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+{
+       return a.id != b.id;
+}
+
+FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node, int version)
+       : _legacy_id (false)
 {
        name = node->string_child ("Name");
        id = node->number_child<int> ("Id");
+       if (version == 4 || node->optional_bool_child ("LegacyId")) {
+               _legacy_id = true;
+       }
+}
+
+void
+FFmpegStream::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("Name")->add_child_text (name);
+       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+       if (_legacy_id) {
+               /* Write this so that version > 4 files are read in correctly
+                  if the Id came originally from a version <= 4 file.
+               */
+               root->add_child("LegacyId")->add_child_text ("1");
+       }
+}
+
+FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
+       : FFmpegStream (node, version)
+       , mapping (node->node_child ("Mapping"))
+{
        frame_rate = node->number_child<int> ("FrameRate");
        channels = node->number_child<int64_t> ("Channels");
        first_audio = node->optional_number_child<double> ("FirstAudio");
@@ -318,8 +384,7 @@ FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
 void
 FFmpegAudioStream::as_xml (xmlpp::Node* root) const
 {
-       root->add_child("Name")->add_child_text (name);
-       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+       FFmpegStream::as_xml (root);
        root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
        root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
        if (first_audio) {
@@ -328,21 +393,57 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const
        mapping.as_xml (root->add_child("Mapping"));
 }
 
+int
+FFmpegStream::index (AVFormatContext const * fc) const
+{
+       if (_legacy_id) {
+               return id;
+       }
+       
+       size_t i = 0;
+       while (i < fc->nb_streams) {
+               if (fc->streams[i]->id == id) {
+                       return i;
+               }
+               ++i;
+       }
+
+       assert (false);
+}
+
+AVStream *
+FFmpegStream::stream (AVFormatContext const * fc) const
+{
+       if (_legacy_id) {
+               return fc->streams[id];
+       }
+       
+       size_t i = 0;
+       while (i < fc->nb_streams) {
+               if (fc->streams[i]->id == id) {
+                       return fc->streams[i];
+               }
+               ++i;
+       }
+
+       assert (false);
+       return 0;
+}
+
 /** Construct a SubtitleStream from a value returned from to_string().
  *  @param t String returned from to_string().
  *  @param v State file version.
  */
-FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node, int version)
+       : FFmpegStream (node, version)
 {
-       name = node->string_child ("Name");
-       id = node->number_child<int> ("Id");
+       
 }
 
 void
 FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
 {
-       root->add_child("Name")->add_child_text (name);
-       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+       FFmpegStream::as_xml (root);
 }
 
 Time
index 775cb92205c9f2b9a1660a3713d08c14ab5871f9..7ff159b852b4cb69df4004b1f6a813ad2131e182 100644 (file)
 #include "subtitle_content.h"
 #include "audio_mapping.h"
 
+struct AVFormatContext;
+struct AVStream;
+
 class Filter;
 class ffmpeg_pts_offset_test;
 
-class FFmpegAudioStream
+class FFmpegStream
 {
 public:
-       FFmpegAudioStream (std::string n, int i, int f, int c)
+       FFmpegStream (std::string n, int i)
                : name (n)
                , id (i)
+               , _legacy_id (false)
+       {}
+                               
+       FFmpegStream (boost::shared_ptr<const cxml::Node>, int);
+
+       void as_xml (xmlpp::Node *) const;
+
+       /** @param c An AVFormatContext.
+        *  @return Stream index within the AVFormatContext.
+        */
+       int index (AVFormatContext const * c) const;
+       AVStream* stream (AVFormatContext const * c) const;
+
+       std::string name;
+       int id;
+       
+private:
+       /** If this is true, id is in fact the index */
+       bool _legacy_id;
+};
+
+class FFmpegAudioStream : public FFmpegStream
+{
+public:
+       FFmpegAudioStream (std::string n, int i, int f, int c)
+               : FFmpegStream (n, i)
                , frame_rate (f)
                , channels (c)
                , mapping (c)
@@ -42,12 +71,10 @@ public:
                mapping.make_default ();
        }
 
-       FFmpegAudioStream (boost::shared_ptr<const cxml::Node>);
+       FFmpegAudioStream (boost::shared_ptr<const cxml::Node>, int);
 
        void as_xml (xmlpp::Node *) const;
-       
-       std::string name;
-       int id;
+
        int frame_rate;
        int channels;
        AudioMapping mapping;
@@ -58,29 +85,28 @@ private:
 
        /* Constructor for tests */
        FFmpegAudioStream ()
-               : mapping (1)
+               : FFmpegStream ("", 0)
+               , mapping (1)
        {}
 };
 
 extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
+extern bool operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
 
-class FFmpegSubtitleStream
+class FFmpegSubtitleStream : public FFmpegStream
 {
 public:
        FFmpegSubtitleStream (std::string n, int i)
-               : name (n)
-               , id (i)
+               : FFmpegStream (n, i)
        {}
        
-       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
+       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>, int);
 
        void as_xml (xmlpp::Node *) const;
-       
-       std::string name;
-       int id;
 };
 
 extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
+extern bool operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
 
 class FFmpegContentProperty : public VideoContentProperty
 {
@@ -96,7 +122,8 @@ class FFmpegContent : public VideoContent, public AudioContent, public SubtitleC
 {
 public:
        FFmpegContent (boost::shared_ptr<const Film>, boost::filesystem::path);
-       FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       FFmpegContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int version);
+       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 ());
@@ -153,7 +180,7 @@ public:
                boost::mutex::scoped_lock lm (_mutex);
                return _first_video;
        }
-       
+
 private:
        friend class ffmpeg_pts_offset_test;
        
index be32475377da2c851647bc4ea8f247218c9d24f5..5a1b78762d92e6e30d55108dfae637f8b348e676 100644 (file)
@@ -63,7 +63,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
        , VideoDecoder (f, c)
        , AudioDecoder (f, c)
        , SubtitleDecoder (f)
-       , FFmpeg (c, false)
+       , FFmpeg (c)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
        , _decode_video (video)
@@ -171,12 +171,14 @@ FFmpegDecoder::pass ()
 
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
+
+       int const si = _packet.stream_index;
        
-       if (_packet.stream_index == _video_stream && _decode_video) {
+       if (si == _video_stream && _decode_video) {
                decode_video_packet ();
-       } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
+       } else if (_ffmpeg_content->audio_stream() && si == _ffmpeg_content->audio_stream()->index (_format_context) && _decode_audio) {
                decode_audio_packet ();
-       } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && film->with_subtitles ()) {
+       } else if (_ffmpeg_content->subtitle_stream() && si == _ffmpeg_content->subtitle_stream()->index (_format_context) && film->with_subtitles ()) {
                decode_subtitle_packet ();
        }
 
@@ -511,11 +513,11 @@ FFmpegDecoder::setup_subtitle ()
 {
        boost::mutex::scoped_lock lm (_mutex);
        
-       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
+       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->index (_format_context) >= int (_format_context->nb_streams)) {
                return;
        }
 
-       _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
+       _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 
        if (_subtitle_codec == 0) {
index 4d10aabbb85daf819f127a6946888446e56121ec..78b6e3121c363faf1b0c95a4701970273b970636 100644 (file)
@@ -24,6 +24,8 @@ extern "C" {
 #include "ffmpeg_examiner.h"
 #include "ffmpeg_content.h"
 
+#include "i18n.h"
+
 using std::string;
 using std::cout;
 using std::max;
@@ -32,7 +34,7 @@ using boost::shared_ptr;
 using boost::optional;
 
 FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
-       : FFmpeg (c, true)
+       : FFmpeg (c)
 {
        /* Find audio and subtitle streams */
 
@@ -50,12 +52,12 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
                        
                        _audio_streams.push_back (
                                shared_ptr<FFmpegAudioStream> (
-                                       new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channels)
+                                       new FFmpegAudioStream (audio_stream_name (s), s->id, s->codec->sample_rate, s->codec->channels)
                                        )
                                );
 
                } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
-                       _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (stream_name (s), i)));
+                       _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (subtitle_stream_name (s), s->id)));
                }
        }
 
@@ -78,9 +80,9 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
                        }
                } else {
                        for (size_t i = 0; i < _audio_streams.size(); ++i) {
-                               if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->first_audio) {
+                               if (_packet.stream_index == _audio_streams[i]->index (_format_context) && !_audio_streams[i]->first_audio) {
                                        if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                                               _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->id);
+                                               _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->index (_format_context));
                                        }
                                }
                        }
@@ -140,6 +142,36 @@ FFmpegExaminer::video_length () const
        return max (1, length);
 }
 
+string
+FFmpegExaminer::audio_stream_name (AVStream* s) const
+{
+       stringstream n;
+
+       n << stream_name (s);
+
+       if (!n.str().empty()) {
+               n << "; ";
+       }
+
+       n << s->codec->channels << " channels";
+
+       return n.str ();
+}
+
+string
+FFmpegExaminer::subtitle_stream_name (AVStream* s) const
+{
+       stringstream n;
+
+       n << stream_name (s);
+
+       if (n.str().empty()) {
+               n << _("unknown");
+       }
+
+       return n.str ();
+}
+
 string
 FFmpegExaminer::stream_name (AVStream* s) const
 {
@@ -160,11 +192,5 @@ FFmpegExaminer::stream_name (AVStream* s) const
                }
        }
 
-       if (!n.str().empty()) {
-               n << "; ";
-       }
-
-       n << s->codec->channels << " channels";
-
        return n.str ();
 }
index 4912d899a85f7a70015fa83c72fe61743cb6aab2..4de475d2a3f78d753985f6b165e56bf3b74c7290 100644 (file)
@@ -47,6 +47,8 @@ public:
        
 private:
        std::string stream_name (AVStream* s) const;
+       std::string audio_stream_name (AVStream* s) const;
+       std::string subtitle_stream_name (AVStream* s) const;
        boost::optional<double> frame_time (int) const;
        
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
diff --git a/src/lib/file_group.cc b/src/lib/file_group.cc
new file mode 100644 (file)
index 0000000..9c20651
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cstdio>
+#include <sndfile.h>
+#include "file_group.h"
+#include "exceptions.h"
+
+using std::vector;
+using std::cout;
+
+FileGroup::FileGroup ()
+       : _current_path (0)
+       , _current_file (0)
+{
+
+}
+
+FileGroup::FileGroup (boost::filesystem::path p)
+       : _current_path (0)
+       , _current_file (0)
+{
+       _paths.push_back (p);
+       seek (0, SEEK_SET);
+}
+
+FileGroup::FileGroup (vector<boost::filesystem::path> const & p)
+       : _paths (p)
+       , _current_path (0)
+       , _current_file (0)
+{
+       ensure_open_path (0);
+       seek (0, SEEK_SET);
+}
+
+FileGroup::~FileGroup ()
+{
+       if (_current_file) {
+               fclose (_current_file);
+       }
+}
+
+void
+FileGroup::set_paths (vector<boost::filesystem::path> const & p)
+{
+       _paths = p;
+       ensure_open_path (0);
+       seek (0, SEEK_SET);
+}
+
+/** Ensure that the given path index in the content is the _current_file */
+void
+FileGroup::ensure_open_path (size_t p) const
+{
+       if (_current_file && _current_path == p) {
+               /* Already open */
+               return;
+       }
+       
+       if (_current_file) {
+               fclose (_current_file);
+       }
+
+       _current_path = p;
+       _current_file = fopen (_paths[_current_path].string().c_str(), "rb");
+       if (_current_file == 0) {
+               throw OpenFileError (_paths[_current_path]);
+       }
+}
+
+int64_t
+FileGroup::seek (int64_t pos, int whence) const
+{
+       /* Convert pos to `full_pos', which is an offset from the start
+          of all the files.
+       */
+       int64_t full_pos = 0;
+       switch (whence) {
+       case SEEK_SET:
+               full_pos = pos;
+               break;
+       case SEEK_CUR:
+               for (size_t i = 0; i < _current_path; ++i) {
+                       full_pos += boost::filesystem::file_size (_paths[i]);
+               }
+               full_pos += ftell (_current_file);
+               full_pos += pos;
+               break;
+       case SEEK_END:
+               full_pos = length() - pos;
+               break;
+       }
+
+       /* Seek to full_pos */
+       size_t i = 0;
+       int64_t sub_pos = full_pos;
+       while (i < _paths.size ()) {
+               boost::uintmax_t len = boost::filesystem::file_size (_paths[i]);
+               if (sub_pos < int64_t (len)) {
+                       break;
+               }
+               sub_pos -= len;
+               ++i;
+       }
+
+       if (i == _paths.size ()) {
+               return -1;
+       }
+
+       ensure_open_path (i);
+       fseek (_current_file, sub_pos, SEEK_SET);
+       return full_pos;
+}
+
+/** Try to read some data from the current position into a buffer.
+ *  @param buffer Buffer to write data into.
+ *  @param amount Number of bytes to read.
+ *  @return Number of bytes read, or -1 in the case of error.
+ */
+int
+FileGroup::read (uint8_t* buffer, int amount) const
+{
+       int read = 0;
+       while (1) {
+               int const this_time = fread (buffer + read, 1, amount - read, _current_file);
+               read += this_time;
+               if (read == amount) {
+                       /* Done */
+                       break;
+               }
+
+               /* See if there is another file to use */
+               if ((_current_path + 1) >= _paths.size()) {
+                       break;
+               }
+               ensure_open_path (_current_path + 1);
+       }
+
+       return read;
+}
+
+int64_t
+FileGroup::length () const
+{
+       int64_t len = 0;
+       for (size_t i = 0; i < _paths.size(); ++i) {
+               len += boost::filesystem::file_size (_paths[i]);
+       }
+
+       return len;
+}
diff --git a/src/lib/file_group.h b/src/lib/file_group.h
new file mode 100644 (file)
index 0000000..65091c9
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_FILE_GROUP_H
+#define DCPOMATIC_FILE_GROUP_H
+
+#include <vector>
+#include <boost/filesystem.hpp>
+
+class FileGroup
+{
+public:
+       FileGroup ();
+       FileGroup (boost::filesystem::path);
+       FileGroup (std::vector<boost::filesystem::path> const &);
+       ~FileGroup ();
+
+       void set_paths (std::vector<boost::filesystem::path> const &);
+
+       int64_t seek (int64_t, int) const;
+       int read (uint8_t*, int) const;
+       int64_t length () const;
+
+private:
+       void ensure_open_path (size_t) const;
+
+       std::vector<boost::filesystem::path> _paths;
+       /** Index of path that we are currently reading from */
+       mutable size_t _current_path;
+       mutable FILE* _current_file;
+};
+
+#endif
index b1b86898403fdbb62c39b6fe1bc9e42eed0bd9c7..71836f2540fc1fa617241cf2a622fc1e34283527 100644 (file)
@@ -81,7 +81,7 @@ using boost::optional;
 using libdcp::Size;
 using libdcp::Signer;
 
-int const Film::state_version = 4;
+int const Film::state_version = 5;
 
 /** Construct a Film object in a given directory.
  *
@@ -372,6 +372,8 @@ Film::read_metadata ()
 
        cxml::Document f ("Metadata");
        f.read_file (file ("metadata.xml"));
+
+       int const version = f.number_child<int> ("Version");
        
        _name = f.string_child ("Name");
        _use_dci_name = f.bool_child ("UseDCIName");
@@ -403,7 +405,7 @@ Film::read_metadata ()
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
        _key = libdcp::Key (f.string_child ("Key"));
-       _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"));
+       _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), version);
 
        _dirty = false;
 }
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
new file mode 100644 (file)
index 0000000..b05fa6b
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libcxml/cxml.h>
+#include "image_content.h"
+#include "image_examiner.h"
+#include "config.h"
+#include "compose.hpp"
+#include "film.h"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::stringstream;
+using boost::shared_ptr;
+
+ImageContent::ImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
+       : Content (f)
+       , VideoContent (f)
+{
+       if (boost::filesystem::is_regular_file (p)) {
+               _paths.push_back (p);
+       } else {
+               for (boost::filesystem::directory_iterator i(p); i != boost::filesystem::directory_iterator(); ++i) {
+                       if (boost::filesystem::is_regular_file (i->path()) && valid_image_file (i->path())) {
+                               _paths.push_back (i->path ());
+                       }
+               }
+               
+               sort (_paths.begin(), _paths.end());
+       }
+}
+
+
+ImageContent::ImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int)
+       : Content (f, node)
+       , VideoContent (f, node)
+{
+       
+}
+
+string
+ImageContent::summary () const
+{
+       string s = path_summary () + " ";
+       /* Get the string() here so that the name does not have quotes around it */
+       if (still ()) {
+               s += _("[still]");
+       } else {
+               s += _("[moving images]");
+       }
+
+       return s;
+}
+
+string
+ImageContent::technical_summary () const
+{
+       string s = Content::technical_summary() + " - "
+               + VideoContent::technical_summary() + " - ";
+
+       if (still ()) {
+               s += _("still");
+       } else {
+               s += _("moving");
+       }
+
+       return s;
+}
+
+void
+ImageContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("Image");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+}
+
+void
+ImageContent::examine (shared_ptr<Job> job)
+{
+       job->sub (_("Computing digest"));
+       Content::examine (job);
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       shared_ptr<ImageExaminer> examiner (new ImageExaminer (film, shared_from_this(), job));
+
+       take_from_video_examiner (examiner);
+       set_video_length (examiner->video_length ());
+}
+
+void
+ImageContent::set_video_length (VideoContent::Frame len)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _video_length = len;
+       }
+
+       signal_changed (ContentProperty::LENGTH);
+}
+
+Time
+ImageContent::full_length () const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+       return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
+}
+
+string
+ImageContent::identifier () const
+{
+       stringstream s;
+       s << VideoContent::identifier ();
+       s << "_" << video_length();
+       return s.str ();
+}
+
+bool
+ImageContent::still () const
+{
+       return number_of_paths() == 1;
+}
diff --git a/src/lib/image_content.h b/src/lib/image_content.h
new file mode 100644 (file)
index 0000000..47c5a20
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_IMAGE_CONTENT_H
+#define DCPOMATIC_IMAGE_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+       class Node;
+}
+
+class ImageContent : public VideoContent
+{
+public:
+       ImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       ImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+       boost::shared_ptr<ImageContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<ImageContent> (Content::shared_from_this ());
+       };
+
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string technical_summary () const;
+       void as_xml (xmlpp::Node *) const;
+       Time full_length () const;
+
+       std::string identifier () const;
+       
+       void set_video_length (VideoContent::Frame);
+       bool still () const;
+};
+
+#endif
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
new file mode 100644 (file)
index 0000000..498ff2e
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/filesystem.hpp>
+#include <Magick++.h>
+#include "image_content.h"
+#include "image_decoder.h"
+#include "image.h"
+#include "film.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::cout;
+using boost::shared_ptr;
+using libdcp::Size;
+
+ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c)
+       : Decoder (f)
+       , VideoDecoder (f, c)
+       , _image_content (c)
+{
+
+}
+
+void
+ImageDecoder::pass ()
+{
+       if (_video_position >= _image_content->video_length ()) {
+               return;
+       }
+
+       if (_image && _image_content->still ()) {
+               video (_image, true, _video_position);
+               return;
+       }
+
+       Magick::Image* magick_image = new Magick::Image (_image_content->path(_video_position).string ());
+       libdcp::Size size (magick_image->columns(), magick_image->rows());
+
+       _image.reset (new Image (PIX_FMT_RGB24, size, true));
+
+       using namespace MagickCore;
+       
+       uint8_t* p = _image->data()[0];
+       for (int y = 0; y < size.height; ++y) {
+               uint8_t* q = p;
+               for (int x = 0; x < size.width; ++x) {
+                       Magick::Color c = magick_image->pixelColor (x, y);
+                       *q++ = c.redQuantum() * 255 / QuantumRange;
+                       *q++ = c.greenQuantum() * 255 / QuantumRange;
+                       *q++ = c.blueQuantum() * 255 / QuantumRange;
+               }
+               p += _image->stride()[0];
+       }
+
+       delete magick_image;
+
+       video (_image, false, _video_position);
+}
+
+void
+ImageDecoder::seek (VideoContent::Frame frame, bool)
+{
+       _video_position = frame;
+}
+
+bool
+ImageDecoder::done () const
+{
+       return _video_position >= _image_content->video_length ();
+}
diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h
new file mode 100644 (file)
index 0000000..c750024
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "video_decoder.h"
+
+namespace Magick {
+       class Image;
+}
+
+class ImageContent;
+
+class ImageDecoder : public VideoDecoder
+{
+public:
+       ImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>);
+
+       boost::shared_ptr<const ImageContent> content () {
+               return _image_content;
+       }
+
+       /* Decoder */
+
+       void pass ();
+       void seek (VideoContent::Frame, bool);
+       bool done () const;
+
+private:
+       boost::shared_ptr<const ImageContent> _image_content;
+       boost::shared_ptr<Image> _image;
+};
+
diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc
new file mode 100644 (file)
index 0000000..2d15058
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+#include <Magick++.h>
+#include "image_content.h"
+#include "image_examiner.h"
+#include "film.h"
+#include "job.h"
+#include "exceptions.h"
+#include "config.h"
+
+#include "i18n.h"
+
+using std::cout;
+using std::list;
+using std::sort;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::bad_lexical_cast;
+
+ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const ImageContent> content, shared_ptr<Job> job)
+       : _film (film)
+       , _image_content (content)
+       , _video_length (0)
+{
+       list<unsigned int> frames;
+       size_t const N = content->number_of_paths ();
+
+       for (size_t i = 0; i < N; ++i) {
+               boost::filesystem::path const p = content->path (i);
+               try {
+                       frames.push_back (lexical_cast<int> (p.stem().string()));
+               } catch (bad_lexical_cast &) {
+                       /* We couldn't turn that filename into a number; never mind */
+               }
+               
+               if (!_video_size) {
+                       using namespace MagickCore;
+                       Magick::Image* image = new Magick::Image (p.string());
+                       _video_size = libdcp::Size (image->columns(), image->rows());
+                       delete image;
+               }
+       
+               job->set_progress (float (i) / N);
+       }
+
+       frames.sort ();
+       
+       if (N > 1 && frames.front() != 0 && frames.front() != 1) {
+               throw StringError (String::compose (_("first frame in moving image directory is number %1"), frames.front ()));
+       }
+
+       if (N > 1 && frames.back() != frames.size() && frames.back() != (frames.size() - 1)) {
+               throw StringError (String::compose (_("there are %1 images in the directory but the last one is number %2"), frames.size(), frames.back ()));
+       }
+
+       if (content->still ()) {
+               _video_length = Config::instance()->default_still_length() * video_frame_rate();
+       } else {
+               _video_length = _image_content->number_of_paths ();
+       }
+}
+
+libdcp::Size
+ImageExaminer::video_size () const
+{
+       return _video_size.get ();
+}
+
+float
+ImageExaminer::video_frame_rate () const
+{
+       boost::shared_ptr<const Film> f = _film.lock ();
+       if (!f) {
+               return 24;
+       }
+
+       return f->video_frame_rate ();
+}
diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h
new file mode 100644 (file)
index 0000000..8887f0d
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "video_examiner.h"
+
+namespace Magick {
+       class Image;
+}
+
+class ImageContent;
+
+class ImageExaminer : public VideoExaminer
+{
+public:
+       ImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>, boost::shared_ptr<Job>);
+
+       float video_frame_rate () const;
+       libdcp::Size video_size () const;
+       VideoContent::Frame video_length () const {
+               return _video_length;
+       }
+
+private:
+       boost::weak_ptr<const Film> _film;
+       boost::shared_ptr<const ImageContent> _image_content;
+       boost::optional<libdcp::Size> _video_size;
+       VideoContent::Frame _video_length;
+};
diff --git a/src/lib/moving_image.h b/src/lib/moving_image.h
deleted file mode 100644 (file)
index a81403d..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-class MovingImageContent;
-
-class MovingImage
-{
-public:
-       MovingImage (boost::shared_ptr<const MovingImageContent> c)
-               : _moving_image_content (c)
-       {}
-
-       boost::shared_ptr<const MovingImageContent> content () const {
-               return _moving_image_content;
-       }
-
-protected:
-       boost::shared_ptr<const MovingImageContent> _moving_image_content;
-};
diff --git a/src/lib/moving_image_content.cc b/src/lib/moving_image_content.cc
deleted file mode 100644 (file)
index dd486b0..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <libcxml/cxml.h>
-#include "moving_image_content.h"
-#include "moving_image_examiner.h"
-#include "config.h"
-#include "compose.hpp"
-#include "film.h"
-#include "job.h"
-
-#include "i18n.h"
-
-using std::string;
-using std::cout;
-using std::list;
-using std::stringstream;
-using std::vector;
-using boost::shared_ptr;
-
-MovingImageContent::MovingImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
-       : Content (f, p)
-       , VideoContent (f, p)
-{
-
-}
-
-MovingImageContent::MovingImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
-       : Content (f, node)
-       , VideoContent (f, node)
-{
-       list<cxml::NodePtr> c = node->node_children ("File");
-       for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
-               _files.push_back ((*i)->content ());
-       }
-}
-
-string
-MovingImageContent::summary () const
-{
-       /* Get the string() here so that the name does not have quotes around it */
-       return String::compose (_("%1 [moving images]"), path().filename().string());
-}
-
-string
-MovingImageContent::technical_summary () const
-{
-       return Content::technical_summary() + " - "
-               + VideoContent::technical_summary() + " - "
-               + "moving";
-}
-
-void
-MovingImageContent::as_xml (xmlpp::Node* node) const
-{
-       node->add_child("Type")->add_child_text ("MovingImage");
-       Content::as_xml (node);
-       VideoContent::as_xml (node);
-
-       for (vector<boost::filesystem::path>::const_iterator i = _files.begin(); i != _files.end(); ++i) {
-               node->add_child("File")->add_child_text (i->filename().string());
-       }
-}
-
-void
-MovingImageContent::examine (shared_ptr<Job> job)
-{
-       job->sub (_("Computing digest"));
-       Content::examine (job);
-
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-
-       job->sub (_("Examining content"));
-       shared_ptr<MovingImageExaminer> examiner (new MovingImageExaminer (film, shared_from_this(), job));
-
-       take_from_video_examiner (examiner);
-
-       _video_length = examiner->files().size ();
-       _files = examiner->files ();
-}
-
-Time
-MovingImageContent::full_length () const
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
-       return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
-}
-
-string
-MovingImageContent::identifier () const
-{
-       stringstream s;
-       s << VideoContent::identifier ();
-       s << "_" << video_length();
-       return s.str ();
-}
diff --git a/src/lib/moving_image_content.h b/src/lib/moving_image_content.h
deleted file mode 100644 (file)
index 1a64750..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DCPOMATIC_MOVING_IMAGE_CONTENT_H
-#define DCPOMATIC_MOVING_IMAGE_CONTENT_H
-
-#include <boost/enable_shared_from_this.hpp>
-#include "video_content.h"
-
-namespace cxml {
-       class Node;
-}
-
-/** A directory of image files which are to be presented as a movie */
-class MovingImageContent : public VideoContent
-{
-public:
-       MovingImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
-       MovingImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
-
-       boost::shared_ptr<MovingImageContent> shared_from_this () {
-               return boost::dynamic_pointer_cast<MovingImageContent> (Content::shared_from_this ());
-       };
-       
-       void examine (boost::shared_ptr<Job>);
-       std::string summary () const;
-       std::string technical_summary () const;
-       void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
-
-       std::string identifier () const;
-
-       std::vector<boost::filesystem::path> const & files () const {
-               return _files;
-       }
-       
-private:
-       std::vector<boost::filesystem::path> _files;
-};
-
-#endif
diff --git a/src/lib/moving_image_decoder.cc b/src/lib/moving_image_decoder.cc
deleted file mode 100644 (file)
index 0960960..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <Magick++.h>
-#include "moving_image_content.h"
-#include "moving_image_decoder.h"
-#include "image.h"
-#include "film.h"
-#include "exceptions.h"
-
-#include "i18n.h"
-
-using std::cout;
-using boost::shared_ptr;
-using libdcp::Size;
-
-MovingImageDecoder::MovingImageDecoder (shared_ptr<const Film> f, shared_ptr<const MovingImageContent> c)
-       : Decoder (f)
-       , VideoDecoder (f, c)
-       , MovingImage (c)
-{
-
-}
-
-void
-MovingImageDecoder::pass ()
-{
-       if (_video_position >= _moving_image_content->video_length ()) {
-               return;
-       }
-
-       boost::filesystem::path path = _moving_image_content->path ();
-       path /= _moving_image_content->files()[_video_position];
-
-       Magick::Image* magick_image = new Magick::Image (path.string());
-       libdcp::Size size (magick_image->columns(), magick_image->rows());
-
-       shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, false));
-
-       using namespace MagickCore;
-       
-       uint8_t* p = image->data()[0];
-       for (int y = 0; y < size.height; ++y) {
-               for (int x = 0; x < size.width; ++x) {
-                       Magick::Color c = magick_image->pixelColor (x, y);
-                       *p++ = c.redQuantum() * 255 / QuantumRange;
-                       *p++ = c.greenQuantum() * 255 / QuantumRange;
-                       *p++ = c.blueQuantum() * 255 / QuantumRange;
-               }
-       }
-
-       delete magick_image;
-
-       video (image, false, _video_position);
-}
-
-void
-MovingImageDecoder::seek (VideoContent::Frame frame, bool)
-{
-       _video_position = frame;
-}
-
-bool
-MovingImageDecoder::done () const
-{
-       return _video_position >= _moving_image_content->video_length ();
-}
diff --git a/src/lib/moving_image_decoder.h b/src/lib/moving_image_decoder.h
deleted file mode 100644 (file)
index 5cc8b32..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "video_decoder.h"
-#include "moving_image.h"
-
-namespace Magick {
-       class Image;
-}
-
-class MovingImageContent;
-
-class MovingImageDecoder : public VideoDecoder, public MovingImage
-{
-public:
-       MovingImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>);
-
-       /* Decoder */
-
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
-};
-
diff --git a/src/lib/moving_image_examiner.cc b/src/lib/moving_image_examiner.cc
deleted file mode 100644 (file)
index 8724683..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <boost/lexical_cast.hpp>
-#include <Magick++.h>
-#include "moving_image_content.h"
-#include "moving_image_examiner.h"
-#include "film.h"
-#include "job.h"
-#include "exceptions.h"
-
-#include "i18n.h"
-
-using std::cout;
-using std::list;
-using std::sort;
-using boost::shared_ptr;
-using boost::lexical_cast;
-
-MovingImageExaminer::MovingImageExaminer (shared_ptr<const Film> film, shared_ptr<const MovingImageContent> content, shared_ptr<Job> job)
-       : MovingImage (content)
-       , _film (film)
-       , _video_length (0)
-{
-       list<unsigned int> frames;
-       unsigned int files = 0;
-       
-       for (boost::filesystem::directory_iterator i(content->path()); i != boost::filesystem::directory_iterator(); ++i) {
-               if (boost::filesystem::is_regular_file (i->path ())) {
-                       ++files;
-               }
-       }
-
-       int j = 0;
-       for (boost::filesystem::directory_iterator i(content->path()); i != boost::filesystem::directory_iterator(); ++i) {
-               if (!boost::filesystem::is_regular_file (i->path ())) {
-                       continue;
-               }
-
-               if (valid_image_file (i->path ())) {
-                       int n = lexical_cast<int> (i->path().stem().string());
-                       frames.push_back (n);
-                       _files.push_back (i->path().filename ());
-
-                       if (!_video_size) {
-                               using namespace MagickCore;
-                               Magick::Image* image = new Magick::Image (i->path().string());
-                               _video_size = libdcp::Size (image->columns(), image->rows());
-                               delete image;
-                       }
-               }
-
-               job->set_progress (float (j) / files);
-               ++j;
-       }
-
-       frames.sort ();
-       sort (_files.begin(), _files.end ());
-       
-       if (frames.size() < 2) {
-               throw StringError (String::compose (_("only %1 file(s) found in moving image directory"), frames.size ()));
-       }
-
-       if (frames.front() != 0 && frames.front() != 1) {
-               throw StringError (String::compose (_("first frame in moving image directory is number %1"), frames.front ()));
-       }
-
-       if (frames.back() != frames.size() && frames.back() != (frames.size() - 1)) {
-               throw StringError (String::compose (_("there are %1 images in the directory but the last one is number %2"), frames.size(), frames.back ()));
-       }
-
-       _video_length = frames.size ();
-}
-
-libdcp::Size
-MovingImageExaminer::video_size () const
-{
-       return _video_size.get ();
-}
-
-int
-MovingImageExaminer::video_length () const
-{
-       return _video_length;
-}
-
-float
-MovingImageExaminer::video_frame_rate () const
-{
-       return 24;
-}
-
diff --git a/src/lib/moving_image_examiner.h b/src/lib/moving_image_examiner.h
deleted file mode 100644 (file)
index db6845e..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "moving_image.h"
-#include "video_examiner.h"
-
-namespace Magick {
-       class Image;
-}
-
-class MovingImageContent;
-
-class MovingImageExaminer : public MovingImage, public VideoExaminer
-{
-public:
-       MovingImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>, boost::shared_ptr<Job>);
-
-       float video_frame_rate () const;
-       libdcp::Size video_size () const;
-       VideoContent::Frame video_length () const;
-
-       std::vector<boost::filesystem::path> const & files () const {
-               return _files;
-       }
-
-private:
-       boost::weak_ptr<const Film> _film;
-       boost::optional<libdcp::Size> _video_size;
-       VideoContent::Frame _video_length;
-       std::vector<boost::filesystem::path> _files;
-};
index b5a96163182b458acf00a58661be273251ceab60..87b10a3989a2021be3e04c0971f14f7e7fe166fa 100644 (file)
 #include "film.h"
 #include "ffmpeg_decoder.h"
 #include "ffmpeg_content.h"
-#include "still_image_decoder.h"
-#include "still_image_content.h"
-#include "moving_image_decoder.h"
-#include "moving_image_content.h"
+#include "image_decoder.h"
+#include "image_content.h"
 #include "sndfile_decoder.h"
 #include "sndfile_content.h"
 #include "subtitle_content.h"
@@ -466,36 +464,24 @@ Player::setup_pieces ()
                        piece->decoder = fd;
                }
                
-               shared_ptr<const StillImageContent> ic = dynamic_pointer_cast<const StillImageContent> (*i);
+               shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
                if (ic) {
-                       shared_ptr<StillImageDecoder> id;
+                       bool reusing = false;
                        
-                       /* See if we can re-use an old StillImageDecoder */
+                       /* See if we can re-use an old ImageDecoder */
                        for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
-                               shared_ptr<StillImageDecoder> imd = dynamic_pointer_cast<StillImageDecoder> ((*j)->decoder);
+                               shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
                                if (imd && imd->content() == ic) {
-                                       id = imd;
+                                       piece = *j;
+                                       reusing = true;
                                }
                        }
 
-                       if (!id) {
-                               id.reset (new StillImageDecoder (_film, ic));
+                       if (!reusing) {
+                               shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
                                id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
+                               piece->decoder = id;
                        }
-
-                       piece->decoder = id;
-               }
-
-               shared_ptr<const MovingImageContent> mc = dynamic_pointer_cast<const MovingImageContent> (*i);
-               if (mc) {
-                       shared_ptr<MovingImageDecoder> md;
-
-                       if (!md) {
-                               md.reset (new MovingImageDecoder (_film, mc));
-                               md->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                       }
-
-                       piece->decoder = md;
                }
 
                shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
index 8f40c9e6d7eed254ca6af288b70fff091d6c0aaa..37b2902189f2b32d61a94a57bbc0baa36c7f0a28 100644 (file)
@@ -26,8 +26,7 @@
 #include "video_content.h"
 #include "ffmpeg_decoder.h"
 #include "ffmpeg_content.h"
-#include "still_image_decoder.h"
-#include "still_image_content.h"
+#include "image_decoder.h"
 #include "content_factory.h"
 #include "job.h"
 #include "config.h"
@@ -114,11 +113,11 @@ Playlist::video_identifier () const
 
 /** @param node <Playlist> node */
 void
-Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node)
+Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
 {
        list<cxml::NodePtr> c = node->node_children ("Content");
        for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
-               _content.push_back (content_factory (film, *i));
+               _content.push_back (content_factory (film, *i, version));
        }
 
        sort (_content.begin(), _content.end(), ContentSorter ());
index a1ae9b151bfad4647fc6b9e4cd67158bbe0f8f53..f87b3397b4b68e388f3f5e0f37b512c385c4bcf5 100644 (file)
@@ -56,7 +56,7 @@ public:
        ~Playlist ();
 
        void as_xml (xmlpp::Node *);
-       void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       void set_from_xml (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
 
        void add (boost::shared_ptr<Content>);
        void remove (boost::shared_ptr<Content>);
index 31c1b4698da19ce3d83cf4ae0b6d052b61ea984f..c7879202f471c6ac5a2f2f978215b35cffc39300 100644 (file)
@@ -43,7 +43,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::pat
 
 }
 
-SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
+SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int)
        : Content (f, node)
        , AudioContent (f, node)
        , _audio_mapping (node->node_child ("AudioMapping"))
@@ -57,7 +57,7 @@ string
 SndfileContent::summary () const
 {
        /* Get the string() here so that the name does not have quotes around it */
-       return String::compose (_("%1 [audio]"), path().filename().string());
+       return String::compose (_("%1 [audio]"), path_summary ());
 }
 
 string
index 191d625277307ea933fa13f1877ffa7494eab965..701ff16b24bd0dbbdf2d75e5708e2be3c7ce9e45 100644 (file)
@@ -33,7 +33,7 @@ class SndfileContent : public AudioContent
 {
 public:
        SndfileContent (boost::shared_ptr<const Film>, boost::filesystem::path);
-       SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       SndfileContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
 
        boost::shared_ptr<SndfileContent> shared_from_this () {
                return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ());
index 09ccf4fbc1fd9ba5d2bcf3eac9d3aa2ae135b45b..e10f4f568430d08dd86d1d5db38d2116606fbbbb 100644 (file)
@@ -40,7 +40,7 @@ SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const Sndfi
        , _deinterleave_buffer (0)
 {
        _info.format = 0;
-       _sndfile = sf_open (_sndfile_content->path().string().c_str(), SFM_READ, &_info);
+       _sndfile = sf_open (_sndfile_content->path(0).string().c_str(), SFM_READ, &_info);
        if (!_sndfile) {
                throw DecodeError (_("could not open audio file for reading"));
        }
diff --git a/src/lib/still_image.h b/src/lib/still_image.h
deleted file mode 100644 (file)
index 366d693..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DCPOMATIC_STILL_IMAGE_H
-#define DCPOMATIC_STILL_IMAGE_H
-
-class StillImageContent;
-
-class StillImage
-{
-public:
-       StillImage (boost::shared_ptr<const StillImageContent> c)
-               : _still_image_content (c)
-       {}
-
-       boost::shared_ptr<const StillImageContent> content () const {
-               return _still_image_content;
-       }
-
-protected:
-       boost::shared_ptr<const StillImageContent> _still_image_content;
-};
-
-#endif
diff --git a/src/lib/still_image_content.cc b/src/lib/still_image_content.cc
deleted file mode 100644 (file)
index 0cf80b5..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <libcxml/cxml.h>
-#include "still_image_content.h"
-#include "still_image_examiner.h"
-#include "config.h"
-#include "compose.hpp"
-#include "film.h"
-
-#include "i18n.h"
-
-using std::string;
-using std::cout;
-using std::stringstream;
-using boost::shared_ptr;
-
-StillImageContent::StillImageContent (shared_ptr<const Film> f, boost::filesystem::path p)
-       : Content (f, p)
-       , VideoContent (f, p)
-{
-
-}
-
-StillImageContent::StillImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
-       : Content (f, node)
-       , VideoContent (f, node)
-{
-       
-}
-
-string
-StillImageContent::summary () const
-{
-       /* Get the string() here so that the name does not have quotes around it */
-       return String::compose (_("%1 [still]"), path().filename().string());
-}
-
-string
-StillImageContent::technical_summary () const
-{
-       return Content::technical_summary() + " - "
-               + VideoContent::technical_summary() + " - "
-               + "still";
-}
-
-void
-StillImageContent::as_xml (xmlpp::Node* node) const
-{
-       node->add_child("Type")->add_child_text ("StillImage");
-       Content::as_xml (node);
-       VideoContent::as_xml (node);
-}
-
-void
-StillImageContent::examine (shared_ptr<Job> job)
-{
-       Content::examine (job);
-
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       
-       shared_ptr<StillImageExaminer> examiner (new StillImageExaminer (film, shared_from_this()));
-
-       take_from_video_examiner (examiner);
-       set_video_length (Config::instance()->default_still_length() * video_frame_rate());
-}
-
-void
-StillImageContent::set_video_length (VideoContent::Frame len)
-{
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               _video_length = len;
-       }
-
-       signal_changed (ContentProperty::LENGTH);
-}
-
-Time
-StillImageContent::full_length () const
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
-       return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
-}
-
-string
-StillImageContent::identifier () const
-{
-       stringstream s;
-       s << VideoContent::identifier ();
-       s << "_" << video_length();
-       return s.str ();
-}
diff --git a/src/lib/still_image_content.h b/src/lib/still_image_content.h
deleted file mode 100644 (file)
index ccd7fbc..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DCPOMATIC_STILL_IMAGE_CONTENT_H
-#define DCPOMATIC_STILL_IMAGE_CONTENT_H
-
-#include <boost/enable_shared_from_this.hpp>
-#include "video_content.h"
-
-namespace cxml {
-       class Node;
-}
-
-/** A single image which is to be held on screen for some time (i.e. a slide) */
-class StillImageContent : public VideoContent
-{
-public:
-       StillImageContent (boost::shared_ptr<const Film>, boost::filesystem::path);
-       StillImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
-
-       boost::shared_ptr<StillImageContent> shared_from_this () {
-               return boost::dynamic_pointer_cast<StillImageContent> (Content::shared_from_this ());
-       };
-
-       void examine (boost::shared_ptr<Job>);
-       std::string summary () const;
-       std::string technical_summary () const;
-       void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
-
-       std::string identifier () const;
-       
-       void set_video_length (VideoContent::Frame);
-};
-
-#endif
diff --git a/src/lib/still_image_decoder.cc b/src/lib/still_image_decoder.cc
deleted file mode 100644 (file)
index 6e82f9a..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <Magick++.h>
-#include "still_image_content.h"
-#include "still_image_decoder.h"
-#include "image.h"
-#include "film.h"
-#include "exceptions.h"
-
-#include "i18n.h"
-
-using std::cout;
-using boost::shared_ptr;
-using libdcp::Size;
-
-StillImageDecoder::StillImageDecoder (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
-       : Decoder (f)
-       , VideoDecoder (f, c)
-       , StillImage (c)
-{
-
-}
-
-void
-StillImageDecoder::pass ()
-{
-       if (_video_position >= _still_image_content->video_length ()) {
-               return;
-       }
-
-       if (_image) {
-               video (_image, true, _video_position);
-               return;
-       }
-
-       Magick::Image* magick_image = new Magick::Image (_still_image_content->path().string ());
-       _video_size = libdcp::Size (magick_image->columns(), magick_image->rows());
-       
-       _image.reset (new Image (PIX_FMT_RGB24, _video_size.get(), true));
-
-       using namespace MagickCore;
-       
-       uint8_t* p = _image->data()[0];
-       for (int y = 0; y < _video_size->height; ++y) {
-               uint8_t* q = p;
-               for (int x = 0; x < _video_size->width; ++x) {
-                       Magick::Color c = magick_image->pixelColor (x, y);
-                       *q++ = c.redQuantum() * 255 / QuantumRange;
-                       *q++ = c.greenQuantum() * 255 / QuantumRange;
-                       *q++ = c.blueQuantum() * 255 / QuantumRange;
-               }
-               p += _image->stride()[0];
-       }
-
-       delete magick_image;
-
-       video (_image, false, _video_position);
-}
-
-void
-StillImageDecoder::seek (VideoContent::Frame frame, bool)
-{
-       _video_position = frame;
-}
-
-bool
-StillImageDecoder::done () const
-{
-       return _video_position >= _still_image_content->video_length ();
-}
diff --git a/src/lib/still_image_decoder.h b/src/lib/still_image_decoder.h
deleted file mode 100644 (file)
index db41b03..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-    Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "video_decoder.h"
-#include "still_image.h"
-
-namespace Magick {
-       class Image;
-}
-
-class StillImageContent;
-
-class StillImageDecoder : public VideoDecoder, public StillImage
-{
-public:
-       StillImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>);
-
-       /* Decoder */
-
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
-
-private:
-       boost::shared_ptr<Image> _image;
-       mutable boost::optional<libdcp::Size> _video_size;
-};
-
diff --git a/src/lib/still_image_examiner.cc b/src/lib/still_image_examiner.cc
deleted file mode 100644 (file)
index d5e8636..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <iostream>
-#include <Magick++.h>
-#include "still_image_content.h"
-#include "still_image_examiner.h"
-#include "film.h"
-
-#include "i18n.h"
-
-using std::cout;
-using std::string;
-using boost::shared_ptr;
-
-StillImageExaminer::StillImageExaminer (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c)
-       : StillImage (c)
-       , _film (f)
-{
-       using namespace MagickCore;
-       Magick::Image* image = new Magick::Image (_still_image_content->path().string ());
-       _video_size = libdcp::Size (image->columns(), image->rows());
-       delete image;
-}
-
-libdcp::Size
-StillImageExaminer::video_size () const
-{
-       return _video_size;
-}
-
-int
-StillImageExaminer::video_length () const
-{
-       return _still_image_content->video_length ();
-}
-
-float
-StillImageExaminer::video_frame_rate () const
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return 24;
-       }
-
-       return f->video_frame_rate ();
-}
-
diff --git a/src/lib/still_image_examiner.h b/src/lib/still_image_examiner.h
deleted file mode 100644 (file)
index fa3456e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program 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.
-
-    This program 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 this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "still_image.h"
-#include "video_examiner.h"
-
-namespace Magick {
-       class Image;
-}
-
-class StillImageContent;
-
-class StillImageExaminer : public StillImage, public VideoExaminer
-{
-public:
-       StillImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>);
-
-       float video_frame_rate () const;
-       libdcp::Size video_size () const;
-       VideoContent::Frame video_length () const;
-
-private:
-       boost::weak_ptr<const Film> _film;
-       libdcp::Size _video_size;
-};
index 1194e2dc950b99531185544b162ea5b2eae9b907..6c0d3af86a6687571d74e3bb576add9494c67567 100644 (file)
 #include <libcxml/cxml.h>
 #include "subtitle_content.h"
 #include "util.h"
+#include "exceptions.h"
+
+#include "i18n.h"
 
 using std::string;
+using std::vector;
 using boost::shared_ptr;
 using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
 
 int const SubtitleContentProperty::SUBTITLE_OFFSET = 500;
 int const SubtitleContentProperty::SUBTITLE_SCALE = 501;
@@ -47,6 +52,28 @@ SubtitleContent::SubtitleContent (shared_ptr<const Film> f, shared_ptr<const cxm
        _subtitle_scale = node->number_child<float> ("SubtitleScale");
 }
 
+SubtitleContent::SubtitleContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
+       : Content (f, c)
+{
+       shared_ptr<SubtitleContent> ref = dynamic_pointer_cast<SubtitleContent> (c[0]);
+       assert (ref);
+       
+       for (size_t i = 0; i < c.size(); ++i) {
+               shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (c[i]);
+
+               if (sc->subtitle_offset() != ref->subtitle_offset()) {
+                       throw JoinError (_("Content to be joined must have the same subtitle offset."));
+               }
+
+               if (sc->subtitle_scale() != ref->subtitle_scale()) {
+                       throw JoinError (_("Content to be joined must have the same subtitle scale."));
+               }
+       }
+
+       _subtitle_offset = ref->subtitle_offset ();
+       _subtitle_scale = ref->subtitle_scale ();
+}
+
 void
 SubtitleContent::as_xml (xmlpp::Node* root) const
 {
index c29485feeb6796bf1bbe942c0949da96d1fe4501..854647d181dfc065f11ed191b051d5c939965683 100644 (file)
@@ -34,6 +34,7 @@ class SubtitleContent : public virtual Content
 public:
        SubtitleContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        SubtitleContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       SubtitleContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
        
        void as_xml (xmlpp::Node *) const;
 
index 9dffffa9865b033cb190a5ec1ddffcb2a65a56ad..ddc0a297459cef504b49300b25f874752ec2ba64 100644 (file)
@@ -92,6 +92,7 @@ using std::istream;
 using std::numeric_limits;
 using std::pair;
 using std::cout;
+using std::streampos;
 using boost::shared_ptr;
 using boost::thread;
 using boost::lexical_cast;
@@ -405,46 +406,9 @@ md5_digest (void const * data, int size)
        return s.str ();
 }
 
-/** @param file File name.
- *  @return MD5 digest of file's contents.
- */
-string
-md5_digest (boost::filesystem::path file)
-{
-       FILE* f = fopen_boost (file, "rb");
-       if (!f) {
-               throw OpenFileError (file.string());
-       }
-
-       boost::uintmax_t bytes = boost::filesystem::file_size (file);
-
-       boost::uintmax_t const buffer_size = 64 * 1024;
-       char buffer[buffer_size];
-
-       MD5_CTX md5_context;
-       MD5_Init (&md5_context);
-       while (bytes > 0) {
-               int const t = min (bytes, buffer_size);
-               fread (buffer, 1, t, f);
-               MD5_Update (&md5_context, buffer, t);
-               bytes -= t;
-       }
-
-       unsigned char digest[MD5_DIGEST_LENGTH];
-       MD5_Final (digest, &md5_context);
-       fclose (f);
-
-       stringstream s;
-       for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
-               s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
-       }
-
-       return s.str ();
-}
-
 /** @param job Optional job for which to report progress */
 string
-md5_digest_directory (boost::filesystem::path directory, shared_ptr<Job> job)
+md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job)
 {
        boost::uintmax_t const buffer_size = 64 * 1024;
        char buffer[buffer_size];
@@ -452,32 +416,29 @@ md5_digest_directory (boost::filesystem::path directory, shared_ptr<Job> job)
        MD5_CTX md5_context;
        MD5_Init (&md5_context);
 
-       int files = 0;
-       if (job) {
-               for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
-                       ++files;
-               }
+       vector<int64_t> sizes;
+       for (size_t i = 0; i < files.size(); ++i) {
+               sizes.push_back (boost::filesystem::file_size (files[i]));
        }
 
-       int j = 0;
-       for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
-               FILE* f = fopen_boost (i->path(), "rb");
+       for (size_t i = 0; i < files.size(); ++i) {
+               FILE* f = fopen_boost (files[i], "rb");
                if (!f) {
-                       throw OpenFileError (i->path().string());
+                       throw OpenFileError (files[i].string());
                }
 
-               boost::uintmax_t bytes = boost::filesystem::file_size (i->path ());
+               boost::uintmax_t const bytes = boost::filesystem::file_size (files[i]);
+               boost::uintmax_t remaining = bytes;
 
-               while (bytes > 0) {
-                       int const t = min (bytes, buffer_size);
+               while (remaining > 0) {
+                       int const t = min (remaining, buffer_size);
                        fread (buffer, 1, t, f);
                        MD5_Update (&md5_context, buffer, t);
-                       bytes -= t;
-               }
+                       remaining -= t;
 
-               if (job) {
-                       job->set_progress (float (j) / files);
-                       ++j;
+                       if (job) {
+                               job->set_progress ((float (i) + 1 - float(remaining) / bytes) / files.size ());
+                       }
                }
 
                fclose (f);
index 5e568cc27934cb84828cea9a0a25743520dea1b9..7dcd920b7cac3d72974332eda264466faf0cc42a 100644 (file)
@@ -67,8 +67,7 @@ extern double seconds (struct timeval);
 extern void dcpomatic_setup ();
 extern void dcpomatic_setup_gettext_i18n (std::string);
 extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
-extern std::string md5_digest (boost::filesystem::path);
-extern std::string md5_digest_directory (boost::filesystem::path, boost::shared_ptr<Job>);
+extern std::string md5_digest (std::vector<boost::filesystem::path>, boost::shared_ptr<Job>);
 extern std::string md5_digest (void const *, int);
 extern void ensure_ui_thread ();
 extern std::string audio_channel_name (int);
index d0eab4dbf93f870344b7c34228ed14d7813ad26d..0a19ffd692463ac4f2923cd106ea54ddfccd4de1 100644 (file)
@@ -28,6 +28,7 @@
 #include "colour_conversion.h"
 #include "util.h"
 #include "film.h"
+#include "exceptions.h"
 
 #include "i18n.h"
 
@@ -42,9 +43,21 @@ using std::string;
 using std::stringstream;
 using std::setprecision;
 using std::cout;
+using std::vector;
 using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::optional;
+using boost::dynamic_pointer_cast;
+
+VideoContent::VideoContent (shared_ptr<const Film> f)
+       : Content (f)
+       , _video_length (0)
+       , _video_frame_rate (0)
+       , _video_frame_type (VIDEO_FRAME_TYPE_2D)
+       , _ratio (Ratio::from_id ("185"))
+{
+       setup_default_colour_conversion ();
+}
 
 VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
        : Content (f, s)
@@ -86,6 +99,51 @@ VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod
        _colour_conversion = ColourConversion (node->node_child ("ColourConversion"));
 }
 
+VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
+       : Content (f, c)
+       , _video_length (0)
+{
+       shared_ptr<VideoContent> ref = dynamic_pointer_cast<VideoContent> (c[0]);
+       assert (ref);
+
+       for (size_t i = 0; i < c.size(); ++i) {
+               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c[i]);
+
+               if (vc->video_size() != ref->video_size()) {
+                       throw JoinError (_("Content to be joined must have the same picture size."));
+               }
+
+               if (vc->video_frame_rate() != ref->video_frame_rate()) {
+                       throw JoinError (_("Content to be joined must have the same video frame rate."));
+               }
+
+               if (vc->video_frame_type() != ref->video_frame_type()) {
+                       throw JoinError (_("Content to be joined must have the same video frame type."));
+               }
+
+               if (vc->crop() != ref->crop()) {
+                       throw JoinError (_("Content to be joined must have the same crop."));
+               }
+
+               if (vc->ratio() != ref->ratio()) {
+                       throw JoinError (_("Content to be joined must have the same ratio."));
+               }
+
+               if (vc->colour_conversion() != ref->colour_conversion()) {
+                       throw JoinError (_("Content to be joined must have the same colour conversion."));
+               }
+
+               _video_length += vc->video_length ();
+       }
+
+       _video_size = ref->video_size ();
+       _video_frame_rate = ref->video_frame_rate ();
+       _video_frame_type = ref->video_frame_type ();
+       _crop = ref->crop ();
+       _ratio = ref->ratio ();
+       _colour_conversion = ref->colour_conversion ();
+}
+
 void
 VideoContent::as_xml (xmlpp::Node* node) const
 {
index 106adf959cf4ff43f3cd039acd15dd493a633533..effca5c61c314c50c4328d02adb5db7ce92dd48e 100644 (file)
@@ -42,9 +42,11 @@ class VideoContent : public virtual Content
 public:
        typedef int Frame;
 
+       VideoContent (boost::shared_ptr<const Film>);
        VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
+       VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
 
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
index 5098d406927d895513af5f5c5ce513ae95d383e5..1699c5ec8c6b5c1b9f782a3cf718dfcbdca8049e 100644 (file)
@@ -22,6 +22,7 @@ sources = """
           encoder.cc
           examine_content_job.cc
           exceptions.cc
+          file_group.cc
           filter_graph.cc
           ffmpeg.cc
           ffmpeg_content.cc
@@ -30,13 +31,13 @@ sources = """
           film.cc
           filter.cc
           image.cc
+          image_content.cc
+          image_decoder.cc
+          image_examiner.cc
           job.cc
           job_manager.cc
           kdm.cc
           log.cc
-          moving_image_content.cc
-          moving_image_decoder.cc
-          moving_image_examiner.cc
           player.cc
           playlist.cc
           ratio.cc
@@ -48,9 +49,6 @@ sources = """
           sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
-          still_image_content.cc
-          still_image_decoder.cc
-          still_image_examiner.cc
           subtitle_content.cc
           subtitle_decoder.cc
           timer.cc
index 2a0cb9cd36935a1ae53d67fafd60e8d92578b7c6..b4ec1d77da9d754db678c75e191e93af9ddba35a 100644 (file)
@@ -245,6 +245,7 @@ public:
        Frame (wxString const & title)
                : wxFrame (NULL, -1, title)
                , _servers_list_dialog (0)
+               , _hints_dialog (0)
        {
 #ifdef DCPOMATIC_WINDOWS_CONSOLE               
                 AllocConsole();
index b7c244c9c51341d8c3625004e4f9ac6df45b1d89..78f3deab655b36eca6a956fc72cfb5b553b807e3 100644 (file)
@@ -95,7 +95,7 @@ AudioDialog::set_content (shared_ptr<AudioContent> c)
 
        _content_changed_connection = _content->Changed.connect (bind (&AudioDialog::content_changed, this, _2));
 
-       SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->path().filename().string()).data()));
+       SetTitle (wxString::Format (_("DCP-o-matic audio - %s"), std_to_wx(_content->path_summary()).data()));
 }
 
 void
index 6183e344470fc496871746c80a8aa83d84891808..254109eb3c882083c52193abf816d8b04d8c871b 100644 (file)
 #include <wx/dirdlg.h>
 #include "lib/playlist.h"
 #include "lib/film.h"
-#include "lib/moving_image_content.h"
+#include "lib/image_content.h"
 #include "lib/content_factory.h"
 #include "lib/examine_content_job.h"
 #include "lib/job_manager.h"
+#include "lib/exceptions.h"
 #include "content_menu.h"
 #include "repeat_dialog.h"
 #include "wx_util.h"
 
 using std::cout;
+using std::vector;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 
 enum {
        ID_repeat = 1,
+       ID_join,
        ID_find_missing,
        ID_remove
 };
@@ -46,11 +49,13 @@ ContentMenu::ContentMenu (shared_ptr<Film> f, wxWindow* p)
        , _parent (p)
 {
        _repeat = _menu->Append (ID_repeat, _("Repeat..."));
+       _join = _menu->Append (ID_join, _("Join"));
        _find_missing = _menu->Append (ID_find_missing, _("Find missing..."));
        _menu->AppendSeparator ();
        _remove = _menu->Append (ID_remove, _("Remove"));
 
        _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::repeat, this), ID_repeat);
+       _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::join, this), ID_join);
        _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::find_missing, this), ID_find_missing);
        _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::remove, this), ID_remove);
 }
@@ -65,6 +70,16 @@ ContentMenu::popup (ContentList c, wxPoint p)
 {
        _content = c;
        _repeat->Enable (!_content.empty ());
+
+       int n = 0;
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               if (dynamic_pointer_cast<FFmpegContent> (*i)) {
+                       ++n;
+               }
+       }
+       
+       _join->Enable (n > 1);
+       
        _find_missing->Enable (_content.size() == 1 && !_content.front()->path_valid ());
        _remove->Enable (!_content.empty ());
        _parent->PopupMenu (_menu, p);
@@ -94,6 +109,35 @@ ContentMenu::repeat ()
        _content.clear ();
 }
 
+void
+ContentMenu::join ()
+{
+       vector<shared_ptr<Content> > fc;
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<FFmpegContent> f = dynamic_pointer_cast<FFmpegContent> (*i);
+               if (f) {
+                       fc.push_back (f);
+               }
+       }
+
+       assert (fc.size() > 1);
+
+       shared_ptr<Film> film = _film.lock ();
+       if (!film) {
+               return;
+       }
+
+       try {
+               shared_ptr<FFmpegContent> joined (new FFmpegContent (film, fc));
+               for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+                       film->remove_content (*i);
+               }
+               film->add_content (joined);
+       } catch (JoinError& e) {
+               error_dialog (_parent, std_to_wx (e.what ()));
+       }
+}
+
 void
 ContentMenu::remove ()
 {
@@ -126,11 +170,12 @@ ContentMenu::find_missing ()
        shared_ptr<Content> content;
 
        /* XXX: a bit nasty */
-       if (dynamic_pointer_cast<MovingImageContent> (_content.front ())) {
+       shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (_content.front ());
+       if (ic && !ic->still ()) {
                wxDirDialog* d = new wxDirDialog (0, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
                int const r = d->ShowModal ();
                if (r == wxID_OK) {
-                       content.reset (new MovingImageContent (film, boost::filesystem::path (wx_to_std (d->GetPath ()))));
+                       content.reset (new ImageContent (film, boost::filesystem::path (wx_to_std (d->GetPath ()))));
                }
                d->Destroy ();
        } else {
@@ -179,5 +224,5 @@ ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_pt
                return;
        }
 
-       old_content->set_path (new_content->path ());
+       old_content->set_path (new_content->path (0));
 }
index 2a672532001ca0ddaebcec1f41c04d4d03a599b8..9f4bd4cc4378059d116fc8d071660826ca6e0ac2 100644 (file)
@@ -37,6 +37,7 @@ public:
 
 private:
        void repeat ();
+       void join ();
        void find_missing ();
        void remove ();
        void maybe_found_missing (boost::weak_ptr<Job>, boost::weak_ptr<Content>, boost::weak_ptr<Content>);
@@ -46,6 +47,7 @@ private:
        wxWindow* _parent;
        ContentList _content;
        wxMenuItem* _repeat;
+       wxMenuItem* _join;
        wxMenuItem* _find_missing;
        wxMenuItem* _remove;
 };
index 92fb9d0f37839df840f4035c979e6c178aefafeb..bf13620e53bd31b62d099a7f6769e050504e05e6 100644 (file)
@@ -36,8 +36,7 @@
 #include "lib/filter.h"
 #include "lib/ratio.h"
 #include "lib/config.h"
-#include "lib/still_image_content.h"
-#include "lib/moving_image_content.h"
+#include "lib/image_content.h"
 #include "lib/ffmpeg_content.h"
 #include "lib/sndfile_content.h"
 #include "lib/dcp_content_type.h"
@@ -766,8 +765,8 @@ FilmEditor::content_add_folder_clicked ()
        }
 
        _film->examine_and_add_content (
-               shared_ptr<MovingImageContent> (
-                       new MovingImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ())))
+               shared_ptr<ImageContent> (
+                       new ImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ())))
                        )
                );
 }
@@ -826,7 +825,9 @@ FilmEditor::selected_content ()
                        break;
                }
 
-               sel.push_back (_film->content()[s]);
+               if (s < int (_film->content().size ())) {
+                       sel.push_back (_film->content()[s]);
+               }
        }
 
        return sel;
index 03e8419a091f16b5fa95c7085af4f376824ff5a9..0e1fe3ea0ab87baf7453e175c6c30ad204efe0b2 100644 (file)
@@ -35,8 +35,6 @@
 #include "lib/filter.h"
 #include "lib/player.h"
 #include "lib/video_content.h"
-#include "lib/ffmpeg_content.h"
-#include "lib/still_image_content.h"
 #include "lib/video_decoder.h"
 #include "film_viewer.h"
 #include "wx_util.h"
index df22500d2ca6b6a1f8eca51913f1202a8000656a..668d713216e8ad49b1a40a8a9acd41b0ab7e62fd 100644 (file)
@@ -80,14 +80,14 @@ HintsDialog::film_changed ()
        ContentList content = film->content ();
        int vob = 0;
        for (ContentList::const_iterator i = content.begin(); i != content.end(); ++i) {
-               if (boost::algorithm::starts_with ((*i)->path().filename().string(), "VTS_")) {
+               if (boost::algorithm::starts_with ((*i)->path(0).filename().string(), "VTS_")) {
                        ++vob;
                }
        }
 
        if (vob > 1) {
                hint = true;
-               _text->WriteText (wxString::Format (_("You have %d files that look like they are VOB files from DVD.  You should coalesce them to ensure smooth joins between the files."), vob));
+               _text->WriteText (wxString::Format (_("You have %d files that look like they are VOB files from DVD. You should join them to ensure smooth joins between the files."), vob));
                _text->Newline ();
        }
 
index e04d9ee744e94f83f4cf5354cfc6e150f65d5910..0ac9a1d4bb6d513e6fa3bedbfe96eeae66441777 100644 (file)
@@ -162,7 +162,7 @@ private:
                gc->StrokePath (path);
                gc->FillPath (path);
 
-               wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (cont->path().filename().string()).data(), type().data());
+               wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (cont->path_summary()).data(), type().data());
                wxDouble name_width;
                wxDouble name_height;
                wxDouble name_descent;
index 57eb96af0e5e68bb70c03f33211b1ebc4da32ff1..b5a0c43d4981ce28bc094a9bec220475d4c727d9 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include "lib/content.h"
-#include "lib/still_image_content.h"
+#include "lib/image_content.h"
 #include "timing_panel.h"
 #include "wx_util.h"
 #include "timecode.h"
@@ -88,7 +88,8 @@ TimingPanel::film_content_changed (int property)
                }
        }       
 
-       _length->set_editable (dynamic_pointer_cast<StillImageContent> (content));
+       shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (content);
+       _length->set_editable (ic && ic->still ());
 }
 
 void
@@ -105,8 +106,8 @@ TimingPanel::length_changed ()
 {
        ContentList c = _editor->selected_content ();
        if (c.size() == 1) {
-               shared_ptr<StillImageContent> ic = dynamic_pointer_cast<StillImageContent> (c.front ());
-               if (ic) {
+               shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
+               if (ic && ic->still ()) {
                        ic->set_video_length (rint (_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
                }
        }
index 2c239e767fcf99bc4856224e5cedb42895b1b332..9e8aa381bbd0cb0832006fe2a7d6108b71699bac 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <boost/test/unit_test.hpp>
-#include "lib/still_image_content.h"
+#include "lib/image_content.h"
 #include "lib/dcp_content_type.h"
 #include "lib/film.h"
 #include "lib/ratio.h"
@@ -37,9 +37,9 @@ BOOST_AUTO_TEST_CASE (black_fill_test)
        film->set_name ("black_fill_test");
        film->set_container (Ratio::from_id ("185"));
        film->set_sequence_video (false);
-       shared_ptr<StillImageContent> contentA (new StillImageContent (film, "test/data/simple_testcard_640x480.png"));
+       shared_ptr<ImageContent> contentA (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
        contentA->set_ratio (Ratio::from_id ("185"));
-       shared_ptr<StillImageContent> contentB (new StillImageContent (film, "test/data/simple_testcard_640x480.png"));
+       shared_ptr<ImageContent> contentB (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
        contentB->set_ratio (Ratio::from_id ("185"));
 
        film->examine_and_add_content (contentA);
diff --git a/test/file_group_test.cc b/test/file_group_test.cc
new file mode 100644 (file)
index 0000000..14c01a9
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program 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.
+
+    This program 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 this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include <cstdio>
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include "lib/file_group.h"
+
+using std::vector;
+
+BOOST_AUTO_TEST_CASE (file_group_test)
+{
+       /* Random data; must be big enough for all the files */
+       uint8_t data[65536];
+       for (int i = 0; i < 65536; ++i) {
+               data[i] = rand() & 0xff;
+       }
+       
+       int const num_files = 4;
+
+       int length[] = {
+               99,
+               18941,
+               33110,
+               42
+       };
+
+       int total_length = 0;
+       for (int i = 0; i < num_files; ++i) {
+               total_length += length[i];
+       }
+
+       vector<boost::filesystem::path> name;
+       boost::filesystem::create_directories ("build/test/file_group_test");
+       name.push_back ("build/test/file_group_test/A");
+       name.push_back ("build/test/file_group_test/B");
+       name.push_back ("build/test/file_group_test/C");
+       name.push_back ("build/test/file_group_test/D");
+
+       int base = 0;
+       for (int i = 0; i < num_files; ++i) {
+               FILE* f = fopen (name[i].string().c_str(), "wb");
+               fwrite (data + base, 1, length[i], f);
+               fclose (f);
+               base += length[i];
+       }
+
+       FileGroup fg (name);
+       uint8_t test[65536];
+
+       int pos = 0;
+
+       /* Basic read from 0 */
+       BOOST_CHECK_EQUAL (fg.read (test, 64), 64);
+       BOOST_CHECK_EQUAL (memcmp (data, test, 64), 0);
+       pos += 64;
+
+       /* Another read following the previous */
+       BOOST_CHECK_EQUAL (fg.read (test, 4), 4);
+       BOOST_CHECK_EQUAL (memcmp (data + pos, test, 4), 0);
+       pos += 4;
+
+       /* Read overlapping A and B */
+       BOOST_CHECK_EQUAL (fg.read (test, 128), 128);
+       BOOST_CHECK_EQUAL (memcmp (data + pos, test, 128), 0);
+       pos += 128;
+
+       /* Read overlapping B/C/D and over-reading */
+       BOOST_CHECK_EQUAL (fg.read (test, total_length * 3), total_length - pos);
+       BOOST_CHECK_EQUAL (memcmp (data + pos, test, total_length - pos), 0);
+
+       /* Bad seek */
+       BOOST_CHECK_EQUAL (fg.seek (total_length * 2, SEEK_SET), -1);
+
+       /* SEEK_SET */
+       BOOST_CHECK_EQUAL (fg.seek (999, SEEK_SET), 999);
+       BOOST_CHECK_EQUAL (fg.read (test, 64), 64);
+       BOOST_CHECK_EQUAL (memcmp (data + 999, test, 64), 0);
+
+       /* SEEK_CUR */
+       BOOST_CHECK_EQUAL (fg.seek (42, SEEK_CUR), 999 + 64 + 42);
+       BOOST_CHECK_EQUAL (fg.read (test, 64), 64);
+       BOOST_CHECK_EQUAL (memcmp (data + 999 + 64 + 42, test, 64), 0);
+
+       /* SEEK_END */
+       BOOST_CHECK_EQUAL (fg.seek (1077, SEEK_END), total_length - 1077);
+       BOOST_CHECK_EQUAL (fg.read (test, 256), 256);
+       BOOST_CHECK_EQUAL (memcmp (data + total_length - 1077, test, 256), 0);
+}
index 8d00be72a33cee4df2cb482a62c092e1f1b4b8ca..c936fe8d45fa4175e8c774d1782238e35a28b6bf 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <boost/test/unit_test.hpp>
-#include "lib/still_image_content.h"
+#include "lib/image_content.h"
 #include "lib/ratio.h"
 #include "lib/film.h"
 #include "lib/dcp_content_type.h"
@@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE (scaling_test)
        shared_ptr<Film> film = new_test_film ("scaling_test");
        film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
        film->set_name ("scaling_test");
-       shared_ptr<StillImageContent> imc (new StillImageContent (film, "test/data/simple_testcard_640x480.png"));
+       shared_ptr<ImageContent> imc (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
 
        film->examine_and_add_content (imc);
 
index b56f133c7df1af8efb817a8231c0b78728833eb1..86bcc5a6939889bcfec87387c682256ad23f4cb6 100644 (file)
@@ -21,6 +21,7 @@
 #include <libxml++/libxml++.h>
 #include <libcxml/cxml.h>
 #include "lib/ffmpeg_content.h"
+#include "lib/film.h"
 
 using std::pair;
 using std::list;
@@ -61,7 +62,7 @@ BOOST_AUTO_TEST_CASE (stream_test)
                map->add_child("DCP")->add_child_text ("2");
        }
                
-       FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)));
+       FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)), Film::state_version);
 
        BOOST_CHECK_EQUAL (a.id, 4);
        BOOST_CHECK_EQUAL (a.frame_rate, 44100);
index a5e671f238d9e4e9ba856bd9c015215b9620c9db..4dccb49c6c1d95302fb031794b90d1ba97217357 100644 (file)
@@ -23,6 +23,7 @@
 
 using std::string;
 using std::vector;
+using boost::shared_ptr;
 
 BOOST_AUTO_TEST_CASE (util_test)
 {
@@ -43,8 +44,12 @@ BOOST_AUTO_TEST_CASE (util_test)
 
 BOOST_AUTO_TEST_CASE (md5_digest_test)
 {
-       string const t = md5_digest ("test/data/md5.test");
+       vector<boost::filesystem::path> p;
+       p.push_back ("test/data/md5.test");
+       string const t = md5_digest (p, shared_ptr<Job> ());
        BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0");
 
-       BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
+       p.clear ();
+       p.push_back ("foobar");
+       BOOST_CHECK_THROW (md5_digest (p, shared_ptr<Job> ()), std::runtime_error);
 }
index 76de63a523098f613d80770bfe0e67efd69e4285..4ae49b087a05b32c3dfac078a4dbfaaddcb4896b 100644 (file)
@@ -15,32 +15,33 @@ def build(bld):
     obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
     obj.use    = 'libdcpomatic'
     obj.source = """
-                 test.cc
+                 4k_test.cc
                  audio_analysis_test.cc
-                 scaling_test.cc
-                 film_metadata_test.cc
-                 frame_rate_test.cc
-                 colour_conversion_test.cc
                  audio_delay_test.cc
-                 silence_padding_test.cc
                  audio_merger_test.cc
-                 resampler_test.cc
+                 black_fill_test.cc
+                 client_server_test.cc
+                 colour_conversion_test.cc
                  ffmpeg_audio_test.cc
-                 threed_test.cc
-                 play_test.cc
-                 ffmpeg_pts_offset.cc
+                 ffmpeg_dcp_test.cc
                  ffmpeg_examiner_test.cc
-                 black_fill_test.cc
-                 ratio_test.cc
-                 pixel_formats_test.cc
+                 ffmpeg_pts_offset.cc
+                 file_group_test.cc
+                 film_metadata_test.cc
+                 frame_rate_test.cc
+                 image_test.cc
+                 job_test.cc
                  make_black_test.cc
+                 pixel_formats_test.cc
+                 play_test.cc
+                 ratio_test.cc
+                 resampler_test.cc
+                 scaling_test.cc
+                 silence_padding_test.cc
                  stream_test.cc
+                 test.cc
+                 threed_test.cc
                  util_test.cc
-                 ffmpeg_dcp_test.cc
-                 job_test.cc
-                 client_server_test.cc
-                 image_test.cc
-                 4k_test.cc
                  """
 
     obj.target = 'unit-tests'