summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog18
-rwxr-xr-xrun/dvdomatic4
-rw-r--r--src/lib/ab_transcode_job.cc7
-rw-r--r--src/lib/ab_transcode_job.h12
-rw-r--r--src/lib/ab_transcoder.cc25
-rw-r--r--src/lib/ab_transcoder.h10
-rw-r--r--src/lib/audio_decoder.cc2
-rw-r--r--src/lib/audio_decoder.h2
-rw-r--r--src/lib/check_hashes_job.cc15
-rw-r--r--src/lib/check_hashes_job.h13
-rw-r--r--src/lib/dcp_video_frame.cc10
-rw-r--r--src/lib/dcp_video_frame.h5
-rw-r--r--src/lib/decoder.cc10
-rw-r--r--src/lib/decoder.h19
-rw-r--r--src/lib/decoder_factory.cc11
-rw-r--r--src/lib/decoder_factory.h23
-rw-r--r--src/lib/encoder.cc12
-rw-r--r--src/lib/encoder.h6
-rw-r--r--src/lib/encoder_factory.cc2
-rw-r--r--src/lib/encoder_factory.h4
-rw-r--r--src/lib/examine_content_job.cc97
-rw-r--r--src/lib/external_audio_decoder.cc2
-rw-r--r--src/lib/external_audio_decoder.h2
-rw-r--r--src/lib/ffmpeg_decoder.cc159
-rw-r--r--src/lib/ffmpeg_decoder.h13
-rw-r--r--src/lib/film.cc211
-rw-r--r--src/lib/film.h28
-rw-r--r--src/lib/filter_graph.cc9
-rw-r--r--src/lib/filter_graph.h2
-rw-r--r--src/lib/image.cc252
-rw-r--r--src/lib/image.h55
-rw-r--r--src/lib/imagemagick_decoder.cc33
-rw-r--r--src/lib/imagemagick_decoder.h11
-rw-r--r--src/lib/imagemagick_encoder.cc91
-rw-r--r--src/lib/imagemagick_encoder.h42
-rw-r--r--src/lib/j2k_still_encoder.cc35
-rw-r--r--src/lib/j2k_still_encoder.h5
-rw-r--r--src/lib/j2k_wav_encoder.cc2
-rw-r--r--src/lib/j2k_wav_encoder.h2
-rw-r--r--src/lib/job.h1
-rw-r--r--src/lib/make_dcp_job.cc2
-rw-r--r--src/lib/make_dcp_job.h6
-rw-r--r--src/lib/matcher.cc2
-rw-r--r--src/lib/options.h52
-rw-r--r--src/lib/server.cc4
-rw-r--r--src/lib/subtitle.cc2
-rw-r--r--src/lib/transcode_job.cc15
-rw-r--r--src/lib/transcode_job.h7
-rw-r--r--src/lib/transcoder.cc26
-rw-r--r--src/lib/transcoder.h18
-rw-r--r--src/lib/util.cc3
-rw-r--r--src/lib/video_decoder.cc16
-rw-r--r--src/lib/video_decoder.h13
-rw-r--r--src/lib/wscript1
-rw-r--r--src/tools/dvdomatic.cc1
-rw-r--r--src/tools/servomatictest.cc1
-rw-r--r--src/wx/film_editor.cc37
-rw-r--r--src/wx/film_editor.h2
-rw-r--r--src/wx/film_viewer.cc472
-rw-r--r--src/wx/film_viewer.h44
-rw-r--r--test/metadata.ref1
-rw-r--r--test/test.cc106
-rw-r--r--wscript2
63 files changed, 1175 insertions, 920 deletions
diff --git a/ChangeLog b/ChangeLog
index 6524aa3ed..b540313a3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2012-12-18 Carl Hetherington <cth@carlh.net>
+
+ * Alter film viewer so that it is much quicker, responds instantly
+ to changes in video filtering settings, and can (roughly) play the
+ source material back.
+
+ * Make the examination of content for length optional, so that
+ if a source file has an accurate header you can trust it.
+
2012-12-13 Carl Hetherington <cth@carlh.net>
* Version 0.64 released.
@@ -42,6 +51,15 @@
2012-12-10 Carl Hetherington <cth@carlh.net>
+ * Add a check-box (which defaults to on) which tells DVD-o-matic
+ not to scan new content files to work out their length, but instead
+ to trust the length from the header. This length only matters for
+ working out what thumbnails to generate, so it isn't critical.
+ Trusting the header will speed up the "Examine Content" job by
+ a factor of about 2, which is handy for large films.
+
+2012-12-10 Carl Hetherington <cth@carlh.net>
+
* Version 0.59 released.
2012-12-09 Carl Hetherington <cth@carlh.net>
diff --git a/run/dvdomatic b/run/dvdomatic
index 70906a254..ff3897064 100755
--- a/run/dvdomatic
+++ b/run/dvdomatic
@@ -3,10 +3,10 @@
export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
if [ "$1" == "--debug" ]; then
shift
- gdb --args build/src/tools/dvdomatic $*
+ gdb --args build/src/tools/dvdomatic "$*"
elif [ "$1" == "--valgrind" ]; then
shift
valgrind --tool="memcheck" build/src/tools/dvdomatic $*
else
- build/src/tools/dvdomatic $*
+ build/src/tools/dvdomatic "$*"
fi
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
index c9fd5bc97..b9538ce2e 100644
--- a/src/lib/ab_transcode_job.cc
+++ b/src/lib/ab_transcode_job.cc
@@ -33,9 +33,10 @@ using boost::shared_ptr;
/** @param f Film to compare.
* @param o Options.
*/
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req)
+ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
: Job (f, req)
- , _opt (o)
+ , _decode_opt (od)
+ , _encode_opt (oe)
{
_film_b.reset (new Film (*_film));
_film_b->set_scaler (Config::instance()->reference_scaler ());
@@ -53,7 +54,7 @@ ABTranscodeJob::run ()
{
try {
/* _film_b is the one with reference filters */
- ABTranscoder w (_film_b, _film, _opt, this, encoder_factory (_film, _opt));
+ ABTranscoder w (_film_b, _film, _decode_opt, this, encoder_factory (_film, _encode_opt));
w.go ();
set_progress (1);
set_state (FINISHED_OK);
diff --git a/src/lib/ab_transcode_job.h b/src/lib/ab_transcode_job.h
index 8331edf76..86a2a81b8 100644
--- a/src/lib/ab_transcode_job.h
+++ b/src/lib/ab_transcode_job.h
@@ -25,6 +25,8 @@
#include "job.h"
class Film;
+class DecodeOptions;
+class EncodeOptions;
/** @class ABTranscodeJob
* @brief Job to run a transcoder which produces output for A/B comparison of various settings.
@@ -36,13 +38,19 @@ class Film;
class ABTranscodeJob : public Job
{
public:
- ABTranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, boost::shared_ptr<Job> req);
+ ABTranscodeJob (
+ boost::shared_ptr<Film> f,
+ boost::shared_ptr<const DecodeOptions> od,
+ boost::shared_ptr<const EncodeOptions> oe,
+ boost::shared_ptr<Job> req
+ );
std::string name () const;
void run ();
private:
- boost::shared_ptr<const Options> _opt;
+ boost::shared_ptr<const DecodeOptions> _decode_opt;
+ boost::shared_ptr<const EncodeOptions> _encode_opt;
/** Copy of our Film using the reference filters and scaler */
boost::shared_ptr<Film> _film_b;
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc
index 537cb4dd7..d85f078a5 100644
--- a/src/lib/ab_transcoder.cc
+++ b/src/lib/ab_transcoder.cc
@@ -43,16 +43,15 @@ using boost::shared_ptr;
/** @param a Film to use for the left half of the screen.
* @param b Film to use for the right half of the screen.
- * @param o Options.
+ * @param o Decoder options.
* @param j Job that we are associated with.
* @param e Encoder to use.
*/
ABTranscoder::ABTranscoder (
- shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e)
+ shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
: _film_a (a)
, _film_b (b)
- , _opt (o)
, _job (j)
, _encoder (e)
{
@@ -67,12 +66,12 @@ ABTranscoder::ABTranscoder (
}
/* Set up the decoder to use the film's set streams */
- _da.first->set_subtitle_stream (_film_a->subtitle_stream ());
- _db.first->set_subtitle_stream (_film_a->subtitle_stream ());
- _da.second->set_audio_stream (_film_a->audio_stream ());
+ _da.video->set_subtitle_stream (_film_a->subtitle_stream ());
+ _db.video->set_subtitle_stream (_film_a->subtitle_stream ());
+ _da.audio->set_audio_stream (_film_a->audio_stream ());
- _da.first->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2));
- _db.first->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2));
+ _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2));
+ _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2));
if (_matcher) {
_combiner->connect_video (_matcher);
@@ -82,7 +81,7 @@ ABTranscoder::ABTranscoder (
}
if (_matcher && _delay_line) {
- _da.second->connect_audio (_delay_line);
+ _da.audio->connect_audio (_delay_line);
_delay_line->connect_audio (_matcher);
_matcher->connect_audio (_gain);
_gain->connect_audio (_encoder);
@@ -95,11 +94,11 @@ ABTranscoder::go ()
_encoder->process_begin ();
while (1) {
- bool const va = _da.first->pass ();
- bool const vb = _db.first->pass ();
- bool const a = _da.first->pass ();
+ bool const va = _da.video->pass ();
+ bool const vb = _db.video->pass ();
+ bool const a = _da.audio->pass ();
- _da.first->set_progress ();
+ _da.video->set_progress ();
if (va && vb && a) {
break;
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
index 9b57e4f73..7bfcb393c 100644
--- a/src/lib/ab_transcoder.h
+++ b/src/lib/ab_transcoder.h
@@ -25,12 +25,13 @@
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include "util.h"
+#include "decoder_factory.h"
class Job;
class Encoder;
class VideoDecoder;
class AudioDecoder;
-class Options;
+class DecodeOptions;
class Image;
class Log;
class Subtitle;
@@ -50,7 +51,7 @@ public:
ABTranscoder (
boost::shared_ptr<Film> a,
boost::shared_ptr<Film> b,
- boost::shared_ptr<const Options> o,
+ boost::shared_ptr<const DecodeOptions> o,
Job* j,
boost::shared_ptr<Encoder> e
);
@@ -60,11 +61,10 @@ public:
private:
boost::shared_ptr<Film> _film_a;
boost::shared_ptr<Film> _film_b;
- boost::shared_ptr<const Options> _opt;
Job* _job;
boost::shared_ptr<Encoder> _encoder;
- std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _da;
- std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _db;
+ Decoders _da;
+ Decoders _db;
boost::shared_ptr<Combiner> _combiner;
boost::shared_ptr<Matcher> _matcher;
boost::shared_ptr<DelayLine> _delay_line;
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index 70f0effd9..9d8de971c 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -23,7 +23,7 @@
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
: Decoder (f, o, j)
{
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index 1570fe3b0..013a6327f 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -34,7 +34,7 @@
class AudioDecoder : public AudioSource, public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
diff --git a/src/lib/check_hashes_job.cc b/src/lib/check_hashes_job.cc
index 3967d0d70..701584c74 100644
--- a/src/lib/check_hashes_job.cc
+++ b/src/lib/check_hashes_job.cc
@@ -34,9 +34,10 @@ using std::stringstream;
using std::ifstream;
using boost::shared_ptr;
-CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req)
+CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
: Job (f, req)
- , _opt (o)
+ , _decode_opt (od)
+ , _encode_opt (oe)
, _bad (0)
{
@@ -61,8 +62,8 @@ CheckHashesJob::run ()
DCPFrameRate const dfr = dcp_frame_rate (_film->frames_per_second ());
for (SourceFrame i = _film->dcp_trim_start(); i < N; i += dfr.skip) {
- string const j2k_file = _opt->frame_out_path (i, false);
- string const hash_file = j2k_file + ".md5";
+ string const j2k_file = _encode_opt->frame_out_path (i, false);
+ string const hash_file = _encode_opt->hash_out_path (i, false);
if (!boost::filesystem::exists (j2k_file)) {
_film->log()->log (String::compose ("Frame %1 has a missing J2K file.", i));
@@ -91,13 +92,13 @@ CheckHashesJob::run ()
shared_ptr<Job> tc;
if (_film->dcp_ab()) {
- tc.reset (new ABTranscodeJob (_film, _opt, shared_from_this()));
+ tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
} else {
- tc.reset (new TranscodeJob (_film, _opt, shared_from_this()));
+ tc.reset (new TranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
}
JobManager::instance()->add_after (shared_from_this(), tc);
- JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _opt, tc)));
+ JobManager::instance()->add_after (tc, shared_ptr<Job> (new CheckHashesJob (_film, _decode_opt, _encode_opt, tc)));
}
set_progress (1);
diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h
index e0ed6a64a..c41af9d3f 100644
--- a/src/lib/check_hashes_job.h
+++ b/src/lib/check_hashes_job.h
@@ -19,16 +19,25 @@
#include "job.h"
+class DecodeOptions;
+class EncodeOptions;
+
class CheckHashesJob : public Job
{
public:
- CheckHashesJob (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, boost::shared_ptr<Job> req);
+ CheckHashesJob (
+ boost::shared_ptr<Film> f,
+ boost::shared_ptr<const DecodeOptions> od,
+ boost::shared_ptr<const EncodeOptions> oe,
+ boost::shared_ptr<Job> req
+ );
std::string name () const;
void run ();
std::string status () const;
private:
- boost::shared_ptr<const Options> _opt;
+ boost::shared_ptr<const DecodeOptions> _decode_opt;
+ boost::shared_ptr<const EncodeOptions> _encode_opt;
int _bad;
};
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index c185de0f4..8b70b0aa4 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -154,10 +154,10 @@ shared_ptr<EncodedData>
DCPVideoFrame::encode_locally ()
{
if (!_post_process.empty ()) {
- _input = _input->post_process (_post_process);
+ _input = _input->post_process (_post_process, true);
}
- shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler);
+ shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true);
if (_subtitle) {
Rect tx = subtitle_transformed_area (
@@ -166,7 +166,7 @@ DCPVideoFrame::encode_locally ()
_subtitle->area(), _subtitle_offset, _subtitle_scale
);
- shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler);
+ shared_ptr<Image> im = _subtitle->image()->scale (tx.size(), _scaler, true);
prepared->alpha_blend (im, tx.position());
}
@@ -376,7 +376,7 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
* @param frame Frame index.
*/
void
-EncodedData::write (shared_ptr<const Options> opt, SourceFrame frame)
+EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
{
string const tmp_j2k = opt->frame_out_path (frame, true);
@@ -395,7 +395,7 @@ EncodedData::write (shared_ptr<const Options> opt, SourceFrame frame)
boost::filesystem::rename (tmp_j2k, real_j2k);
/* Write a file containing the hash */
- string const hash = real_j2k + ".md5";
+ string const hash = opt->hash_out_path (frame, false);
ofstream h (hash.c_str());
h << md5_digest (_data, _size) << "\n";
h.close ();
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index 5ae53f1e8..57e7e6203 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -26,7 +26,7 @@
*/
class FilmState;
-class Options;
+class EncodeOptions;
class ServerDescription;
class Scaler;
class Image;
@@ -50,7 +50,7 @@ public:
virtual ~EncodedData () {}
void send (boost::shared_ptr<Socket> socket);
- void write (boost::shared_ptr<const Options>, SourceFrame);
+ void write (boost::shared_ptr<const EncodeOptions>, SourceFrame);
/** @return data */
uint8_t* data () const {
@@ -122,7 +122,6 @@ public:
private:
void create_openjpeg_container ();
- void write_encoded (boost::shared_ptr<const Options>, uint8_t *, int);
boost::shared_ptr<const Image> _input; ///< the input image
boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 2bacf58e7..7d4085045 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -49,10 +49,16 @@ using boost::optional;
* @param o Options.
* @param j Job that we are running within, or 0
*/
-Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j)
+Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
: _film (f)
, _opt (o)
, _job (j)
{
-
+ _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1));
+}
+
+bool
+Decoder::seek (SourceFrame f)
+{
+ throw DecodeError ("decoder does not support seek");
}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index e757e5401..b8278ff80 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -33,15 +33,15 @@
#include "stream.h"
#include "video_source.h"
#include "audio_source.h"
+#include "film.h"
class Job;
-class Options;
+class DecodeOptions;
class Image;
class Log;
class DelayLine;
class TimedSubtitle;
class Subtitle;
-class Film;
class FilterGraph;
/** @class Decoder.
@@ -54,18 +54,29 @@ class FilterGraph;
class Decoder
{
public:
- Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
virtual ~Decoder () {}
virtual bool pass () = 0;
+ /** Seek.
+ * @return true on error.
+ */
+ virtual bool seek (SourceFrame);
+
+ boost::signals2::signal<void()> OutputChanged;
protected:
/** our Film */
boost::shared_ptr<Film> _film;
/** our options */
- boost::shared_ptr<const Options> _opt;
+ boost::shared_ptr<const DecodeOptions> _opt;
/** associated Job, or 0 */
Job* _job;
+
+private:
+ virtual void film_changed (Film::Property) {}
+
+ boost::signals2::scoped_connection _film_connection;
};
#endif
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
index b2118ef74..1d8d12cd5 100644
--- a/src/lib/decoder_factory.cc
+++ b/src/lib/decoder_factory.cc
@@ -26,6 +26,7 @@
#include "imagemagick_decoder.h"
#include "film.h"
#include "external_audio_decoder.h"
+#include "decoder_factory.h"
using std::string;
using std::pair;
@@ -33,14 +34,14 @@ using std::make_pair;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
-pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> >
+Decoders
decoder_factory (
- shared_ptr<Film> f, shared_ptr<const Options> o, Job* j
+ shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j
)
{
if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
/* A single image file, or a directory of them */
- return make_pair (
+ return Decoders (
shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o, j)),
shared_ptr<AudioDecoder> ()
);
@@ -48,8 +49,8 @@ decoder_factory (
shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o, j));
if (f->use_content_audio()) {
- return make_pair (fd, fd);
+ return Decoders (fd, fd);
}
- return make_pair (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j)));
+ return Decoders (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j)));
}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
index 1f3690611..47d977ce7 100644
--- a/src/lib/decoder_factory.h
+++ b/src/lib/decoder_factory.h
@@ -17,16 +17,33 @@
*/
+#ifndef DVDOMATIC_DECODER_FACTORY_H
+#define DVDOMATIC_DECODER_FACTORY_H
+
/** @file src/decoder_factory.h
* @brief A method to create appropriate decoders for some content.
*/
class Film;
-class Options;
+class DecodeOptions;
class Job;
class VideoDecoder;
class AudioDecoder;
-extern std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > decoder_factory (
- boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *
+struct Decoders {
+ Decoders () {}
+
+ Decoders (boost::shared_ptr<VideoDecoder> v, boost::shared_ptr<AudioDecoder> a)
+ : video (v)
+ , audio (a)
+ {}
+
+ boost::shared_ptr<VideoDecoder> video;
+ boost::shared_ptr<AudioDecoder> audio;
+};
+
+extern Decoders decoder_factory (
+ boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *
);
+
+#endif
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 17a6726a6..1fc7d5997 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -33,7 +33,7 @@ int const Encoder::_history_size = 25;
/** @param f Film that we are encoding.
* @param o Options.
*/
-Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const Options> o)
+Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
: _film (f)
, _opt (o)
, _just_skipped (false)
@@ -107,13 +107,13 @@ Encoder::frame_skipped ()
void
Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s)
{
- if (_opt->decode_video_skip != 0 && (_video_frame % _opt->decode_video_skip) != 0) {
+ if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) {
++_video_frame;
return;
}
- if (_opt->video_decode_range) {
- pair<SourceFrame, SourceFrame> const r = _opt->video_decode_range.get();
+ if (_opt->video_range) {
+ pair<SourceFrame, SourceFrame> const r = _opt->video_range.get();
if (_video_frame < r.first || _video_frame >= r.second) {
++_video_frame;
return;
@@ -127,12 +127,12 @@ Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s)
void
Encoder::process_audio (shared_ptr<AudioBuffers> data)
{
- if (_opt->audio_decode_range) {
+ if (_opt->audio_range) {
shared_ptr<AudioBuffers> trimmed (new AudioBuffers (*data.get ()));
/* Range that we are encoding */
- pair<int64_t, int64_t> required_range = _opt->audio_decode_range.get();
+ pair<int64_t, int64_t> required_range = _opt->audio_range.get();
/* Range of this block of data */
pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames());
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index b12bd0d48..64f113d74 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -35,7 +35,7 @@ extern "C" {
#include "video_sink.h"
#include "audio_sink.h"
-class Options;
+class EncodeOptions;
class Image;
class Subtitle;
class AudioBuffers;
@@ -54,7 +54,7 @@ class Film;
class Encoder : public VideoSink, public AudioSink
{
public:
- Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o);
+ Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o);
virtual ~Encoder () {}
/** Called to indicate that a processing run is about to begin */
@@ -93,7 +93,7 @@ protected:
/** Film that we are encoding */
boost::shared_ptr<const Film> _film;
/** Options */
- boost::shared_ptr<const Options> _opt;
+ boost::shared_ptr<const EncodeOptions> _opt;
/** Mutex for _time_history, _just_skipped and _last_frame */
mutable boost::mutex _history_mutex;
diff --git a/src/lib/encoder_factory.cc b/src/lib/encoder_factory.cc
index 2da021ad8..fe4d50ef3 100644
--- a/src/lib/encoder_factory.cc
+++ b/src/lib/encoder_factory.cc
@@ -29,7 +29,7 @@
using boost::shared_ptr;
shared_ptr<Encoder>
-encoder_factory (shared_ptr<const Film> f, shared_ptr<const Options> o)
+encoder_factory (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
{
if (!boost::filesystem::is_directory (f->content_path()) && f->content_type() == STILL) {
return shared_ptr<Encoder> (new J2KStillEncoder (f, o));
diff --git a/src/lib/encoder_factory.h b/src/lib/encoder_factory.h
index 1bc4e18df..5ac5c9559 100644
--- a/src/lib/encoder_factory.h
+++ b/src/lib/encoder_factory.h
@@ -22,9 +22,9 @@
*/
class Encoder;
-class Options;
+class EncodeOptions;
class Job;
class Log;
class Film;
-extern boost::shared_ptr<Encoder> encoder_factory (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>);
+extern boost::shared_ptr<Encoder> encoder_factory (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>);
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
index 8db74801f..a783cde33 100644
--- a/src/lib/examine_content_job.cc
+++ b/src/lib/examine_content_job.cc
@@ -26,7 +26,6 @@
#include "options.h"
#include "decoder_factory.h"
#include "decoder.h"
-#include "imagemagick_encoder.h"
#include "transcoder.h"
#include "log.h"
#include "film.h"
@@ -60,84 +59,50 @@ ExamineContentJob::name () const
void
ExamineContentJob::run ()
{
- /* Decode the content to get an accurate length */
-
- /* We don't want to use any existing length here, as progress
- will be messed up.
- */
- _film->unset_length ();
-
- shared_ptr<Options> o (new Options ("", "", ""));
- o->out_size = Size (512, 512);
- o->apply_crop = false;
- o->decode_audio = false;
-
descend (0.5);
-
- pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoders = decoder_factory (_film, o, this);
-
- set_progress_unknown ();
- while (!decoders.first->pass()) {
- /* keep going */
- }
-
- _film->set_length (decoders.first->video_frame());
-
- _film->log()->log (String::compose ("Video length is %1 frames", _film->length()));
-
+ _film->set_content_digest (md5_digest (_film->content_path ()));
ascend ();
- /* Now make thumbnails for it */
-
descend (0.5);
- try {
- o.reset (new Options (_film->dir ("thumbs"), ".png", ""));
- o->out_size = _film->size ();
- o->apply_crop = false;
+ /* Set the film's length to either
+ a) a length judged by running through the content or
+ b) the length from a decoder's header.
+ */
+ if (!_film->trust_content_header()) {
+ /* Decode the content to get an accurate length */
+
+ /* We don't want to use any existing length here, as progress
+ will be messed up.
+ */
+ _film->unset_length ();
+ _film->set_crop (Crop ());
+
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
o->decode_audio = false;
- if (_film->length() > 0) {
- o->decode_video_skip = _film->length().get() / 128;
- } else {
- o->decode_video_skip = 0;
+
+ Decoders decoders = decoder_factory (_film, o, this);
+
+ set_progress_unknown ();
+ while (!decoders.video->pass()) {
+ /* keep going */
}
- o->decode_subtitles = true;
- shared_ptr<ImageMagickEncoder> e (new ImageMagickEncoder (_film, o));
- Transcoder w (_film, o, this, e);
- w.go ();
- } catch (std::exception& e) {
-
- ascend ();
- set_progress (1);
- set_error (e.what ());
- set_state (FINISHED_ERROR);
- return;
+ _film->set_length (decoders.video->video_frame());
- }
-
- string const tdir = _film->dir ("thumbs");
- vector<SourceFrame> thumbs;
-
- for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (tdir); i != boost::filesystem::directory_iterator(); ++i) {
+ _film->log()->log (String::compose ("Video length examined as %1 frames", _film->length().get()));
+
+ } else {
- /* Aah, the sweet smell of progress */
-#if BOOST_FILESYSTEM_VERSION == 3
- string const l = boost::filesystem::path(*i).leaf().generic_string();
-#else
- string const l = i->leaf ();
-#endif
+ /* Get a quick decoder to get the content's length from its header */
- size_t const d = l.find (".png");
- size_t const t = l.find (".tmp");
- if (d != string::npos && t == string::npos) {
- thumbs.push_back (atoi (l.substr (0, d).c_str()));
- }
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
+ Decoders d = decoder_factory (_film, o, 0);
+ _film->set_length (d.video->length());
+
+ _film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get()));
}
- sort (thumbs.begin(), thumbs.end());
- _film->set_thumbs (thumbs);
-
ascend ();
set_progress (1);
set_state (FINISHED_OK);
diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc
index 9b121235a..25c8068b6 100644
--- a/src/lib/external_audio_decoder.cc
+++ b/src/lib/external_audio_decoder.cc
@@ -31,7 +31,7 @@ using std::cout;
using boost::shared_ptr;
using boost::optional;
-ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
: Decoder (f, o, j)
, AudioDecoder (f, o, j)
{
diff --git a/src/lib/external_audio_decoder.h b/src/lib/external_audio_decoder.h
index 45a2a809c..2558955eb 100644
--- a/src/lib/external_audio_decoder.h
+++ b/src/lib/external_audio_decoder.h
@@ -44,7 +44,7 @@ private:
class ExternalAudioDecoder : public AudioDecoder
{
public:
- ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
bool pass ();
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 911714d7b..0e4446a86 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -59,7 +59,7 @@ using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
: Decoder (f, o, j)
, VideoDecoder (f, o, j)
, AudioDecoder (f, o, j)
@@ -77,6 +77,10 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, J
setup_video ();
setup_audio ();
setup_subtitle ();
+
+ if (!o->video_sync) {
+ _first_video = 0;
+ }
}
FFmpegDecoder::~FFmpegDecoder ()
@@ -162,14 +166,7 @@ FFmpegDecoder::setup_video ()
throw DecodeError ("could not find video decoder");
}
- /* I think this prevents problems with green hash on decodes and
- "changing frame properties on the fly is not supported by all filters"
- messages with some content. Although I'm not sure; needs checking.
- */
- AVDictionary* opts = 0;
- av_dict_set (&opts, "threads", "1", 0);
-
- if (avcodec_open2 (_video_codec_context, _video_codec, &opts) < 0) {
+ if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
throw DecodeError ("could not open video decoder");
}
}
@@ -259,7 +256,7 @@ FFmpegDecoder::pass ()
avcodec_get_frame_defaults (_frame);
shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
+
if (_packet.stream_index == _video_stream) {
int frame_finished;
@@ -270,46 +267,10 @@ FFmpegDecoder::pass ()
_film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size));
}
- /* Where we are in the output, in seconds */
- double const out_pts_seconds = video_frame() / frames_per_second();
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
-
- _film->log()->log (
- String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds),
- Log::VERBOSE
- );
-
- if (!_first_video) {
- _first_video = source_pts_seconds;
- }
-
- /* Difference between where we are and where we should be */
- double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
- double const one_frame = 1 / frames_per_second();
-
- /* Insert frames if required to get out_pts_seconds up to pts_seconds */
- if (delta > one_frame) {
- int const extra = rint (delta / one_frame);
- for (int i = 0; i < extra; ++i) {
- repeat_last_video ();
- _film->log()->log (
- String::compose (
- "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)",
- out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
- )
- );
- }
- }
-
- if (delta > -one_frame) {
- /* Process this frame */
- filter_and_emit_video (_frame);
+ if (_opt->video_sync) {
+ out_with_sync ();
} else {
- /* Otherwise we are omitting a frame to keep things right */
- _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
+ filter_and_emit_video (_frame);
}
}
@@ -557,11 +518,14 @@ FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
{
VideoDecoder::set_subtitle_stream (s);
setup_subtitle ();
+ OutputChanged ();
}
void
FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
{
+ boost::mutex::scoped_lock lm (_filter_graphs_mutex);
+
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
@@ -570,7 +534,7 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
}
if (i == _filter_graphs.end ()) {
- graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+ graph.reset (new FilterGraph (_film, this, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
_filter_graphs.push_back (graph);
_film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format));
} else {
@@ -579,11 +543,33 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
list<shared_ptr<Image> > images = graph->process (frame);
+ SourceFrame const sf = av_q2d (_format_context->streams[_video_stream]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame) * frames_per_second();
+
for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
- emit_video (*i);
+ emit_video (*i, sf);
}
}
+bool
+FFmpegDecoder::seek (SourceFrame f)
+{
+ int64_t const vt = static_cast<int64_t>(f) / (av_q2d (_format_context->streams[_video_stream]->time_base) * frames_per_second());
+
+ /* This AVSEEK_FLAG_BACKWARD is a bit of a hack; without it, if we ask for a seek to the same place as last time
+ (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
+ staying in the same place.
+ */
+ int const r = av_seek_frame (_format_context, _video_stream, vt, (f == last_source_frame() ? AVSEEK_FLAG_BACKWARD : 0));
+
+ avcodec_flush_buffers (_video_codec_context);
+ if (_subtitle_codec_context) {
+ avcodec_flush_buffers (_subtitle_codec_context);
+ }
+
+ return r < 0;
+}
+
shared_ptr<FFmpegAudioStream>
FFmpegAudioStream::create (string t, optional<int> v)
{
@@ -636,3 +622,74 @@ FFmpegAudioStream::to_string () const
return String::compose ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name);
}
+void
+FFmpegDecoder::out_with_sync ()
+{
+ /* Where we are in the output, in seconds */
+ double const out_pts_seconds = video_frame() / frames_per_second();
+
+ /* Where we are in the source, in seconds */
+ double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame);
+
+ _film->log()->log (
+ String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds),
+ Log::VERBOSE
+ );
+
+ if (!_first_video) {
+ _first_video = source_pts_seconds;
+ }
+
+ /* Difference between where we are and where we should be */
+ double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
+ double const one_frame = 1 / frames_per_second();
+
+ /* Insert frames if required to get out_pts_seconds up to pts_seconds */
+ if (delta > one_frame) {
+ int const extra = rint (delta / one_frame);
+ for (int i = 0; i < extra; ++i) {
+ repeat_last_video ();
+ _film->log()->log (
+ String::compose (
+ "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)",
+ out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
+ )
+ );
+ }
+ }
+
+ if (delta > -one_frame) {
+ /* Process this frame */
+ filter_and_emit_video (_frame);
+ } else {
+ /* Otherwise we are omitting a frame to keep things right */
+ _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
+ }
+}
+
+void
+FFmpegDecoder::film_changed (Film::Property p)
+{
+ switch (p) {
+ case Film::CROP:
+ case Film::FILTERS:
+ {
+ boost::mutex::scoped_lock lm (_filter_graphs_mutex);
+ _filter_graphs.clear ();
+ }
+ OutputChanged ();
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @return Length (in video frames) according to our content's header */
+SourceFrame
+FFmpegDecoder::length () const
+{
+ return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
+}
+
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 87eebe1ec..2011ef72f 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -26,6 +26,7 @@
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include <boost/optional.hpp>
+#include <boost/thread/mutex.hpp>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libpostproc/postprocess.h>
@@ -34,6 +35,7 @@ extern "C" {
#include "decoder.h"
#include "video_decoder.h"
#include "audio_decoder.h"
+#include "film.h"
struct AVFilterGraph;
struct AVCodecContext;
@@ -56,7 +58,7 @@ public:
, _name (n)
, _id (i)
{}
-
+
std::string to_string () const;
std::string name () const {
@@ -84,11 +86,12 @@ private:
class FFmpegDecoder : public VideoDecoder, public AudioDecoder
{
public:
- FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
~FFmpegDecoder ();
float frames_per_second () const;
Size native_size () const;
+ SourceFrame length () const;
int time_base_numerator () const;
int time_base_denominator () const;
int sample_aspect_ratio_numerator () const;
@@ -97,6 +100,8 @@ public:
void set_audio_stream (boost::shared_ptr<AudioStream>);
void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+ bool seek (SourceFrame);
+
private:
bool pass ();
@@ -104,6 +109,7 @@ private:
AVSampleFormat audio_sample_format () const;
int bytes_per_audio_sample () const;
+ void out_with_sync ();
void filter_and_emit_video (AVFrame *);
void setup_general ();
@@ -114,6 +120,8 @@ private:
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size);
+ void film_changed (Film::Property);
+
std::string stream_name (AVStream* s) const;
AVFormatContext* _format_context;
@@ -134,4 +142,5 @@ private:
boost::optional<double> _first_audio;
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
+ boost::mutex _filter_graphs_mutex;
};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 3f9210080..58d1e5010 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -31,7 +31,6 @@
#include <boost/date_time.hpp>
#include "film.h"
#include "format.h"
-#include "imagemagick_encoder.h"
#include "job.h"
#include "filter.h"
#include "transcoder.h"
@@ -86,6 +85,7 @@ int const Film::state_version = 1;
Film::Film (string d, bool must_exist)
: _use_dci_name (true)
+ , _trust_content_header (true)
, _dcp_content_type (0)
, _format (0)
, _scaler (Scaler::from_id ("bicubic"))
@@ -144,6 +144,7 @@ Film::Film (Film const & o)
, _name (o._name)
, _use_dci_name (o._use_dci_name)
, _content (o._content)
+ , _trust_content_header (o._trust_content_header)
, _dcp_content_type (o._dcp_content_type)
, _format (o._format)
, _crop (o._crop)
@@ -169,7 +170,6 @@ Film::Film (Film const & o)
, _studio (o._studio)
, _facility (o._facility)
, _package_type (o._package_type)
- , _thumbs (o._thumbs)
, _size (o._size)
, _length (o._length)
, _content_digest (o._content_digest)
@@ -260,35 +260,37 @@ Film::make_dcp (bool transcode)
throw MissingSettingError ("name");
}
- shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", dir ("wavs")));
- o->out_size = format()->dcp_size ();
- o->padding = format()->dcp_padding (shared_from_this ());
- o->ratio = format()->ratio_as_float (shared_from_this ());
+ shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
+ oe->out_size = format()->dcp_size ();
+ oe->padding = format()->dcp_padding (shared_from_this ());
if (dcp_length ()) {
- o->video_decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
+ oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
if (audio_stream()) {
- o->audio_decode_range = make_pair (
- video_frames_to_audio_frames (o->video_decode_range.get().first, audio_stream()->sample_rate(), frames_per_second()),
- video_frames_to_audio_frames (o->video_decode_range.get().second, audio_stream()->sample_rate(), frames_per_second())
+ oe->audio_range = make_pair (
+ video_frames_to_audio_frames (oe->video_range.get().first, audio_stream()->sample_rate(), frames_per_second()),
+ video_frames_to_audio_frames (oe->video_range.get().second, audio_stream()->sample_rate(), frames_per_second())
);
}
}
- o->decode_subtitles = with_subtitles ();
- o->decode_video_skip = dcp_frame_rate (frames_per_second()).skip;
+
+ oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
+
+ shared_ptr<DecodeOptions> od (new DecodeOptions);
+ od->decode_subtitles = with_subtitles ();
shared_ptr<Job> r;
if (transcode) {
if (dcp_ab()) {
- r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
+ r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
} else {
- r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
+ r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
}
}
- r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), o, r)));
- JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), o, r)));
+ r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
+ JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
}
/** Start a job to examine our content file */
@@ -299,12 +301,6 @@ Film::examine_content ()
return;
}
- set_thumbs (vector<SourceFrame> ());
- boost::filesystem::remove_all (dir ("thumbs"));
-
- /* This call will recreate the directory */
- dir ("thumbs");
-
_examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr<Job> ()));
_examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
JobManager::instance()->add (_examine_content_job);
@@ -355,35 +351,6 @@ Film::encoded_frames () const
return N;
}
-/** Return the filename of a subtitle image if one exists for a given thumb index.
- * @param Thumbnail index.
- * @return Position of the image within the source frame, and the image filename, if one exists.
- * Otherwise the filename will be empty.
- */
-pair<Position, string>
-Film::thumb_subtitle (int n) const
-{
- string sub_file = thumb_base(n) + ".sub";
- if (!boost::filesystem::exists (sub_file)) {
- return pair<Position, string> ();
- }
-
- pair<Position, string> sub;
-
- ifstream f (sub_file.c_str ());
- multimap<string, string> kv = read_key_value (f);
- for (map<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
- if (i->first == "x") {
- sub.first.x = lexical_cast<int> (i->second);
- } else if (i->first == "y") {
- sub.first.y = lexical_cast<int> (i->second);
- sub.second = String::compose ("%1.sub.png", thumb_base(n));
- }
- }
-
- return sub;
-}
-
/** Write state to our `metadata' file */
void
Film::write_metadata () const
@@ -404,6 +371,7 @@ Film::write_metadata () const
f << "name " << _name << "\n";
f << "use_dci_name " << _use_dci_name << "\n";
f << "content " << _content << "\n";
+ f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n";
if (_dcp_content_type) {
f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
}
@@ -445,12 +413,6 @@ Film::write_metadata () const
f << "facility " << _facility << "\n";
f << "package_type " << _package_type << "\n";
- /* Cached stuff; this is information about our content; we could
- look it up each time, but that's slow.
- */
- for (vector<SourceFrame>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) {
- f << "thumb " << *i << "\n";
- }
f << "width " << _size.width << "\n";
f << "height " << _size.height << "\n";
f << "length " << _length.get_value_or(0) << "\n";
@@ -478,7 +440,6 @@ Film::read_metadata ()
boost::mutex::scoped_lock lm (_state_mutex);
_external_audio.clear ();
- _thumbs.clear ();
_content_audio_streams.clear ();
_subtitle_streams.clear ();
@@ -513,6 +474,8 @@ Film::read_metadata ()
_use_dci_name = (v == "1");
} else if (k == "content") {
_content = v;
+ } else if (k == "trust_content_header") {
+ _trust_content_header = (v == "1");
} else if (k == "dcp_content_type") {
_dcp_content_type = DCPContentType::from_pretty_name (v);
} else if (k == "format") {
@@ -580,13 +543,7 @@ Film::read_metadata ()
}
/* Cached stuff */
- if (k == "thumb") {
- int const n = atoi (v.c_str ());
- /* Only add it to the list if it still exists */
- if (boost::filesystem::exists (thumb_file_for_frame (n))) {
- _thumbs.push_back (n);
- }
- } else if (k == "width") {
+ if (k == "width") {
_size.width = atoi (v.c_str ());
} else if (k == "height") {
_size.height = atoi (v.c_str ());
@@ -630,61 +587,6 @@ Film::read_metadata ()
_dirty = false;
}
-/** @param n A thumb index.
- * @return The path to the thumb's image file.
- */
-string
-Film::thumb_file (int n) const
-{
- return thumb_file_for_frame (thumb_frame (n));
-}
-
-/** @param n A frame index within the Film's source.
- * @return The path to the thumb's image file for this frame;
- * we assume that it exists.
- */
-string
-Film::thumb_file_for_frame (SourceFrame n) const
-{
- return thumb_base_for_frame(n) + ".png";
-}
-
-/** @param n Thumb index.
- * Must not be called with the _state_mutex locked.
- */
-string
-Film::thumb_base (int n) const
-{
- return thumb_base_for_frame (thumb_frame (n));
-}
-
-string
-Film::thumb_base_for_frame (SourceFrame n) const
-{
- stringstream s;
- s.width (8);
- s << setfill('0') << n;
-
- boost::filesystem::path p;
- p /= dir ("thumbs");
- p /= s.str ();
-
- return p.string ();
-}
-
-/** @param n A thumb index.
- * @return The frame within the Film's source that it is for.
- *
- * Must not be called with the _state_mutex locked.
- */
-SourceFrame
-Film::thumb_frame (int n) const
-{
- boost::mutex::scoped_lock lm (_state_mutex);
- assert (n < int (_thumbs.size ()));
- return _thumbs[n];
-}
-
Size
Film::cropped_size (Size s) const
{
@@ -776,6 +678,10 @@ Film::target_audio_sample_rate () const
boost::optional<SourceFrame>
Film::dcp_length () const
{
+ if (content_type() == STILL) {
+ return _still_duration * frames_per_second();
+ }
+
if (!length()) {
return boost::optional<SourceFrame> ();
}
@@ -949,23 +855,23 @@ Film::set_content (string c)
*/
try {
- shared_ptr<Options> o (new Options ("", "", ""));
- o->out_size = Size (1024, 1024);
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
+ Decoders d = decoder_factory (shared_from_this(), o, 0);
- pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (shared_from_this(), o, 0);
-
- set_size (d.first->native_size ());
- set_frames_per_second (d.first->frames_per_second ());
- set_subtitle_streams (d.first->subtitle_streams ());
- set_content_audio_streams (d.second->audio_streams ());
+ set_size (d.video->native_size ());
+ set_frames_per_second (d.video->frames_per_second ());
+ set_subtitle_streams (d.video->subtitle_streams ());
+ if (d.audio) {
+ set_content_audio_streams (d.audio->audio_streams ());
+ }
/* Start off with the first audio and subtitle streams */
- if (!d.second->audio_streams().empty()) {
- set_content_audio_stream (d.second->audio_streams().front());
+ if (d.audio && !d.audio->audio_streams().empty()) {
+ set_content_audio_stream (d.audio->audio_streams().front());
}
- if (!d.first->subtitle_streams().empty()) {
- set_subtitle_stream (d.first->subtitle_streams().front());
+ if (!d.video->subtitle_streams().empty()) {
+ set_subtitle_stream (d.video->subtitle_streams().front());
}
{
@@ -975,8 +881,6 @@ Film::set_content (string c)
signal_changed (CONTENT);
- set_content_digest (md5_digest (content_path ()));
-
examine_content ();
} catch (...) {
@@ -986,6 +890,32 @@ Film::set_content (string c)
throw;
}
+
+ /* Default format */
+ switch (content_type()) {
+ case STILL:
+ set_format (Format::from_id ("var-185"));
+ break;
+ case VIDEO:
+ set_format (Format::from_id ("185"));
+ break;
+ }
+}
+
+void
+Film::set_trust_content_header (bool t)
+{
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _trust_content_header = t;
+ }
+
+ signal_changed (TRUST_CONTENT_HEADER);
+
+ if (!_trust_content_header && !content().empty()) {
+ /* We just said that we don't trust the content's header */
+ examine_content ();
+ }
}
void
@@ -1143,8 +1073,7 @@ Film::set_external_audio (vector<string> a)
_external_audio = a;
}
- shared_ptr<Options> o (new Options ("", "", ""));
- o->decode_audio = true;
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
if (decoder->audio_stream()) {
_external_audio_stream = decoder->audio_stream ();
@@ -1305,16 +1234,6 @@ Film::set_package_type (string p)
}
void
-Film::set_thumbs (vector<SourceFrame> t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _thumbs = t;
- }
- signal_changed (THUMBS);
-}
-
-void
Film::set_size (Size s)
{
{
diff --git a/src/lib/film.h b/src/lib/film.h
index 2e81575e4..536855b1f 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -61,7 +61,6 @@ public:
std::string j2k_dir () const;
std::vector<std::string> audio_files () const;
- std::pair<Position, std::string> thumb_subtitle (int) const;
void examine_content ();
void send_dcp_to_tms ();
@@ -83,10 +82,6 @@ public:
std::string content_path () const;
ContentType content_type () const;
- std::string thumb_file (int) const;
- std::string thumb_base (int) const;
- SourceFrame thumb_frame (int) const;
-
int target_audio_sample_rate () const;
void write_metadata () const;
@@ -110,6 +105,7 @@ public:
NAME,
USE_DCI_NAME,
CONTENT,
+ TRUST_CONTENT_HEADER,
DCP_CONTENT_TYPE,
FORMAT,
CROP,
@@ -129,7 +125,6 @@ public:
SUBTITLE_OFFSET,
SUBTITLE_SCALE,
DCI_METADATA,
- THUMBS,
SIZE,
LENGTH,
CONTENT_AUDIO_STREAMS,
@@ -160,6 +155,11 @@ public:
return _content;
}
+ bool trust_content_header () const {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _trust_content_header;
+ }
+
DCPContentType const * dcp_content_type () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _dcp_content_type;
@@ -285,11 +285,6 @@ public:
return _package_type;
}
- std::vector<SourceFrame> thumbs () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _thumbs;
- }
-
Size size () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _size;
@@ -317,6 +312,10 @@ public:
float frames_per_second () const {
boost::mutex::scoped_lock lm (_state_mutex);
+ if (content_type() == STILL) {
+ return 24;
+ }
+
return _frames_per_second;
}
@@ -329,6 +328,7 @@ public:
void set_name (std::string);
void set_use_dci_name (bool);
void set_content (std::string);
+ void set_trust_content_header (bool);
void set_dcp_content_type (DCPContentType const *);
void set_format (Format const *);
void set_crop (Crop);
@@ -358,7 +358,6 @@ public:
void set_studio (std::string);
void set_facility (std::string);
void set_package_type (std::string);
- void set_thumbs (std::vector<SourceFrame>);
void set_size (Size);
void set_length (SourceFrame);
void unset_length ();
@@ -384,8 +383,6 @@ private:
/** The date that we should use in a DCI name */
boost::gregorian::date _dci_date;
- std::string thumb_file_for_frame (SourceFrame) const;
- std::string thumb_base_for_frame (SourceFrame) const;
void signal_changed (Property);
void examine_content_finished ();
@@ -404,6 +401,7 @@ private:
* or an absolute path.
*/
std::string _content;
+ bool _trust_content_header;
/** The type of content that this Film represents (feature, trailer etc.) */
DCPContentType const * _dcp_content_type;
/** The format to present this Film in (flat, scope, etc.) */
@@ -458,8 +456,6 @@ private:
/* Data which are cached to speed things up */
- /** Vector of frame indices for each of our `thumbnails' */
- std::vector<SourceFrame> _thumbs;
/** Size, in pixels, of the source (ignoring cropping) */
Size _size;
/** Actual length of the source (in video frames) from examining it */
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index 7320070fe..17107a05b 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -49,11 +49,10 @@ using boost::shared_ptr;
/** Construct a FilterGraph for the settings in a film.
* @param film Film.
* @param decoder Decoder that we are using.
- * @param crop true to apply crop, otherwise false.
* @param s Size of the images to process.
* @param p Pixel format of the images to process.
*/
-FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, Size s, AVPixelFormat p)
: _buffer_src_context (0)
, _buffer_sink_context (0)
, _size (s)
@@ -64,11 +63,7 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, bool cr
filters += ",";
}
- if (crop) {
- filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
- } else {
- filters += crop_string (Position (0, 0), decoder->native_size());
- }
+ filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
avfilter_register_all ();
diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h
index 3842e9f7d..a4b9ef75f 100644
--- a/src/lib/filter_graph.h
+++ b/src/lib/filter_graph.h
@@ -36,7 +36,7 @@ class FFmpegDecoder;
class FilterGraph
{
public:
- FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p);
+ FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, Size s, AVPixelFormat p);
bool can_process (Size s, AVPixelFormat p) const;
std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
diff --git a/src/lib/image.cc b/src/lib/image.cc
index 72828ed46..e136a8469 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -43,6 +43,12 @@ extern "C" {
using namespace std;
using namespace boost;
+void
+Image::swap (Image& other)
+{
+ std::swap (_pixel_format, other._pixel_format);
+}
+
/** @param n Component index.
* @return Number of lines in the image for the given component.
*/
@@ -89,11 +95,11 @@ Image::components () const
}
shared_ptr<Image>
-Image::scale (Size out_size, Scaler const * scaler) const
+Image::scale (Size out_size, Scaler const * scaler, bool aligned) const
{
assert (scaler);
- shared_ptr<Image> scaled (new AlignedImage (pixel_format(), out_size));
+ shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned));
struct SwsContext* scale_context = sws_getContext (
size().width, size().height, pixel_format(),
@@ -118,14 +124,14 @@ Image::scale (Size out_size, Scaler const * scaler) const
* @param scaler Scaler to use.
*/
shared_ptr<Image>
-Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler) const
+Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const
{
assert (scaler);
Size content_size = out_size;
content_size.width -= (padding * 2);
- shared_ptr<Image> rgb (new AlignedImage (PIX_FMT_RGB24, content_size));
+ shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, aligned));
struct SwsContext* scale_context = sws_getContext (
size().width, size().height, pixel_format(),
@@ -146,7 +152,7 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal
scheme of things.
*/
if (padding > 0) {
- shared_ptr<Image> padded_rgb (new AlignedImage (PIX_FMT_RGB24, out_size));
+ shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, aligned));
padded_rgb->make_black ();
/* XXX: we are cheating a bit here; we know the frame is RGB so we can
@@ -173,9 +179,9 @@ Image::scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scal
* @return Post-processed image.
*/
shared_ptr<Image>
-Image::post_process (string pp) const
+Image::post_process (string pp, bool aligned) const
{
- shared_ptr<Image> out (new AlignedImage (pixel_format(), size ()));
+ shared_ptr<Image> out (new SimpleImage (pixel_format(), size (), aligned));
int pp_format = 0;
switch (pixel_format()) {
@@ -206,6 +212,33 @@ Image::post_process (string pp) const
return out;
}
+shared_ptr<Image>
+Image::crop (Crop crop, bool aligned) const
+{
+ Size cropped_size = size ();
+ cropped_size.width -= crop.left + crop.right;
+ cropped_size.height -= crop.top + crop.bottom;
+
+ shared_ptr<Image> out (new SimpleImage (pixel_format(), cropped_size, aligned));
+
+ for (int c = 0; c < components(); ++c) {
+ int const crop_left_in_bytes = bytes_per_pixel(c) * crop.left;
+ int const cropped_width_in_bytes = bytes_per_pixel(c) * cropped_size.width;
+
+ /* Start of the source line, cropped from the top but not the left */
+ uint8_t* in_p = data()[c] + crop.top * stride()[c];
+ uint8_t* out_p = out->data()[c];
+
+ for (int y = 0; y < cropped_size.height; ++y) {
+ memcpy (out_p, in_p + crop_left_in_bytes, cropped_width_in_bytes);
+ in_p += line_size()[c];
+ out_p += out->line_size()[c];
+ }
+ }
+
+ return out;
+}
+
void
Image::make_black ()
{
@@ -228,7 +261,7 @@ Image::make_black ()
}
void
-Image::alpha_blend (shared_ptr<Image> other, Position position)
+Image::alpha_blend (shared_ptr<const Image> other, Position position)
{
/* Only implemented for RGBA onto RGB24 so far */
assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
@@ -287,15 +320,64 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
}
}
+
+float
+Image::bytes_per_pixel (int c) const
+{
+ if (c == 3) {
+ return 0;
+ }
+
+ switch (_pixel_format) {
+ case PIX_FMT_RGB24:
+ if (c == 0) {
+ return 3;
+ } else {
+ return 0;
+ }
+ case PIX_FMT_RGBA:
+ if (c == 0) {
+ return 4;
+ } else {
+ return 0;
+ }
+ case PIX_FMT_YUV420P:
+ case PIX_FMT_YUV422P:
+ if (c == 0) {
+ return 1;
+ } else {
+ return 0.5;
+ }
+ case PIX_FMT_YUV422P10LE:
+ if (c == 1) {
+ return 2;
+ } else {
+ return 1;
+ }
+ default:
+ assert (false);
+ }
+
+ return 0;
+}
+
+
/** Construct a SimpleImage of a given size and format, allocating memory
* as required.
*
* @param p Pixel format.
* @param s Size in pixels.
*/
-SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int, int const *)> stride_computer)
+SimpleImage::SimpleImage (AVPixelFormat p, Size s, bool aligned)
: Image (p)
, _size (s)
+ , _aligned (aligned)
+{
+ allocate ();
+}
+
+void
+SimpleImage::allocate ()
{
_data = (uint8_t **) av_malloc (4 * sizeof (uint8_t *));
_data[0] = _data[1] = _data[2] = _data[3] = 0;
@@ -306,78 +388,68 @@ SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int, int const
_stride = (int *) av_malloc (4 * sizeof (int));
_stride[0] = _stride[1] = _stride[2] = _stride[3] = 0;
- switch (p) {
- case PIX_FMT_RGB24:
- _line_size[0] = s.width * 3;
- break;
- case PIX_FMT_RGBA:
- _line_size[0] = s.width * 4;
- break;
- case PIX_FMT_YUV420P:
- case PIX_FMT_YUV422P:
- _line_size[0] = s.width;
- _line_size[1] = s.width / 2;
- _line_size[2] = s.width / 2;
- break;
- case PIX_FMT_YUV422P10LE:
- _line_size[0] = s.width * 2;
- _line_size[1] = s.width;
- _line_size[2] = s.width;
- break;
- default:
- assert (false);
- }
-
for (int i = 0; i < components(); ++i) {
- _stride[i] = stride_computer (i, _line_size);
+ _line_size[i] = _size.width * bytes_per_pixel(i);
+ _stride[i] = stride_round_up (i, _line_size, _aligned ? 32 : 1);
_data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i));
}
}
-/** Destroy a SimpleImage */
-SimpleImage::~SimpleImage ()
+SimpleImage::SimpleImage (SimpleImage const & other)
+ : Image (other)
{
+ _size = other._size;
+ _aligned = other._aligned;
+
+ allocate ();
+
for (int i = 0; i < components(); ++i) {
- av_free (_data[i]);
+ memcpy (_data[i], other._data[i], _line_size[i] * lines(i));
}
-
- av_free (_data);
- av_free (_line_size);
- av_free (_stride);
}
-uint8_t **
-SimpleImage::data () const
+SimpleImage&
+SimpleImage::operator= (SimpleImage const & other)
{
- return _data;
-}
+ if (this == &other) {
+ return *this;
+ }
-int *
-SimpleImage::line_size () const
-{
- return _line_size;
+ SimpleImage tmp (other);
+ swap (tmp);
+ return *this;
}
-int *
-SimpleImage::stride () const
+void
+SimpleImage::swap (SimpleImage & other)
{
- return _stride;
-}
+ Image::swap (other);
+
+ std::swap (_size, other._size);
-Size
-SimpleImage::size () const
-{
- return _size;
+ for (int i = 0; i < 4; ++i) {
+ std::swap (_data[i], other._data[i]);
+ std::swap (_line_size[i], other._line_size[i]);
+ std::swap (_stride[i], other._stride[i]);
+ }
+
+ std::swap (_aligned, other._aligned);
}
-AlignedImage::AlignedImage (AVPixelFormat f, Size s)
- : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 32))
+/** Destroy a SimpleImage */
+SimpleImage::~SimpleImage ()
{
+ for (int i = 0; i < components(); ++i) {
+ av_free (_data[i]);
+ }
+ av_free (_data);
+ av_free (_line_size);
+ av_free (_stride);
}
-AlignedImage::AlignedImage (shared_ptr<Image> im)
- : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 32))
+SimpleImage::SimpleImage (shared_ptr<const Image> im, bool aligned)
+ : Image (im->pixel_format())
{
assert (components() == im->components());
@@ -396,30 +468,28 @@ AlignedImage::AlignedImage (shared_ptr<Image> im)
}
}
-CompactImage::CompactImage (AVPixelFormat f, Size s)
- : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 1))
+uint8_t **
+SimpleImage::data () const
{
-
+ return _data;
}
-CompactImage::CompactImage (shared_ptr<Image> im)
- : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 1))
+int *
+SimpleImage::line_size () const
{
- assert (components() == im->components());
-
- for (int c = 0; c < components(); ++c) {
+ return _line_size;
+}
- assert (line_size()[c] == im->line_size()[c]);
+int *
+SimpleImage::stride () const
+{
+ return _stride;
+}
- uint8_t* t = data()[c];
- uint8_t* o = im->data()[c];
-
- for (int y = 0; y < lines(c); ++y) {
- memcpy (t, o, line_size()[c]);
- t += stride()[c];
- o += im->stride()[c];
- }
- }
+Size
+SimpleImage::size () const
+{
+ return _size;
}
FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b)
@@ -459,3 +529,31 @@ FilterBufferImage::size () const
return Size (_buffer->video->w, _buffer->video->h);
}
+RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im)
+ : SimpleImage (im->pixel_format(), im->size(), false)
+{
+ assert (im->pixel_format() == PIX_FMT_RGBA);
+
+ _alpha = (uint8_t *) av_malloc (im->size().width * im->size().height);
+
+ uint8_t* in = im->data()[0];
+ uint8_t* out = data()[0];
+ uint8_t* out_alpha = _alpha;
+ for (int y = 0; y < im->size().height; ++y) {
+ uint8_t* in_r = in;
+ for (int x = 0; x < im->size().width; ++x) {
+ *out++ = *in_r++;
+ *out++ = *in_r++;
+ *out++ = *in_r++;
+ *out_alpha++ = *in_r++;
+ }
+
+ in += im->stride()[0];
+ }
+}
+
+RGBPlusAlphaImage::~RGBPlusAlphaImage ()
+{
+ av_free (_alpha);
+}
+
diff --git a/src/lib/image.h b/src/lib/image.h
index 7c118f338..13b92d72f 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -69,10 +69,12 @@ public:
int components () const;
int lines (int) const;
- boost::shared_ptr<Image> scale_and_convert_to_rgb (Size, int, Scaler const *) const;
- boost::shared_ptr<Image> scale (Size, Scaler const *) const;
- boost::shared_ptr<Image> post_process (std::string) const;
- void alpha_blend (boost::shared_ptr<Image> image, Position pos);
+
+ boost::shared_ptr<Image> scale_and_convert_to_rgb (Size out_size, int padding, Scaler const * scaler, bool aligned) const;
+ boost::shared_ptr<Image> scale (Size, Scaler const *, bool aligned) const;
+ boost::shared_ptr<Image> post_process (std::string, bool aligned) const;
+ void alpha_blend (boost::shared_ptr<const Image> image, Position pos);
+ boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
void make_black ();
@@ -83,7 +85,11 @@ public:
return _pixel_format;
}
-private:
+protected:
+ virtual void swap (Image &);
+ float bytes_per_pixel (int) const;
+
+private:
AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image
};
@@ -102,6 +108,10 @@ public:
Size size () const;
private:
+ /* Not allowed */
+ FilterBufferImage (FilterBufferImage const &);
+ FilterBufferImage& operator= (FilterBufferImage const &);
+
AVFilterBufferRef* _buffer;
};
@@ -111,40 +121,41 @@ private:
class SimpleImage : public Image
{
public:
- SimpleImage (AVPixelFormat, Size, boost::function<int (int, int const *)> rounder);
+ SimpleImage (AVPixelFormat, Size, bool);
+ SimpleImage (SimpleImage const &);
+ SimpleImage& operator= (SimpleImage const &);
+ SimpleImage (boost::shared_ptr<const Image>, bool aligned);
~SimpleImage ();
uint8_t ** data () const;
int * line_size () const;
int * stride () const;
Size size () const;
+
+protected:
+ void allocate ();
+ void swap (SimpleImage &);
private:
-
Size _size; ///< size in pixels
uint8_t** _data; ///< array of pointers to components
int* _line_size; ///< array of sizes of the data in each line, in pixels (without any alignment padding bytes)
int* _stride; ///< array of strides for each line (including any alignment padding bytes)
+ bool _aligned;
};
-/** @class AlignedImage
- * @brief An image whose pixel data is padded so that rows always start on 32-byte boundaries.
- */
-class AlignedImage : public SimpleImage
+class RGBPlusAlphaImage : public SimpleImage
{
public:
- AlignedImage (AVPixelFormat, Size);
- AlignedImage (boost::shared_ptr<Image>);
-};
+ RGBPlusAlphaImage (boost::shared_ptr<const Image>);
+ ~RGBPlusAlphaImage ();
-/** @class CompactImage
- * @brief An image whose pixel data is not padded, so rows may start at any pixel alignment.
- */
-class CompactImage : public SimpleImage
-{
-public:
- CompactImage (AVPixelFormat, Size);
- CompactImage (boost::shared_ptr<Image>);
+ uint8_t* alpha () const {
+ return _alpha;
+ }
+
+private:
+ uint8_t* _alpha;
};
#endif
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
index d68c1648f..5713e68f9 100644
--- a/src/lib/imagemagick_decoder.cc
+++ b/src/lib/imagemagick_decoder.cc
@@ -29,7 +29,7 @@ using std::cout;
using boost::shared_ptr;
ImageMagickDecoder::ImageMagickDecoder (
- boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j)
+ boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
: Decoder (f, o, j)
, VideoDecoder (f, o, j)
{
@@ -73,13 +73,13 @@ ImageMagickDecoder::pass ()
return true;
}
- using namespace MagickCore;
-
Magick::Image* magick_image = new Magick::Image (_film->content_path ());
Size size = native_size ();
- shared_ptr<CompactImage> image (new CompactImage (PIX_FMT_RGB24, size));
+ shared_ptr<Image> image (new SimpleImage (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) {
@@ -91,8 +91,10 @@ ImageMagickDecoder::pass ()
}
delete magick_image;
+
+ image = image->crop (_film->crop(), false);
- emit_video (image);
+ emit_video (image, 0);
++_iter;
return false;
@@ -105,3 +107,24 @@ ImageMagickDecoder::pixel_format () const
return PIX_FMT_RGB24;
}
+bool
+ImageMagickDecoder::seek (SourceFrame f)
+{
+ _iter = _files.begin ();
+ for (int i = 0; i < f; ++i) {
+ if (_iter == _files.end()) {
+ return true;
+ }
+ ++_iter;
+ }
+
+ return false;
+}
+
+void
+ImageMagickDecoder::film_changed (Film::Property p)
+{
+ if (p == Film::CROP) {
+ OutputChanged ();
+ }
+}
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
index f636191f2..cf417d373 100644
--- a/src/lib/imagemagick_decoder.h
+++ b/src/lib/imagemagick_decoder.h
@@ -26,7 +26,7 @@ namespace Magick {
class ImageMagickDecoder : public VideoDecoder
{
public:
- ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
float frames_per_second () const {
/* We don't know */
@@ -35,6 +35,11 @@ public:
Size native_size () const;
+ SourceFrame length () const {
+ /* We don't know */
+ return 0;
+ }
+
int audio_channels () const {
return 0;
}
@@ -51,6 +56,8 @@ public:
return false;
}
+ bool seek (SourceFrame);
+
protected:
bool pass ();
PixelFormat pixel_format () const;
@@ -74,6 +81,8 @@ protected:
}
private:
+ void film_changed (Film::Property);
+
std::list<std::string> _files;
std::list<std::string>::iterator _iter;
};
diff --git a/src/lib/imagemagick_encoder.cc b/src/lib/imagemagick_encoder.cc
deleted file mode 100644
index 480dec8bc..000000000
--- a/src/lib/imagemagick_encoder.cc
+++ /dev/null
@@ -1,91 +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.
-
-*/
-
-/** @file src/imagemagick_encoder.cc
- * @brief An encoder that writes image files using ImageMagick (and does nothing with audio).
- */
-
-#include <stdexcept>
-#include <vector>
-#include <sstream>
-#include <iomanip>
-#include <iostream>
-#include <fstream>
-#include <boost/filesystem.hpp>
-#include <Magick++/Image.h>
-#include "imagemagick_encoder.h"
-#include "film.h"
-#include "options.h"
-#include "exceptions.h"
-#include "image.h"
-#include "subtitle.h"
-
-using std::string;
-using std::ofstream;
-using boost::shared_ptr;
-
-/** @param f Film that we are encoding.
- * @param o Options.
- */
-ImageMagickEncoder::ImageMagickEncoder (shared_ptr<const Film> f, shared_ptr<const Options> o)
- : Encoder (f, o)
-{
-
-}
-
-void
-ImageMagickEncoder::do_process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
-{
- shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _film->scaler());
- shared_ptr<Image> compact (new CompactImage (scaled));
-
- string tmp_file = _opt->frame_out_path (_video_frame, true);
- Magick::Image thumb (compact->size().width, compact->size().height, "RGB", MagickCore::CharPixel, compact->data()[0]);
- thumb.magick ("PNG");
- thumb.write (tmp_file);
- boost::filesystem::rename (tmp_file, _opt->frame_out_path (_video_frame, false));
-
- if (sub) {
- float const x_scale = float (_opt->out_size.width) / _film->size().width;
- float const y_scale = float (_opt->out_size.height) / _film->size().height;
-
- string tmp_metadata_file = _opt->frame_out_path (_video_frame, false, ".sub");
- ofstream metadata (tmp_metadata_file.c_str ());
-
- Size new_size = sub->image()->size ();
- new_size.width *= x_scale;
- new_size.height *= y_scale;
- shared_ptr<Image> scaled = sub->image()->scale (new_size, _film->scaler());
- shared_ptr<Image> compact (new CompactImage (scaled));
-
- string tmp_sub_file = _opt->frame_out_path (_video_frame, true, ".sub.png");
- Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]);
- sub_thumb.magick ("PNG");
- sub_thumb.write (tmp_sub_file);
- boost::filesystem::rename (tmp_sub_file, _opt->frame_out_path (_video_frame, false, ".sub.png"));
-
- metadata << "x " << sub->position().x << "\n"
- << "y " << sub->position().y << "\n";
-
- metadata.close ();
- boost::filesystem::rename (tmp_metadata_file, _opt->frame_out_path (_video_frame, false, ".sub"));
- }
-
- frame_done ();
-}
diff --git a/src/lib/imagemagick_encoder.h b/src/lib/imagemagick_encoder.h
deleted file mode 100644
index dfc741cb2..000000000
--- a/src/lib/imagemagick_encoder.h
+++ /dev/null
@@ -1,42 +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.
-
-*/
-
-/** @file src/imagemagick_encoder.h
- * @brief An encoder that writes image files using ImageMagick (and does nothing with audio).
- */
-
-#include <string>
-#include <sndfile.h>
-#include "encoder.h"
-
-class FilmState;
-class Log;
-
-/** @class ImageMagickEncoder
- * @brief An encoder that writes image files using ImageMagick files (and does nothing with audio).
- */
-class ImageMagickEncoder : public Encoder
-{
-public:
- ImageMagickEncoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o);
-
-private:
- void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
- void do_process_audio (boost::shared_ptr<AudioBuffers>) {}
-};
diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc
index dd6ef49b2..968257691 100644
--- a/src/lib/j2k_still_encoder.cc
+++ b/src/lib/j2k_still_encoder.cc
@@ -42,7 +42,7 @@ using std::string;
using std::pair;
using boost::shared_ptr;
-J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const Options> o)
+J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
: Encoder (f, o)
{
@@ -64,19 +64,32 @@ J2KStillEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> s
}
string const real = _opt->frame_out_path (0, false);
- for (int i = 1; i < (_film->still_duration() * 24); ++i) {
+ string const real_hash = _opt->hash_out_path (0, false);
+ for (int i = 1; i < (_film->still_duration() * _film->frames_per_second()); ++i) {
+
if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) {
- string const link = _opt->frame_out_path (i, false);
+ link (real, _opt->frame_out_path (i, false));
+ }
+
+ if (!boost::filesystem::exists (_opt->hash_out_path (i, false))) {
+ link (real_hash, _opt->hash_out_path (i, false));
+ }
+
+ frame_done ();
+ }
+}
+
+void
+J2KStillEncoder::link (string a, string b) const
+{
#ifdef DVDOMATIC_POSIX
- int const r = symlink (real.c_str(), link.c_str());
- if (r) {
- throw EncodeError ("could not create symlink");
- }
+ int const r = symlink (a.c_str(), b.c_str());
+ if (r) {
+ throw EncodeError ("could not create symlink");
+ }
#endif
+
#ifdef DVDOMATIC_WINDOWS
- boost::filesystem::copy_file (real, link);
+ boost::filesystem::copy_file (a, b);
#endif
- }
- frame_done ();
- }
}
diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h
index 4ffe876af..7c302474c 100644
--- a/src/lib/j2k_still_encoder.h
+++ b/src/lib/j2k_still_encoder.h
@@ -27,6 +27,7 @@
class Image;
class Log;
+class EncodeOptions;
/** @class J2KStillEncoder
* @brief An encoder which writes repeated JPEG2000 files from a single decoded input.
@@ -34,9 +35,11 @@ class Log;
class J2KStillEncoder : public Encoder
{
public:
- J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>);
+ J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>);
private:
void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
void do_process_audio (boost::shared_ptr<AudioBuffers>) {}
+
+ void link (std::string, std::string) const;
};
diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc
index 134d74623..e76591552 100644
--- a/src/lib/j2k_wav_encoder.cc
+++ b/src/lib/j2k_wav_encoder.cc
@@ -51,7 +51,7 @@ using boost::shared_ptr;
using boost::thread;
using boost::lexical_cast;
-J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const Film> f, shared_ptr<const Options> o)
+J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const Film> f, shared_ptr<const EncodeOptions> o)
: Encoder (f, o)
#ifdef HAVE_SWRESAMPLE
, _swr_context (0)
diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h
index f3340ba72..064f4221e 100644
--- a/src/lib/j2k_wav_encoder.h
+++ b/src/lib/j2k_wav_encoder.h
@@ -47,7 +47,7 @@ class AudioBuffers;
class J2KWAVEncoder : public Encoder
{
public:
- J2KWAVEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>);
+ J2KWAVEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const EncodeOptions>);
~J2KWAVEncoder ();
void process_begin ();
diff --git a/src/lib/job.h b/src/lib/job.h
index 41cefb9be..f32cfa811 100644
--- a/src/lib/job.h
+++ b/src/lib/job.h
@@ -30,7 +30,6 @@
#include <boost/signals2.hpp>
class Film;
-class Options;
/** @class Job
* @brief A parent class to represent long-running tasks which are run in their own thread.
diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc
index 65cd272e7..4605d1724 100644
--- a/src/lib/make_dcp_job.cc
+++ b/src/lib/make_dcp_job.cc
@@ -42,7 +42,7 @@ using boost::shared_ptr;
/** @param f Film we are making the DCP for.
* @param o Options.
*/
-MakeDCPJob::MakeDCPJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req)
+MakeDCPJob::MakeDCPJob (shared_ptr<Film> f, shared_ptr<const EncodeOptions> o, shared_ptr<Job> req)
: Job (f, req)
, _opt (o)
{
diff --git a/src/lib/make_dcp_job.h b/src/lib/make_dcp_job.h
index 442bb55f5..1aa906b0a 100644
--- a/src/lib/make_dcp_job.h
+++ b/src/lib/make_dcp_job.h
@@ -23,13 +23,15 @@
#include "job.h"
+class EncodeOptions;
+
/** @class MakeDCPJob
* @brief A job to create DCPs
*/
class MakeDCPJob : public Job
{
public:
- MakeDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, boost::shared_ptr<Job> req);
+ MakeDCPJob (boost::shared_ptr<Film>, boost::shared_ptr<const EncodeOptions>, boost::shared_ptr<Job> req);
std::string name () const;
void run ();
@@ -39,6 +41,6 @@ private:
std::string j2c_path (int) const;
std::string wav_path (libdcp::Channel) const;
- boost::shared_ptr<const Options> _opt;
+ boost::shared_ptr<const EncodeOptions> _opt;
};
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
index 7b4434539..2dd36c11e 100644
--- a/src/lib/matcher.cc
+++ b/src/lib/matcher.cc
@@ -81,7 +81,7 @@ Matcher::process_end ()
_log->log (String::compose ("Emitting %1 frames of black video", black_video_frames));
- shared_ptr<Image> black (new CompactImage (_pixel_format.get(), _size.get()));
+ shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), false));
black->make_black ();
for (int i = 0; i < black_video_frames; ++i) {
Video (black, shared_ptr<Subtitle>());
diff --git a/src/lib/options.h b/src/lib/options.h
index 29b3b71cd..10cdfa8cd 100644
--- a/src/lib/options.h
+++ b/src/lib/options.h
@@ -27,22 +27,19 @@
#include <boost/optional.hpp>
#include "util.h"
-/** @class Options
- * @brief Options for a transcoding operation.
+/** @class EncodeOptions
+ * @brief EncodeOptions for an encoding operation.
*
* These are settings which may be different, in different circumstances, for
- * the same film; ie they are options for a particular transcode operation.
+ * the same film; ie they are options for a particular operation.
*/
-class Options
+class EncodeOptions
{
public:
- Options (std::string f, std::string e, std::string m)
+ EncodeOptions (std::string f, std::string e, std::string m)
: padding (0)
- , apply_crop (true)
- , decode_video_skip (0)
- , decode_audio (true)
- , decode_subtitles (false)
+ , video_skip (0)
, _frame_out_path (f)
, _frame_out_extension (e)
, _multichannel_audio_out_path (m)
@@ -57,15 +54,11 @@ public:
* @param t true to return a temporary file path, otherwise a permanent one.
* @return The path to write this video frame to.
*/
- std::string frame_out_path (SourceFrame f, bool t, std::string e = "") const {
- if (e.empty ()) {
- e = _frame_out_extension;
- }
-
+ std::string frame_out_path (SourceFrame f, bool t) const {
std::stringstream s;
s << _frame_out_path << "/";
s.width (8);
- s << std::setfill('0') << f << e;
+ s << std::setfill('0') << f << _frame_out_extension;
if (t) {
s << ".tmp";
@@ -74,6 +67,10 @@ public:
return s.str ();
}
+ std::string hash_out_path (SourceFrame f, bool t) const {
+ return frame_out_path (f, t) + ".md5";
+ }
+
/** @return Path to write multichannel audio data to */
std::string multichannel_audio_out_path () const {
return _multichannel_audio_out_path;
@@ -94,21 +91,17 @@ public:
}
Size out_size; ///< size of output images
- float ratio; ///< ratio of the wanted output image (not considering padding)
int padding; ///< number of pixels of padding (in terms of the output size) each side of the image
- bool apply_crop; ///< true to apply cropping
/** Range of video frames to decode */
- boost::optional<std::pair<SourceFrame, SourceFrame> > video_decode_range;
+ boost::optional<std::pair<SourceFrame, SourceFrame> > video_range;
/** Range of audio frames to decode */
- boost::optional<std::pair<int64_t, int64_t> > audio_decode_range;
+ boost::optional<std::pair<int64_t, int64_t> > audio_range;
/** Skip frames such that we don't decode any frame where (index % decode_video_skip) != 0; e.g.
* 1 for every frame, 2 for every other frame, etc.
*/
- SourceFrame decode_video_skip;
- bool decode_audio; ///< true to decode audio, otherwise false
- bool decode_subtitles;
+ SourceFrame video_skip;
private:
/** Path of the directory to write video frames to */
@@ -118,3 +111,18 @@ private:
/** Path of the directory to write audio files to */
std::string _multichannel_audio_out_path;
};
+
+
+class DecodeOptions
+{
+public:
+ DecodeOptions ()
+ : decode_audio (true)
+ , decode_subtitles (false)
+ , video_sync (true)
+ {}
+
+ bool decode_audio;
+ bool decode_subtitles;
+ bool video_sync;
+};
diff --git a/src/lib/server.cc b/src/lib/server.cc
index 38f9834ff..bea75cff8 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -113,13 +113,13 @@ Server::process (shared_ptr<Socket> socket)
PixelFormat pixel_format = (PixelFormat) pixel_format_int;
Scaler const * scaler = Scaler::from_id (scaler_id);
- shared_ptr<Image> image (new AlignedImage (pixel_format, in_size));
+ shared_ptr<Image> image (new SimpleImage (pixel_format, in_size, true));
image->read_from_socket (socket);
shared_ptr<Subtitle> sub;
if (subtitle_size.width && subtitle_size.height) {
- shared_ptr<Image> subtitle_image (new AlignedImage (PIX_FMT_RGBA, subtitle_size));
+ shared_ptr<Image> subtitle_image (new SimpleImage (PIX_FMT_RGBA, subtitle_size, true));
subtitle_image->read_from_socket (socket);
sub.reset (new Subtitle (subtitle_position, subtitle_image));
}
diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc
index 8e1bfac22..39f8faa68 100644
--- a/src/lib/subtitle.cc
+++ b/src/lib/subtitle.cc
@@ -54,7 +54,7 @@ TimedSubtitle::TimedSubtitle (AVSubtitle const & sub, double c)
throw DecodeError ("non-bitmap subtitles not yet supported");
}
- shared_ptr<Image> image (new AlignedImage (PIX_FMT_RGBA, Size (rect->w, rect->h)));
+ shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGBA, Size (rect->w, rect->h), true));
/* Start of the first line in the subtitle */
uint8_t* sub_p = rect->pict.data[0];
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 081e04252..477c73c75 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -41,9 +41,10 @@ using boost::shared_ptr;
* @param o Options.
* @param req Job that must be completed before this job is run.
*/
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req)
+TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
: Job (f, req)
- , _opt (o)
+ , _decode_opt (od)
+ , _encode_opt (oe)
{
}
@@ -62,8 +63,8 @@ TranscodeJob::run ()
_film->log()->log ("Transcode job starting");
_film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay()));
- _encoder = encoder_factory (_film, _opt);
- Transcoder w (_film, _opt, this, _encoder);
+ _encoder = encoder_factory (_film, _encode_opt);
+ Transcoder w (_film, _decode_opt, this, _encoder);
w.go ();
set_progress (1);
set_state (FINISHED_OK);
@@ -116,7 +117,11 @@ TranscodeJob::remaining_time () const
return 0;
}
- /* We assume that dcp_length() is valid */
+ if (!_film->dcp_length()) {
+ return 0;
+ }
+
+ /* We assume that dcp_length() is valid, if it is set */
SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame();
return left / fps;
}
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
index 1decea070..97f655e15 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -25,6 +25,8 @@
#include "job.h"
class Encoder;
+class DecodeOptions;
+class EncodeOptions;
/** @class TranscodeJob
* @brief A job which transcodes from one format to another.
@@ -32,7 +34,7 @@ class Encoder;
class TranscodeJob : public Job
{
public:
- TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, boost::shared_ptr<Job> req);
+ TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> od, boost::shared_ptr<const EncodeOptions> oe, boost::shared_ptr<Job> req);
std::string name () const;
void run ();
@@ -42,6 +44,7 @@ protected:
int remaining_time () const;
private:
- boost::shared_ptr<const Options> _opt;
+ boost::shared_ptr<const DecodeOptions> _decode_opt;
+ boost::shared_ptr<const EncodeOptions> _encode_opt;
boost::shared_ptr<Encoder> _encoder;
};
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 537b9b664..87a1fb3f2 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -44,11 +44,11 @@ using boost::dynamic_pointer_cast;
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
- * @param o Options.
+ * @param o Decode options.
* @param j Job that we are running under, or 0.
* @param e Encoder to use.
*/
-Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
: _job (j)
, _encoder (e)
, _decoders (decoder_factory (f, o, j))
@@ -63,18 +63,20 @@ Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j,
}
/* Set up the decoder to use the film's set streams */
- _decoders.first->set_subtitle_stream (f->subtitle_stream ());
- _decoders.second->set_audio_stream (f->audio_stream ());
+ _decoders.video->set_subtitle_stream (f->subtitle_stream ());
+ if (_decoders.audio) {
+ _decoders.audio->set_audio_stream (f->audio_stream ());
+ }
if (_matcher) {
- _decoders.first->connect_video (_matcher);
+ _decoders.video->connect_video (_matcher);
_matcher->connect_video (_encoder);
} else {
- _decoders.first->connect_video (_encoder);
+ _decoders.video->connect_video (_encoder);
}
- if (_matcher && _delay_line) {
- _decoders.second->connect_audio (_delay_line);
+ if (_matcher && _delay_line && _decoders.audio) {
+ _decoders.audio->connect_audio (_delay_line);
_delay_line->connect_audio (_matcher);
_matcher->connect_audio (_gain);
_gain->connect_audio (_encoder);
@@ -93,12 +95,12 @@ Transcoder::go ()
while (1) {
if (!done[0]) {
- done[0] = _decoders.first->pass ();
- _decoders.first->set_progress ();
+ done[0] = _decoders.video->pass ();
+ _decoders.video->set_progress ();
}
- if (!done[1] && dynamic_pointer_cast<Decoder> (_decoders.second) != dynamic_pointer_cast<Decoder> (_decoders.first)) {
- done[1] = _decoders.second->pass ();
+ if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
+ done[1] = _decoders.audio->pass ();
} else {
done[1] = true;
}
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index e3ca2bb32..b50113742 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -24,6 +24,8 @@
* as a parameter to the constructor.
*/
+#include "decoder_factory.h"
+
class Film;
class Job;
class Encoder;
@@ -34,7 +36,8 @@ class Gain;
class VideoDecoder;
class AudioDecoder;
class DelayLine;
-class Options;
+class EncodeOptions;
+class DecodeOptions;
/** @class Transcoder
* @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
@@ -45,17 +48,26 @@ class Options;
class Transcoder
{
public:
- Transcoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j, boost::shared_ptr<Encoder> e);
+ Transcoder (
+ boost::shared_ptr<Film> f,
+ boost::shared_ptr<const DecodeOptions> o,
+ Job* j,
+ boost::shared_ptr<Encoder> e
+ );
void go ();
+ boost::shared_ptr<VideoDecoder> video_decoder () const {
+ return _decoders.video;
+ }
+
protected:
/** A Job that is running this Transcoder, or 0 */
Job* _job;
/** The encoder that we will use */
boost::shared_ptr<Encoder> _encoder;
/** The decoders that we will use */
- std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _decoders;
+ Decoders _decoders;
boost::shared_ptr<Matcher> _matcher;
boost::shared_ptr<DelayLine> _delay_line;
boost::shared_ptr<Gain> _gain;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index b69581eba..66eaea39e 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -576,7 +576,8 @@ Rect::intersection (Rect const & other) const
}
/** Round a number up to the nearest multiple of another number.
- * @param a Number to round.
+ * @param c Index.
+ * @param s Array of numbers to round, indexed by c.
* @param t Multiple to round to.
* @return Rounded number.
*/
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index 23a69f958..4c05d5fcd 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -28,33 +28,35 @@
using boost::shared_ptr;
using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
: Decoder (f, o, j)
, _video_frame (0)
+ , _last_source_frame (0)
{
}
/** Called by subclasses to tell the world that some video data is ready.
* We find a subtitle then emit it for listeners.
- * @param frame to decode; caller manages memory.
+ * @param frame to emit.
*/
void
-VideoDecoder::emit_video (shared_ptr<Image> image)
+VideoDecoder::emit_video (shared_ptr<Image> image, SourceFrame f)
{
shared_ptr<Subtitle> sub;
- if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) {
+ if (_timed_subtitle && _timed_subtitle->displayed_at (f / _film->frames_per_second())) {
sub = _timed_subtitle->subtitle ();
}
signal_video (image, sub);
+ _last_source_frame = f;
}
void
VideoDecoder::repeat_last_video ()
{
if (!_last_image) {
- _last_image.reset (new CompactImage (pixel_format(), native_size()));
+ _last_image.reset (new SimpleImage (pixel_format(), native_size(), false));
_last_image->make_black ();
}
@@ -77,7 +79,7 @@ VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
{
_timed_subtitle = s;
- if (_timed_subtitle && _opt->apply_crop) {
+ if (_timed_subtitle) {
Position const p = _timed_subtitle->subtitle()->position ();
_timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top));
}
@@ -92,7 +94,7 @@ VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
void
VideoDecoder::set_progress () const
{
- if (_job && _film->dcp_length()) {
+ if (_job && _film->length()) {
_job->set_progress (float (_video_frame) / _film->length().get());
}
}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index ea1899840..f682941d1 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -27,12 +27,14 @@
class VideoDecoder : public VideoSource, public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
/** @return video frames per second, or 0 if unknown */
virtual float frames_per_second () const = 0;
/** @return native size in pixels */
virtual Size native_size () const = 0;
+ /** @return length (in source video frames), according to our content's header */
+ virtual SourceFrame length () const = 0;
virtual int time_base_numerator () const = 0;
virtual int time_base_denominator () const = 0;
@@ -55,11 +57,15 @@ public:
return _subtitle_streams;
}
+ SourceFrame last_source_frame () const {
+ return _last_source_frame;
+ }
+
protected:
virtual PixelFormat pixel_format () const = 0;
- void emit_video (boost::shared_ptr<Image>);
+ void emit_video (boost::shared_ptr<Image>, SourceFrame);
void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
void repeat_last_video ();
@@ -72,7 +78,8 @@ private:
void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
SourceFrame _video_frame;
-
+ SourceFrame _last_source_frame;
+
boost::shared_ptr<TimedSubtitle> _timed_subtitle;
boost::shared_ptr<Image> _last_image;
diff --git a/src/lib/wscript b/src/lib/wscript
index c1786bb81..5d3fbb906 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -37,7 +37,6 @@ def build(bld):
gain.cc
image.cc
imagemagick_decoder.cc
- imagemagick_encoder.cc
j2k_still_encoder.cc
j2k_wav_encoder.cc
job.cc
diff --git a/src/tools/dvdomatic.cc b/src/tools/dvdomatic.cc
index 993c41563..d5d5bfc2f 100644
--- a/src/tools/dvdomatic.cc
+++ b/src/tools/dvdomatic.cc
@@ -226,7 +226,6 @@ public:
/* XXX: calling these here is a bit of a hack */
film_editor->setup_visibility ();
- film_viewer->setup_visibility ();
film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
if (film) {
diff --git a/src/tools/servomatictest.cc b/src/tools/servomatictest.cc
index d6804c981..41ec8075d 100644
--- a/src/tools/servomatictest.cc
+++ b/src/tools/servomatictest.cc
@@ -135,7 +135,6 @@ main (int argc, char* argv[])
shared_ptr<Options> opt (new Options ("fred", "jim", "sheila"));
opt->out_size = Size (1024, 1024);
- opt->apply_crop = false;
opt->decode_audio = false;
shared_ptr<Decoder> decoder = decoder_factory (film.state_copy(), opt, 0, &log_);
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index 2528f49db..da48c2645 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -114,6 +114,11 @@ FilmEditor::make_film_panel ()
_content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
_film_sizer->Add (_content, 1, wxEXPAND);
+ _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header"));
+ video_control (_trust_content_header);
+ _film_sizer->Add (_trust_content_header, 1);
+ _film_sizer->AddSpacer (0);
+
add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
_dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
_film_sizer->Add (_dcp_content_type);
@@ -134,12 +139,12 @@ FilmEditor::make_film_panel ()
{
video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames"));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- add_label_to_sizer (s, _film_panel, "Start");
+ video_control (add_label_to_sizer (s, _film_panel, "Start"));
_dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (_dcp_trim_start);
- add_label_to_sizer (s, _film_panel, "End");
+ s->Add (video_control (_dcp_trim_start));
+ video_control (add_label_to_sizer (s, _film_panel, "End"));
_dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (_dcp_trim_end);
+ s->Add (video_control (_dcp_trim_end));
_film_sizer->Add (s);
}
@@ -174,6 +179,7 @@ FilmEditor::connect_to_widgets ()
_edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
_format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
_content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
+ _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this);
_left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
_right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
_top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
@@ -263,7 +269,7 @@ FilmEditor::make_video_panel ()
_top_crop->SetRange (0, 1024);
_right_crop->SetRange (0, 1024);
_bottom_crop->SetRange (0, 1024);
- _still_duration->SetRange (0, 60 * 60);
+ _still_duration->SetRange (1, 60 * 60);
_dcp_trim_start->SetRange (0, 100);
_dcp_trim_end->SetRange (0, 100);
}
@@ -325,7 +331,7 @@ FilmEditor::make_audio_panel ()
};
for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]);
+ video_control (add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]));
_external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav"));
_audio_sizer->Add (video_control (_external_audio[i]), 1, wxEXPAND);
}
@@ -348,7 +354,7 @@ FilmEditor::make_subtitle_panel ()
_subtitle_sizer->Add (_with_subtitles, 1);
_subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
- _subtitle_sizer->Add (_subtitle_stream);
+ _subtitle_sizer->Add (video_control (_subtitle_stream));
video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset"));
_subtitle_offset = new wxSpinCtrl (_subtitle_panel);
@@ -427,6 +433,16 @@ FilmEditor::content_changed (wxCommandEvent &)
}
}
+void
+FilmEditor::trust_content_header_changed (wxCommandEvent &)
+{
+ if (!_film) {
+ return;
+ }
+
+ _film->set_trust_content_header (_trust_content_header->GetValue ());
+}
+
/** Called when the DCP A/B switch has been toggled */
void
FilmEditor::dcp_ab_toggled (wxCommandEvent &)
@@ -495,6 +511,9 @@ FilmEditor::film_changed (Film::Property p)
setup_subtitle_control_sensitivity ();
setup_streams ();
break;
+ case Film::TRUST_CONTENT_HEADER:
+ checked_set (_trust_content_header, _film->trust_content_header ());
+ break;
case Film::SUBTITLE_STREAMS:
setup_subtitle_control_sensitivity ();
setup_streams ();
@@ -569,8 +588,6 @@ FilmEditor::film_changed (Film::Property p)
checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
_dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
break;
- case Film::THUMBS:
- break;
case Film::DCP_AB:
checked_set (_dcp_ab, _film->dcp_ab ());
break;
@@ -694,6 +711,7 @@ FilmEditor::set_film (shared_ptr<Film> f)
film_changed (Film::NAME);
film_changed (Film::USE_DCI_NAME);
film_changed (Film::CONTENT);
+ film_changed (Film::TRUST_CONTENT_HEADER);
film_changed (Film::DCP_CONTENT_TYPE);
film_changed (Film::FORMAT);
film_changed (Film::CROP);
@@ -731,6 +749,7 @@ FilmEditor::set_things_sensitive (bool s)
_edit_dci_button->Enable (s);
_format->Enable (s);
_content->Enable (s);
+ _trust_content_header->Enable (s);
_left_crop->Enable (s);
_right_crop->Enable (s);
_top_crop->Enable (s);
diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h
index 428b994b8..7e75b4bf0 100644
--- a/src/wx/film_editor.h
+++ b/src/wx/film_editor.h
@@ -61,6 +61,7 @@ private:
void top_crop_changed (wxCommandEvent &);
void bottom_crop_changed (wxCommandEvent &);
void content_changed (wxCommandEvent &);
+ void trust_content_header_changed (wxCommandEvent &);
void format_changed (wxCommandEvent &);
void dcp_trim_start_changed (wxCommandEvent &);
void dcp_trim_end_changed (wxCommandEvent &);
@@ -118,6 +119,7 @@ private:
wxComboBox* _format;
/** The Film's content file */
wxFilePickerCtrl* _content;
+ wxCheckBox* _trust_content_header;
/** The Film's left crop */
wxSpinCtrl* _left_crop;
/** The Film's right crop */
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index a82132358..891d1671b 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -18,307 +18,343 @@
*/
/** @file src/film_viewer.cc
- * @brief A wx widget to view `thumbnails' of a Film.
+ * @brief A wx widget to view a preview of a Film.
*/
#include <iostream>
#include <iomanip>
+#include <wx/tglbtn.h>
#include "lib/film.h"
#include "lib/format.h"
#include "lib/util.h"
#include "lib/job_manager.h"
#include "lib/options.h"
#include "lib/subtitle.h"
+#include "lib/image.h"
+#include "lib/scaler.h"
+#include "lib/exceptions.h"
+#include "lib/examine_content_job.h"
#include "film_viewer.h"
#include "wx_util.h"
+#include "video_decoder.h"
using std::string;
using std::pair;
using std::max;
+using std::cout;
+using std::list;
using boost::shared_ptr;
-class ThumbPanel : public wxPanel
+FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
+ : wxPanel (p)
+ , _panel (new wxPanel (this))
+ , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
+ , _play_button (new wxToggleButton (this, wxID_ANY, wxT ("Play")))
+ , _out_width (0)
+ , _out_height (0)
+ , _panel_width (0)
+ , _panel_height (0)
{
-public:
- ThumbPanel (wxPanel* parent, shared_ptr<Film> film)
- : wxPanel (parent)
- , _film (film)
- , _index (0)
- , _frame_rebuild_needed (false)
- , _composition_needed (false)
- {}
-
- /** Handle a paint event */
- void paint_event (wxPaintEvent& ev)
- {
- if (!_film || _film->thumbs().size() == 0) {
- wxPaintDC dc (this);
- return;
- }
-
- if (_frame_rebuild_needed) {
- _image.reset (new wxImage (std_to_wx (_film->thumb_file (_index))));
+ _panel->SetDoubleBuffered (true);
+ _panel->SetBackgroundStyle (wxBG_STYLE_PAINT);
+
+ wxBoxSizer* v_sizer = new wxBoxSizer (wxVERTICAL);
+ SetSizer (v_sizer);
- _subtitle.reset ();
- pair<Position, string> s = _film->thumb_subtitle (_index);
- if (!s.second.empty ()) {
- _subtitle.reset (new SubtitleView (s.first, std_to_wx (s.second)));
- }
+ v_sizer->Add (_panel, 1, wxEXPAND);
- _frame_rebuild_needed = false;
- compose ();
- }
+ wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
+ h_sizer->Add (_play_button, 0, wxEXPAND);
+ h_sizer->Add (_slider, 1, wxEXPAND);
- if (_composition_needed) {
- compose ();
- }
+ v_sizer->Add (h_sizer, 0, wxEXPAND);
- wxPaintDC dc (this);
- if (_bitmap) {
- dc.DrawBitmap (*_bitmap, 0, 0, false);
- }
+ _panel->Bind (wxEVT_PAINT, &FilmViewer::paint_panel, this);
+ _panel->Bind (wxEVT_SIZE, &FilmViewer::panel_sized, this);
+ _slider->Bind (wxEVT_SCROLL_THUMBTRACK, &FilmViewer::slider_moved, this);
+ _slider->Bind (wxEVT_SCROLL_PAGEUP, &FilmViewer::slider_moved, this);
+ _slider->Bind (wxEVT_SCROLL_PAGEDOWN, &FilmViewer::slider_moved, this);
+ _play_button->Bind (wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &FilmViewer::play_clicked, this);
+ _timer.Bind (wxEVT_TIMER, &FilmViewer::timer, this);
- if (_film->with_subtitles() && _subtitle) {
- dc.DrawBitmap (*_subtitle->bitmap, _subtitle->transformed_area.x, _subtitle->transformed_area.y, true);
- }
- }
-
- /** Handle a size event */
- void size_event (wxSizeEvent &)
- {
- if (!_image) {
- return;
- }
+ set_film (_film);
- recompose ();
- }
+ JobManager::instance()->ActiveJobsChanged.connect (
+ bind (&FilmViewer::active_jobs_changed, this, _1)
+ );
+}
- /** @param n Thumbnail index */
- void set (int n)
+void
+FilmViewer::film_changed (Film::Property p)
+{
+ switch (p) {
+ case Film::FORMAT:
+ calculate_sizes ();
+ update_from_raw ();
+ break;
+ case Film::CONTENT:
{
- _index = n;
- _frame_rebuild_needed = true;
- Refresh ();
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
+ o->decode_audio = false;
+ o->decode_subtitles = true;
+ o->video_sync = false;
+ _decoders = decoder_factory (_film, o, 0);
+ _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2));
+ _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
+ _decoders.video->set_subtitle_stream (_film->subtitle_stream());
+ calculate_sizes ();
+ get_frame ();
+ _panel->Refresh ();
+ _slider->Show (_film->content_type() == VIDEO);
+ _play_button->Show (_film->content_type() == VIDEO);
+ break;
}
-
- void set_film (shared_ptr<Film> f)
- {
- _film = f;
- if (!_film) {
- clear ();
- _frame_rebuild_needed = true;
- Refresh ();
- } else {
- _frame_rebuild_needed = true;
- Refresh ();
- }
+ case Film::WITH_SUBTITLES:
+ case Film::SUBTITLE_OFFSET:
+ case Film::SUBTITLE_SCALE:
+ case Film::SCALER:
+ update_from_raw ();
+ break;
+ case Film::SUBTITLE_STREAM:
+ _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
+ break;
+ default:
+ break;
}
+}
- /** Clear our thumbnail image */
- void clear ()
- {
- _bitmap.reset ();
- _image.reset ();
- _subtitle.reset ();
+void
+FilmViewer::set_film (shared_ptr<Film> f)
+{
+ if (_film == f) {
+ return;
}
+
+ _film = f;
- void recompose ()
- {
- _composition_needed = true;
- Refresh ();
+ if (!_film) {
+ return;
}
- DECLARE_EVENT_TABLE ();
+ _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
-private:
-
- void compose ()
- {
- _composition_needed = false;
-
- if (!_film || !_image) {
- return;
- }
-
- /* Size of the view */
- int vw, vh;
- GetSize (&vw, &vh);
-
- Crop const fc = _film->crop ();
-
- /* Cropped rectangle */
- Rect cropped_area (
- fc.left,
- fc.top,
- _image->GetWidth() - (fc.left + fc.right),
- _image->GetHeight() - (fc.top + fc.bottom)
- );
+ film_changed (Film::CONTENT);
+ film_changed (Film::CROP);
+ film_changed (Film::FORMAT);
+ film_changed (Film::WITH_SUBTITLES);
+ film_changed (Film::SUBTITLE_OFFSET);
+ film_changed (Film::SUBTITLE_SCALE);
+ film_changed (Film::SUBTITLE_STREAM);
+}
- /* Target ratio */
- float const target = _film->format() ? _film->format()->ratio_as_float (_film) : 1.78;
+void
+FilmViewer::decoder_changed ()
+{
+ seek_and_update (_decoders.video->last_source_frame ());
+}
- _transformed_image = _image->GetSubImage (wxRect (cropped_area.x, cropped_area.y, cropped_area.width, cropped_area.height));
+void
+FilmViewer::timer (wxTimerEvent& ev)
+{
+ if (!_film) {
+ return;
+ }
+
+ _panel->Refresh ();
+ _panel->Update ();
- float x_scale = 1;
- float y_scale = 1;
+ get_frame ();
- if ((float (vw) / vh) > target) {
- /* view is longer (horizontally) than the ratio; fit height */
- _transformed_image.Rescale (vh * target, vh, wxIMAGE_QUALITY_HIGH);
- x_scale = vh * target / cropped_area.width;
- y_scale = float (vh) / cropped_area.height;
- } else {
- /* view is shorter (horizontally) than the ratio; fit width */
- _transformed_image.Rescale (vw, vw / target, wxIMAGE_QUALITY_HIGH);
- x_scale = float (vw) / cropped_area.width;
- y_scale = (vw / target) / cropped_area.height;
+ if (_film->length()) {
+ int const new_slider_position = 4096 * _decoders.video->last_source_frame() / _film->length().get();
+ if (new_slider_position != _slider->GetValue()) {
+ _slider->SetValue (new_slider_position);
}
+ }
+}
- _bitmap.reset (new wxBitmap (_transformed_image));
-
- if (_subtitle) {
- _subtitle->transformed_area = subtitle_transformed_area (
- x_scale, y_scale, _subtitle->base_area, _film->subtitle_offset(), _film->subtitle_scale()
- );
+void
+FilmViewer::paint_panel (wxPaintEvent& ev)
+{
+ wxPaintDC dc (_panel);
- _subtitle->transformed_image = _subtitle->base_image;
- _subtitle->transformed_image.Rescale (_subtitle->transformed_area.width, _subtitle->transformed_area.height, wxIMAGE_QUALITY_HIGH);
- _subtitle->transformed_area.x -= rint (_film->crop().left * x_scale);
- _subtitle->transformed_area.y -= rint (_film->crop().top * y_scale);
- _subtitle->bitmap.reset (new wxBitmap (_subtitle->transformed_image));
- }
+ if (!_display_frame || !_film) {
+ dc.Clear ();
+ return;
}
- shared_ptr<Film> _film;
- shared_ptr<wxImage> _image;
- wxImage _transformed_image;
- /** currently-displayed thumbnail index */
- int _index;
- shared_ptr<wxBitmap> _bitmap;
- bool _frame_rebuild_needed;
- bool _composition_needed;
+ wxImage frame (_out_width, _out_height, _display_frame->data()[0], true);
+ wxBitmap frame_bitmap (frame);
+ dc.DrawBitmap (frame_bitmap, 0, 0);
- struct SubtitleView
- {
- SubtitleView (Position p, wxString const & i)
- : base_image (i)
- {
- base_area.x = p.x;
- base_area.y = p.y;
- base_area.width = base_image.GetWidth ();
- base_area.height = base_image.GetHeight ();
- }
-
- Rect base_area;
- Rect transformed_area;
- wxImage base_image;
- wxImage transformed_image;
- shared_ptr<wxBitmap> bitmap;
- };
-
- shared_ptr<SubtitleView> _subtitle;
-};
+ if (_film->with_subtitles() && _display_sub) {
+ wxImage sub (_display_sub->size().width, _display_sub->size().height, _display_sub->data()[0], _display_sub->alpha(), true);
+ wxBitmap sub_bitmap (sub);
+ dc.DrawBitmap (sub_bitmap, _display_sub_position.x, _display_sub_position.y);
+ }
+}
-BEGIN_EVENT_TABLE (ThumbPanel, wxPanel)
-EVT_PAINT (ThumbPanel::paint_event)
-EVT_SIZE (ThumbPanel::size_event)
-END_EVENT_TABLE ()
-FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
- : wxPanel (p)
+void
+FilmViewer::slider_moved (wxCommandEvent& ev)
{
- _sizer = new wxBoxSizer (wxVERTICAL);
- SetSizer (_sizer);
+ if (!_film) {
+ return;
+ }
- _thumb_panel = new ThumbPanel (this, f);
- _sizer->Add (_thumb_panel, 1, wxEXPAND);
-
- int const m = max ((size_t) 1, f ? f->thumbs().size() - 1 : 0);
- _slider = new wxSlider (this, wxID_ANY, 0, 0, m);
- _sizer->Add (_slider, 0, wxEXPAND | wxLEFT | wxRIGHT);
- set_thumbnail (0);
-
- _slider->Connect (wxID_ANY, wxEVT_COMMAND_SLIDER_UPDATED, wxCommandEventHandler (FilmViewer::slider_changed), 0, this);
-
- set_film (_film);
+ if (_film->length()) {
+ seek_and_update (_slider->GetValue() * _film->length().get() / 4096);
+ }
}
void
-FilmViewer::set_thumbnail (int n)
+FilmViewer::seek_and_update (SourceFrame f)
{
- if (_film == 0 || int (_film->thumbs().size()) <= n) {
+ if (_decoders.video->seek (f)) {
+ cout << "could not s&u to " << f << "\n";
return;
}
- _thumb_panel->set (n);
+ get_frame ();
+ _panel->Refresh ();
+ _panel->Update ();
}
void
-FilmViewer::slider_changed (wxCommandEvent &)
+FilmViewer::panel_sized (wxSizeEvent& ev)
{
- set_thumbnail (_slider->GetValue ());
+ _panel_width = ev.GetSize().GetWidth();
+ _panel_height = ev.GetSize().GetHeight();
+ calculate_sizes ();
+ update_from_raw ();
}
void
-FilmViewer::film_changed (Film::Property p)
+FilmViewer::update_from_raw ()
{
- ensure_ui_thread ();
-
- switch (p) {
- case Film::THUMBS:
- if (_film && _film->thumbs().size() > 1) {
- _slider->SetRange (0, _film->thumbs().size() - 1);
- } else {
- _thumb_panel->clear ();
- _slider->SetRange (0, 1);
- }
-
- _slider->SetValue (0);
- set_thumbnail (0);
- break;
- case Film::CONTENT:
- setup_visibility ();
- break;
- case Film::CROP:
- case Film::FORMAT:
- case Film::WITH_SUBTITLES:
- case Film::SUBTITLE_OFFSET:
- case Film::SUBTITLE_SCALE:
- _thumb_panel->recompose ();
- break;
- default:
- break;
+ if (!_raw_frame) {
+ return;
}
+
+ raw_to_display ();
+
+ _panel->Refresh ();
+ _panel->Update ();
}
void
-FilmViewer::set_film (shared_ptr<Film> f)
+FilmViewer::raw_to_display ()
{
- if (_film == f) {
+ if (!_raw_frame || !_out_width || !_out_height || !_film) {
return;
}
-
- _film = f;
- _thumb_panel->set_film (_film);
+ /* Get a compacted image as we have to feed it to wxWidgets */
+ _display_frame = _raw_frame->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, _film->scaler(), false);
+
+ if (_raw_sub) {
+ Rect tx = subtitle_transformed_area (
+ float (_out_width) / _film->size().width,
+ float (_out_height) / _film->size().height,
+ _raw_sub->area(), _film->subtitle_offset(), _film->subtitle_scale()
+ );
+
+ _display_sub.reset (new RGBPlusAlphaImage (_raw_sub->image()->scale (tx.size(), _film->scaler(), false)));
+ _display_sub_position = tx.position();
+ } else {
+ _display_sub.reset ();
+ }
+}
+
+void
+FilmViewer::calculate_sizes ()
+{
if (!_film) {
return;
}
+
+ float const panel_ratio = static_cast<float> (_panel_width) / _panel_height;
+ float const film_ratio = _film->format() ? _film->format()->ratio_as_float(_film) : 1.78;
+ if (panel_ratio < film_ratio) {
+ /* panel is less widscreen than the film; clamp width */
+ _out_width = _panel_width;
+ _out_height = _out_width / film_ratio;
+ } else {
+ /* panel is more widescreen than the film; clamp heignt */
+ _out_height = _panel_height;
+ _out_width = _out_height * film_ratio;
+ }
+}
- _film->Changed.connect (bind (&FilmViewer::film_changed, this, _1));
- film_changed (Film::CROP);
- film_changed (Film::THUMBS);
- setup_visibility ();
+void
+FilmViewer::play_clicked (wxCommandEvent &)
+{
+ check_play_state ();
}
void
-FilmViewer::setup_visibility ()
+FilmViewer::check_play_state ()
{
if (!_film) {
return;
}
+
+ if (_play_button->GetValue()) {
+ _timer.Start (1000 / _film->frames_per_second());
+ } else {
+ _timer.Stop ();
+ }
+}
- ContentType const c = _film->content_type ();
- _slider->Show (c == VIDEO);
+void
+FilmViewer::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
+{
+ _raw_frame = image;
+ _raw_sub = sub;
+
+ raw_to_display ();
+}
+
+void
+FilmViewer::get_frame ()
+{
+ /* Clear our raw frame in case we don't get a new one */
+ _raw_frame.reset ();
+
+ try {
+ shared_ptr<Image> last = _display_frame;
+ while (last == _display_frame) {
+ if (_decoders.video->pass ()) {
+ /* We didn't get a frame before the decoder gave up,
+ so clear our display frame.
+ */
+ _display_frame.reset ();
+ break;
+ }
+ }
+ } catch (DecodeError& e) {
+ error_dialog (this, String::compose ("Could not decode video for view (%1)", e.what()));
+ }
}
+
+void
+FilmViewer::active_jobs_changed (bool a)
+{
+ if (a) {
+ list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+ list<shared_ptr<Job> >::iterator i = jobs.begin ();
+ while (i != jobs.end() && boost::dynamic_pointer_cast<ExamineContentJob> (*i) == 0) {
+ ++i;
+ }
+
+ if (i == jobs.end() || (*i)->finished()) {
+ /* no examine content job running, so we're ok to use the viewer */
+ a = false;
+ }
+ }
+
+ _slider->Enable (!a);
+ _play_button->Enable (!a);
+}
+
diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h
index 95bdf099d..5a791a8e2 100644
--- a/src/wx/film_viewer.h
+++ b/src/wx/film_viewer.h
@@ -23,11 +23,16 @@
#include <wx/wx.h>
#include "lib/film.h"
+#include "lib/decoder_factory.h"
-class ThumbPanel;
+class wxToggleButton;
+class FFmpegPlayer;
+class Image;
+class RGBPlusAlphaImage;
+class Subtitle;
/** @class FilmViewer
- * @brief A wx widget to view `thumbnails' of a Film.
+ * @brief A wx widget to view a preview of a Film.
*/
class FilmViewer : public wxPanel
{
@@ -35,15 +40,40 @@ public:
FilmViewer (boost::shared_ptr<Film>, wxWindow *);
void set_film (boost::shared_ptr<Film>);
- void setup_visibility ();
private:
- void slider_changed (wxCommandEvent &);
- void set_thumbnail (int);
void film_changed (Film::Property);
+ void paint_panel (wxPaintEvent &);
+ void panel_sized (wxSizeEvent &);
+ void slider_moved (wxCommandEvent &);
+ void play_clicked (wxCommandEvent &);
+ void timer (wxTimerEvent &);
+ void process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
+ void calculate_sizes ();
+ void check_play_state ();
+ void update_from_raw ();
+ void decoder_changed ();
+ void seek_and_update (SourceFrame);
+ void raw_to_display ();
+ void get_frame ();
+ void active_jobs_changed (bool);
boost::shared_ptr<Film> _film;
- wxBoxSizer* _sizer;
- ThumbPanel* _thumb_panel;
+
+ wxPanel* _panel;
wxSlider* _slider;
+ wxToggleButton* _play_button;
+ wxTimer _timer;
+
+ Decoders _decoders;
+ boost::shared_ptr<Image> _raw_frame;
+ boost::shared_ptr<Subtitle> _raw_sub;
+ boost::shared_ptr<Image> _display_frame;
+ boost::shared_ptr<RGBPlusAlphaImage> _display_sub;
+ Position _display_sub_position;
+
+ int _out_width;
+ int _out_height;
+ int _panel_width;
+ int _panel_height;
};
diff --git a/test/metadata.ref b/test/metadata.ref
index 3f129c6e2..ab5e01eb0 100644
--- a/test/metadata.ref
+++ b/test/metadata.ref
@@ -2,6 +2,7 @@ version 1
name fred
use_dci_name 1
content
+trust_content_header 1
dcp_content_type Short
format 185
left_crop 1
diff --git a/test/test.cc b/test/test.cc
index e2f9f41ee..d2c589951 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -307,10 +307,6 @@ BOOST_AUTO_TEST_CASE (paths_test)
{
shared_ptr<Film> f = new_test_film ("paths_test");
f->set_directory ("build/test/a/b/c/d/e");
- vector<SourceFrame> thumbs;
- thumbs.push_back (42);
- f->set_thumbs (thumbs);
- BOOST_CHECK_EQUAL (f->thumb_file (0), "build/test/a/b/c/d/e/thumbs/00000042.png");
f->_content = "/foo/bar/baz";
BOOST_CHECK_EQUAL (f->content_path(), "/foo/bar/baz");
@@ -331,7 +327,7 @@ do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* descriptio
BOOST_AUTO_TEST_CASE (client_server_test)
{
- shared_ptr<Image> image (new CompactImage (PIX_FMT_RGB24, Size (1998, 1080)));
+ shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, Size (1998, 1080), false));
uint8_t* p = image->data()[0];
for (int y = 0; y < 1080; ++y) {
@@ -342,7 +338,7 @@ BOOST_AUTO_TEST_CASE (client_server_test)
}
}
- shared_ptr<Image> sub_image (new CompactImage (PIX_FMT_RGBA, Size (100, 200)));
+ shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, Size (100, 200), false));
p = sub_image->data()[0];
for (int y = 0; y < 200; ++y) {
for (int x = 0; x < 100; ++x) {
@@ -535,3 +531,101 @@ BOOST_AUTO_TEST_CASE (job_manager_test)
BOOST_CHECK_EQUAL (b->running(), false);
}
+BOOST_AUTO_TEST_CASE (compact_image_test)
+{
+ SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, Size (50, 50), false);
+ BOOST_CHECK_EQUAL (s->components(), 1);
+ BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3);
+ BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3);
+ BOOST_CHECK (s->data()[0]);
+ BOOST_CHECK (!s->data()[1]);
+ BOOST_CHECK (!s->data()[2]);
+ BOOST_CHECK (!s->data()[3]);
+
+ /* copy constructor */
+ SimpleImage* t = new SimpleImage (*s);
+ BOOST_CHECK_EQUAL (t->components(), 1);
+ BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3);
+ BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3);
+ BOOST_CHECK (t->data()[0]);
+ BOOST_CHECK (!t->data()[1]);
+ BOOST_CHECK (!t->data()[2]);
+ BOOST_CHECK (!t->data()[3]);
+ BOOST_CHECK (t->data() != s->data());
+ BOOST_CHECK (t->data()[0] != s->data()[0]);
+ BOOST_CHECK (t->line_size() != s->line_size());
+ BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK (t->stride() != s->stride());
+ BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+
+ /* assignment operator */
+ SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, Size (150, 150), true);
+ *u = *s;
+ BOOST_CHECK_EQUAL (u->components(), 1);
+ BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3);
+ BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3);
+ BOOST_CHECK (u->data()[0]);
+ BOOST_CHECK (!u->data()[1]);
+ BOOST_CHECK (!u->data()[2]);
+ BOOST_CHECK (!u->data()[3]);
+ BOOST_CHECK (u->data() != s->data());
+ BOOST_CHECK (u->data()[0] != s->data()[0]);
+ BOOST_CHECK (u->line_size() != s->line_size());
+ BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK (u->stride() != s->stride());
+ BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+
+ delete s;
+ delete t;
+ delete u;
+}
+
+BOOST_AUTO_TEST_CASE (aligned_image_test)
+{
+ SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, Size (50, 50), true);
+ BOOST_CHECK_EQUAL (s->components(), 1);
+ /* 160 is 150 aligned to the nearest 32 bytes */
+ BOOST_CHECK_EQUAL (s->stride()[0], 160);
+ BOOST_CHECK_EQUAL (s->line_size()[0], 150);
+ BOOST_CHECK (s->data()[0]);
+ BOOST_CHECK (!s->data()[1]);
+ BOOST_CHECK (!s->data()[2]);
+ BOOST_CHECK (!s->data()[3]);
+
+ /* copy constructor */
+ SimpleImage* t = new SimpleImage (*s);
+ BOOST_CHECK_EQUAL (t->components(), 1);
+ BOOST_CHECK_EQUAL (t->stride()[0], 160);
+ BOOST_CHECK_EQUAL (t->line_size()[0], 150);
+ BOOST_CHECK (t->data()[0]);
+ BOOST_CHECK (!t->data()[1]);
+ BOOST_CHECK (!t->data()[2]);
+ BOOST_CHECK (!t->data()[3]);
+ BOOST_CHECK (t->data() != s->data());
+ BOOST_CHECK (t->data()[0] != s->data()[0]);
+ BOOST_CHECK (t->line_size() != s->line_size());
+ BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK (t->stride() != s->stride());
+ BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+
+ /* assignment operator */
+ SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, Size (150, 150), false);
+ *u = *s;
+ BOOST_CHECK_EQUAL (u->components(), 1);
+ BOOST_CHECK_EQUAL (u->stride()[0], 160);
+ BOOST_CHECK_EQUAL (u->line_size()[0], 150);
+ BOOST_CHECK (u->data()[0]);
+ BOOST_CHECK (!u->data()[1]);
+ BOOST_CHECK (!u->data()[2]);
+ BOOST_CHECK (!u->data()[3]);
+ BOOST_CHECK (u->data() != s->data());
+ BOOST_CHECK (u->data()[0] != s->data()[0]);
+ BOOST_CHECK (u->line_size() != s->line_size());
+ BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+ BOOST_CHECK (u->stride() != s->stride());
+ BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+
+ delete s;
+ delete t;
+ delete u;
+}
diff --git a/wscript b/wscript
index ad46d8188..9bd8d2d50 100644
--- a/wscript
+++ b/wscript
@@ -22,7 +22,7 @@ def configure(conf):
conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing', '-Wall', '-Wno-attributes'])
if conf.options.target_windows:
- conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H'])
+ conf.env.append_value('CXXFLAGS', ['-DDVDOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE'])
wxrc = os.popen('wx-config --rescomp').read().split()[1:]
print wxrc
conf.env.append_value('WINRCFLAGS', wxrc)