#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;
_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
{
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;
#include "util.h"
#include "content_factory.h"
#include "ui_signaller.h"
+#include "exceptions.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;
: _film (f)
, _change_signals_frequent (false)
{
- _paths.push_back (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."));
+ }
+ }
+}
+
void
Content::as_xml (xmlpp::Node* node) const
{
boost::mutex::scoped_lock lm (_mutex);
-
- node->add_child("Path")->add_child_text (_paths.front().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));
Content::examine (shared_ptr<Job> job)
{
boost::mutex::scoped_lock lm (_mutex);
- boost::filesystem::path p = _paths.front ();
+ 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;
bool
Content::path_valid () const
{
- return boost::filesystem::exists (_paths.front ());
+ 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 (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>);
/** 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.
#include "filter.h"
#include "film.h"
#include "log.h"
+#include "exceptions.h"
#include "i18n.h"
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;
_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 (*(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
{
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;
}
+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"))
{
};
extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
+extern bool operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
class FFmpegSubtitleStream
{
};
extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
+extern bool operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
class FFmpegContentProperty : public VideoContentProperty
{
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>, std::vector<boost::shared_ptr<Content> >);
boost::shared_ptr<FFmpegContent> shared_from_this () {
return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
boost::mutex::scoped_lock lm (_mutex);
return _first_video;
}
-
+
private:
friend class ffmpeg_pts_offset_test;
_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), i, 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), i)));
}
}
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
{
}
}
- if (!n.str().empty()) {
- n << "; ";
- }
-
- n << s->codec->channels << " channels";
-
return n.str ();
}
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;
#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;
_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
{
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;
return s.str ();
}
-/** @param file File name.
- * @return MD5 digest of file's contents.
- */
-string
-md5_digest (boost::filesystem::path file)
-{
- ifstream f (file.string().c_str(), std::ios::binary);
- if (!f.good ()) {
- throw OpenFileError (file.string());
- }
-
- f.seekg (0, std::ios::end);
- int bytes = f.tellg ();
- f.seekg (0, std::ios::beg);
-
- int 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);
- f.read (buffer, t);
- MD5_Update (&md5_context, buffer, t);
- bytes -= t;
- }
-
- unsigned char digest[MD5_DIGEST_LENGTH];
- MD5_Final (digest, &md5_context);
-
- 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)
{
int const buffer_size = 64 * 1024;
char buffer[buffer_size];
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;
- }
- }
-
- int j = 0;
- for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
- ifstream f (i->path().string().c_str(), std::ios::binary);
+ for (size_t i = 0; i < files.size(); ++i) {
+ ifstream f (files[i].string().c_str(), std::ios::binary);
if (!f.good ()) {
- throw OpenFileError (i->path().string());
+ throw OpenFileError (files[i].string());
}
f.seekg (0, std::ios::end);
}
if (job) {
- job->set_progress (float (j) / files);
- ++j;
+ job->set_progress (float (i) / files.size ());
}
}
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);
#include "colour_conversion.h"
#include "util.h"
#include "film.h"
+#include "exceptions.h"
#include "i18n.h"
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)
_colour_conversion = ColourConversion (node->node_child ("ColourConversion"));
}
+VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
+ : Content (f, c)
+{
+ 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_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
{
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;
#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
};
, _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);
}
{
_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);
_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 ()
{
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>);
wxWindow* _parent;
ContentList _content;
wxMenuItem* _repeat;
+ wxMenuItem* _join;
wxMenuItem* _find_missing;
wxMenuItem* _remove;
};
using std::string;
using std::vector;
+using boost::shared_ptr;
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> ()), OpenFileError);
}