diff options
Diffstat (limited to 'src/lib')
44 files changed, 915 insertions, 816 deletions
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index e0eaacb91..04823d1e6 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -22,8 +22,12 @@ #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 { diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 73919105d..b100d7aba 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -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; diff --git a/src/lib/content.cc b/src/lib/content.cc index a41261998..f09012765 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -24,10 +24,17 @@ #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; +} diff --git a/src/lib/content.h b/src/lib/content.h index 626e270bd..9cf6d866a 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -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; diff --git a/src/lib/content_factory.cc b/src/lib/content_factory.cc index ed9a9e769..bab22b8eb 100644 --- a/src/lib/content_factory.cc +++ b/src/lib/content_factory.cc @@ -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 { diff --git a/src/lib/content_factory.h b/src/lib/content_factory.h index d2f75051a..071d925e0 100644 --- a/src/lib/content_factory.h +++ b/src/lib/content_factory.h @@ -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); diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index b04d973dc..f4631c09b 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -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. diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index dbafba0f0..e85a2c44e 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -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); } diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h index d5f4db291..760918437 100644 --- a/src/lib/ffmpeg.h +++ b/src/lib/ffmpeg.h @@ -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 (); }; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index a6f9e6ac8..9533315a5 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -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 @@ -300,16 +332,50 @@ operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b) } 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 diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 775cb9220..7ff159b85 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -26,15 +26,44 @@ #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; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index be3247537..5a1b78762 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -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) { diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index 4d10aabbb..78b6e3121 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -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)); } } } @@ -141,6 +143,36 @@ FFmpegExaminer::video_length () const } 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 { stringstream n; @@ -160,11 +192,5 @@ FFmpegExaminer::stream_name (AVStream* s) const } } - if (!n.str().empty()) { - n << "; "; - } - - n << s->codec->channels << " channels"; - return n.str (); } diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index 4912d899a..4de475d2a 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -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 index 000000000..9c2065141 --- /dev/null +++ b/src/lib/file_group.cc @@ -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/moving_image_examiner.h b/src/lib/file_group.h index db6845ee5..65091c936 100644 --- a/src/lib/moving_image_examiner.h +++ b/src/lib/file_group.h @@ -17,31 +17,33 @@ */ -#include "moving_image.h" -#include "video_examiner.h" +#ifndef DCPOMATIC_FILE_GROUP_H +#define DCPOMATIC_FILE_GROUP_H -namespace Magick { - class Image; -} +#include <vector> +#include <boost/filesystem.hpp> -class MovingImageContent; - -class MovingImageExaminer : public MovingImage, public VideoExaminer +class FileGroup { public: - MovingImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const MovingImageContent>, boost::shared_ptr<Job>); + FileGroup (); + FileGroup (boost::filesystem::path); + FileGroup (std::vector<boost::filesystem::path> const &); + ~FileGroup (); - float video_frame_rate () const; - libdcp::Size video_size () const; - VideoContent::Frame video_length () const; + void set_paths (std::vector<boost::filesystem::path> const &); - std::vector<boost::filesystem::path> const & files () const { - return _files; - } + int64_t seek (int64_t, int) const; + int read (uint8_t*, int) const; + int64_t length () const; private: - boost::weak_ptr<const Film> _film; - boost::optional<libdcp::Size> _video_size; - VideoContent::Frame _video_length; - std::vector<boost::filesystem::path> _files; + 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 diff --git a/src/lib/film.cc b/src/lib/film.cc index b1b868984..71836f254 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -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/still_image_content.cc b/src/lib/image_content.cc index 0cf80b546..b05fa6b8d 100644 --- a/src/lib/still_image_content.cc +++ b/src/lib/image_content.cc @@ -18,11 +18,12 @@ */ #include <libcxml/cxml.h> -#include "still_image_content.h" -#include "still_image_examiner.h" +#include "image_content.h" +#include "image_examiner.h" #include "config.h" #include "compose.hpp" #include "film.h" +#include "job.h" #include "i18n.h" @@ -31,14 +32,25 @@ 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) +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()); + } } -StillImageContent::StillImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node) + +ImageContent::ImageContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int) : Content (f, node) , VideoContent (f, node) { @@ -46,44 +58,59 @@ StillImageContent::StillImageContent (shared_ptr<const Film> f, shared_ptr<const } string -StillImageContent::summary () const +ImageContent::summary () const { + string s = path_summary () + " "; /* Get the string() here so that the name does not have quotes around it */ - return String::compose (_("%1 [still]"), path().filename().string()); + if (still ()) { + s += _("[still]"); + } else { + s += _("[moving images]"); + } + + return s; } string -StillImageContent::technical_summary () const +ImageContent::technical_summary () const { - return Content::technical_summary() + " - " - + VideoContent::technical_summary() + " - " - + "still"; + string s = Content::technical_summary() + " - " + + VideoContent::technical_summary() + " - "; + + if (still ()) { + s += _("still"); + } else { + s += _("moving"); + } + + return s; } void -StillImageContent::as_xml (xmlpp::Node* node) const +ImageContent::as_xml (xmlpp::Node* node) const { - node->add_child("Type")->add_child_text ("StillImage"); + node->add_child("Type")->add_child_text ("Image"); Content::as_xml (node); VideoContent::as_xml (node); } void -StillImageContent::examine (shared_ptr<Job> job) +ImageContent::examine (shared_ptr<Job> job) { + job->sub (_("Computing digest")); Content::examine (job); shared_ptr<const Film> film = _film.lock (); assert (film); - shared_ptr<StillImageExaminer> examiner (new StillImageExaminer (film, shared_from_this())); + shared_ptr<ImageExaminer> examiner (new ImageExaminer (film, shared_from_this(), job)); take_from_video_examiner (examiner); - set_video_length (Config::instance()->default_still_length() * video_frame_rate()); + set_video_length (examiner->video_length ()); } void -StillImageContent::set_video_length (VideoContent::Frame len) +ImageContent::set_video_length (VideoContent::Frame len) { { boost::mutex::scoped_lock lm (_mutex); @@ -94,7 +121,7 @@ StillImageContent::set_video_length (VideoContent::Frame len) } Time -StillImageContent::full_length () const +ImageContent::full_length () const { shared_ptr<const Film> film = _film.lock (); assert (film); @@ -104,10 +131,16 @@ StillImageContent::full_length () const } string -StillImageContent::identifier () const +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/still_image_content.h b/src/lib/image_content.h index ccd7fbc03..47c5a20e3 100644 --- a/src/lib/still_image_content.h +++ b/src/lib/image_content.h @@ -17,8 +17,8 @@ */ -#ifndef DCPOMATIC_STILL_IMAGE_CONTENT_H -#define DCPOMATIC_STILL_IMAGE_CONTENT_H +#ifndef DCPOMATIC_IMAGE_CONTENT_H +#define DCPOMATIC_IMAGE_CONTENT_H #include <boost/enable_shared_from_this.hpp> #include "video_content.h" @@ -27,15 +27,14 @@ 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 +class ImageContent : public VideoContent { public: - StillImageContent (boost::shared_ptr<const Film>, boost::filesystem::path); - StillImageContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>); + 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<StillImageContent> shared_from_this () { - return boost::dynamic_pointer_cast<StillImageContent> (Content::shared_from_this ()); + boost::shared_ptr<ImageContent> shared_from_this () { + return boost::dynamic_pointer_cast<ImageContent> (Content::shared_from_this ()); }; void examine (boost::shared_ptr<Job>); @@ -47,6 +46,7 @@ public: std::string identifier () const; void set_video_length (VideoContent::Frame); + bool still () const; }; #endif diff --git a/src/lib/still_image_decoder.cc b/src/lib/image_decoder.cc index 6e82f9a55..498ff2e25 100644 --- a/src/lib/still_image_decoder.cc +++ b/src/lib/image_decoder.cc @@ -20,8 +20,8 @@ #include <iostream> #include <boost/filesystem.hpp> #include <Magick++.h> -#include "still_image_content.h" -#include "still_image_decoder.h" +#include "image_content.h" +#include "image_decoder.h" #include "image.h" #include "film.h" #include "exceptions.h" @@ -32,37 +32,37 @@ using std::cout; using boost::shared_ptr; using libdcp::Size; -StillImageDecoder::StillImageDecoder (shared_ptr<const Film> f, shared_ptr<const StillImageContent> c) +ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c) : Decoder (f) , VideoDecoder (f, c) - , StillImage (c) + , _image_content (c) { } void -StillImageDecoder::pass () +ImageDecoder::pass () { - if (_video_position >= _still_image_content->video_length ()) { + if (_video_position >= _image_content->video_length ()) { return; } - if (_image) { + if (_image && _image_content->still ()) { 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)); + 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 < _video_size->height; ++y) { + for (int y = 0; y < size.height; ++y) { uint8_t* q = p; - for (int x = 0; x < _video_size->width; ++x) { + 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; @@ -77,13 +77,13 @@ StillImageDecoder::pass () } void -StillImageDecoder::seek (VideoContent::Frame frame, bool) +ImageDecoder::seek (VideoContent::Frame frame, bool) { _video_position = frame; } bool -StillImageDecoder::done () const +ImageDecoder::done () const { - return _video_position >= _still_image_content->video_length (); + return _video_position >= _image_content->video_length (); } diff --git a/src/lib/still_image_decoder.h b/src/lib/image_decoder.h index db41b0357..c7500243e 100644 --- a/src/lib/still_image_decoder.h +++ b/src/lib/image_decoder.h @@ -18,18 +18,21 @@ */ #include "video_decoder.h" -#include "still_image.h" namespace Magick { class Image; } -class StillImageContent; +class ImageContent; -class StillImageDecoder : public VideoDecoder, public StillImage +class ImageDecoder : public VideoDecoder { public: - StillImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>); + ImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>); + + boost::shared_ptr<const ImageContent> content () { + return _image_content; + } /* Decoder */ @@ -38,7 +41,7 @@ public: bool done () const; private: + boost::shared_ptr<const ImageContent> _image_content; boost::shared_ptr<Image> _image; - mutable boost::optional<libdcp::Size> _video_size; }; diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc new file mode 100644 index 000000000..2d150583a --- /dev/null +++ b/src/lib/image_examiner.cc @@ -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/still_image_examiner.h b/src/lib/image_examiner.h index fa3456e8a..8887f0d3d 100644 --- a/src/lib/still_image_examiner.h +++ b/src/lib/image_examiner.h @@ -17,25 +17,28 @@ */ -#include "still_image.h" #include "video_examiner.h" namespace Magick { class Image; } -class StillImageContent; +class ImageContent; -class StillImageExaminer : public StillImage, public VideoExaminer +class ImageExaminer : public VideoExaminer { public: - StillImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const StillImageContent>); + 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; + VideoContent::Frame video_length () const { + return _video_length; + } private: boost::weak_ptr<const Film> _film; - libdcp::Size _video_size; + 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 index a81403dbd..000000000 --- a/src/lib/moving_image.h +++ /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 index dd486b0a7..000000000 --- a/src/lib/moving_image_content.cc +++ /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 index 1a64750fe..000000000 --- a/src/lib/moving_image_content.h +++ /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 index 096096063..000000000 --- a/src/lib/moving_image_decoder.cc +++ /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 index 5cc8b32b9..000000000 --- a/src/lib/moving_image_decoder.h +++ /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 index 87246832d..000000000 --- a/src/lib/moving_image_examiner.cc +++ /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/player.cc b/src/lib/player.cc index b5a961631..87b10a398 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -22,10 +22,8 @@ #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); diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index 8f40c9e6d..37b290218 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -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 ()); diff --git a/src/lib/playlist.h b/src/lib/playlist.h index a1ae9b151..f87b3397b 100644 --- a/src/lib/playlist.h +++ b/src/lib/playlist.h @@ -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>); diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index 31c1b4698..c7879202f 100644 --- a/src/lib/sndfile_content.cc +++ b/src/lib/sndfile_content.cc @@ -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 diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index 191d62527..701ff16b2 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -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 ()); diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index 09ccf4fbc..e10f4f568 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -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 index 366d69331..000000000 --- a/src/lib/still_image.h +++ /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_examiner.cc b/src/lib/still_image_examiner.cc deleted file mode 100644 index d5e863634..000000000 --- a/src/lib/still_image_examiner.cc +++ /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/subtitle_content.cc b/src/lib/subtitle_content.cc index 1194e2dc9..6c0d3af86 100644 --- a/src/lib/subtitle_content.cc +++ b/src/lib/subtitle_content.cc @@ -20,10 +20,15 @@ #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 { diff --git a/src/lib/subtitle_content.h b/src/lib/subtitle_content.h index c29485fee..854647d18 100644 --- a/src/lib/subtitle_content.h +++ b/src/lib/subtitle_content.h @@ -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; diff --git a/src/lib/util.cc b/src/lib/util.cc index 9dffffa98..ddc0a2974 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -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); diff --git a/src/lib/util.h b/src/lib/util.h index 5e568cc27..7dcd920b7 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -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); diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index d0eab4dbf..0a19ffd69 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -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 { diff --git a/src/lib/video_content.h b/src/lib/video_content.h index 106adf959..effca5c61 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -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; diff --git a/src/lib/wscript b/src/lib/wscript index 5098d4069..1699c5ec8 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -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 |
