summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ab_transcode_job.cc9
-rw-r--r--src/lib/ab_transcode_job.h9
-rw-r--r--src/lib/ab_transcoder.cc2
-rw-r--r--src/lib/ab_transcoder.h3
-rw-r--r--src/lib/audio_decoder.cc2
-rw-r--r--src/lib/audio_decoder.h2
-rw-r--r--src/lib/check_hashes_job.cc123
-rw-r--r--src/lib/check_hashes_job.h43
-rw-r--r--src/lib/config.cc4
-rw-r--r--src/lib/config.h9
-rw-r--r--src/lib/dcp_video_frame.cc80
-rw-r--r--src/lib/dcp_video_frame.h40
-rw-r--r--src/lib/decoder.cc4
-rw-r--r--src/lib/decoder.h8
-rw-r--r--src/lib/decoder_factory.cc2
-rw-r--r--src/lib/decoder_factory.h5
-rw-r--r--src/lib/delay_line.cc9
-rw-r--r--src/lib/delay_line.h1
-rw-r--r--src/lib/encoder.cc243
-rw-r--r--src/lib/encoder.h56
-rw-r--r--src/lib/examine_content_job.cc7
-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.cc13
-rw-r--r--src/lib/ffmpeg_decoder.h2
-rw-r--r--src/lib/film.cc221
-rw-r--r--src/lib/film.h39
-rw-r--r--src/lib/filter.cc72
-rw-r--r--src/lib/filter.h8
-rw-r--r--src/lib/filter_graph.cc3
-rw-r--r--src/lib/format.cc1
-rw-r--r--src/lib/image.cc56
-rw-r--r--src/lib/image.h6
-rw-r--r--src/lib/imagemagick_decoder.cc7
-rw-r--r--src/lib/imagemagick_decoder.h2
-rw-r--r--src/lib/log.cc2
-rw-r--r--src/lib/matcher.cc2
-rw-r--r--src/lib/options.h99
-rw-r--r--src/lib/scp_dcp_job.cc5
-rw-r--r--src/lib/server.cc1
-rw-r--r--src/lib/subtitle.cc1
-rw-r--r--src/lib/transcode_job.cc29
-rw-r--r--src/lib/transcode_job.h8
-rw-r--r--src/lib/transcoder.cc2
-rw-r--r--src/lib/transcoder.h4
-rw-r--r--src/lib/util.cc115
-rw-r--r--src/lib/util.h36
-rw-r--r--src/lib/video_decoder.cc4
-rw-r--r--src/lib/video_decoder.h2
-rw-r--r--src/lib/writer.cc361
-rw-r--r--src/lib/writer.h91
-rw-r--r--src/lib/wscript3
52 files changed, 1078 insertions, 782 deletions
diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc
index a6233c185..0efd277bb 100644
--- a/src/lib/ab_transcode_job.cc
+++ b/src/lib/ab_transcode_job.cc
@@ -30,12 +30,11 @@ using std::string;
using boost::shared_ptr;
/** @param f Film to compare.
- * @param o Options.
+ * @param o Decode options.
*/
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
+ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req)
: Job (f, req)
- , _decode_opt (od)
- , _encode_opt (oe)
+ , _decode_opt (o)
{
_film_b.reset (new Film (*_film));
_film_b->set_scaler (Config::instance()->reference_scaler ());
@@ -53,7 +52,7 @@ ABTranscodeJob::run ()
{
try {
/* _film_b is the one with reference filters */
- ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film, _encode_opt)));
+ ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film)));
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 86a2a81b8..983842038 100644
--- a/src/lib/ab_transcode_job.h
+++ b/src/lib/ab_transcode_job.h
@@ -23,10 +23,9 @@
#include <boost/shared_ptr.hpp>
#include "job.h"
+#include "options.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.
@@ -40,8 +39,7 @@ class ABTranscodeJob : public Job
public:
ABTranscodeJob (
boost::shared_ptr<Film> f,
- boost::shared_ptr<const DecodeOptions> od,
- boost::shared_ptr<const EncodeOptions> oe,
+ DecodeOptions o,
boost::shared_ptr<Job> req
);
@@ -49,8 +47,7 @@ public:
void run ();
private:
- boost::shared_ptr<const DecodeOptions> _decode_opt;
- boost::shared_ptr<const EncodeOptions> _encode_opt;
+ DecodeOptions _decode_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 53af43b5d..fc4fb8daa 100644
--- a/src/lib/ab_transcoder.cc
+++ b/src/lib/ab_transcoder.cc
@@ -49,7 +49,7 @@ using boost::shared_ptr;
*/
ABTranscoder::ABTranscoder (
- shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
+ shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
: _film_a (a)
, _film_b (b)
, _job (j)
diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h
index 7bfcb393c..58a08af04 100644
--- a/src/lib/ab_transcoder.h
+++ b/src/lib/ab_transcoder.h
@@ -31,7 +31,6 @@ class Job;
class Encoder;
class VideoDecoder;
class AudioDecoder;
-class DecodeOptions;
class Image;
class Log;
class Subtitle;
@@ -51,7 +50,7 @@ public:
ABTranscoder (
boost::shared_ptr<Film> a,
boost::shared_ptr<Film> b,
- boost::shared_ptr<const DecodeOptions> o,
+ DecodeOptions o,
Job* j,
boost::shared_ptr<Encoder> e
);
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index 9d8de971c..a038dd2bb 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 DecodeOptions> o, Job* j)
+AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
: Decoder (f, o, j)
{
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index 013a6327f..3bf585f4d 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 DecodeOptions>, Job *);
+ AudioDecoder (boost::shared_ptr<Film>, 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
deleted file mode 100644
index 701584c74..000000000
--- a/src/lib/check_hashes_job.cc
+++ /dev/null
@@ -1,123 +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.
-
-*/
-
-#include <fstream>
-#include <boost/lexical_cast.hpp>
-#include <boost/filesystem.hpp>
-#include "check_hashes_job.h"
-#include "options.h"
-#include "log.h"
-#include "job_manager.h"
-#include "ab_transcode_job.h"
-#include "transcode_job.h"
-#include "film.h"
-#include "exceptions.h"
-
-using std::string;
-using std::stringstream;
-using std::ifstream;
-using boost::shared_ptr;
-
-CheckHashesJob::CheckHashesJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
- : Job (f, req)
- , _decode_opt (od)
- , _encode_opt (oe)
- , _bad (0)
-{
-
-}
-
-string
-CheckHashesJob::name () const
-{
- return String::compose ("Check hashes of %1", _film->name());
-}
-
-void
-CheckHashesJob::run ()
-{
- _bad = 0;
-
- if (!_film->dcp_length()) {
- throw EncodeError ("cannot check hashes of a DCP with unknown length");
- }
-
- SourceFrame const N = _film->dcp_trim_start() + _film->dcp_length().get();
- 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 = _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));
- boost::filesystem::remove (hash_file);
- ++_bad;
- } else if (!boost::filesystem::exists (hash_file)) {
- _film->log()->log (String::compose ("Frame %1 has a missing hash file.", i));
- boost::filesystem::remove (j2k_file);
- ++_bad;
- } else {
- ifstream ref (hash_file.c_str ());
- string hash;
- ref >> hash;
- if (hash != md5_digest (j2k_file)) {
- _film->log()->log (String::compose ("Frame %1 has wrong hash; deleting.", i));
- boost::filesystem::remove (j2k_file);
- boost::filesystem::remove (hash_file);
- ++_bad;
- }
- }
-
- set_progress (float (i) / N);
- }
-
- if (_bad) {
- shared_ptr<Job> tc;
-
- if (_film->dcp_ab()) {
- tc.reset (new ABTranscodeJob (_film, _decode_opt, _encode_opt, shared_from_this()));
- } else {
- 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, _decode_opt, _encode_opt, tc)));
- }
-
- set_progress (1);
- set_state (FINISHED_OK);
-}
-
-string
-CheckHashesJob::status () const
-{
- stringstream s;
- s << Job::status ();
- if (overall_progress() > 0) {
- if (_bad == 0) {
- s << "; no bad frames found";
- } else if (_bad == 1) {
- s << "; 1 bad frame found";
- } else {
- s << "; " << _bad << " bad frames found";
- }
- }
- return s.str ();
-}
diff --git a/src/lib/check_hashes_job.h b/src/lib/check_hashes_job.h
deleted file mode 100644
index c41af9d3f..000000000
--- a/src/lib/check_hashes_job.h
+++ /dev/null
@@ -1,43 +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.
-
-*/
-
-#include "job.h"
-
-class DecodeOptions;
-class EncodeOptions;
-
-class CheckHashesJob : public Job
-{
-public:
- 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 DecodeOptions> _decode_opt;
- boost::shared_ptr<const EncodeOptions> _encode_opt;
- int _bad;
-};
diff --git a/src/lib/config.cc b/src/lib/config.cc
index c4659eecf..c165859b0 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -44,6 +44,10 @@ Config::Config ()
, _tms_path (".")
, _sound_processor (SoundProcessor::from_id ("dolby_cp750"))
{
+ _allowed_dcp_frame_rates.push_back (24);
+ _allowed_dcp_frame_rates.push_back (25);
+ _allowed_dcp_frame_rates.push_back (30);
+
ifstream f (file().c_str ());
string line;
while (getline (f, line)) {
diff --git a/src/lib/config.h b/src/lib/config.h
index 98cbf67e5..fed297ad0 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -95,6 +95,10 @@ public:
return _sound_processor;
}
+ std::list<int> allowed_dcp_frame_rates () const {
+ return _allowed_dcp_frame_rates;
+ }
+
DCIMetadata default_dci_metadata () const {
return _default_dci_metadata;
}
@@ -146,6 +150,10 @@ public:
_tms_password = p;
}
+ void set_allowed_dcp_frame_rates (std::list<int> const & r) {
+ _allowed_dcp_frame_rates = r;
+ }
+
void set_default_dci_metadata (DCIMetadata d) {
_default_dci_metadata = d;
}
@@ -181,6 +189,7 @@ private:
std::string _tms_password;
/** Our sound processor */
SoundProcessor const * _sound_processor;
+ std::list<int> _allowed_dcp_frame_rates;
/** Default DCI metadata for newly-created Films */
DCIMetadata _default_dci_metadata;
diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc
index 921a1876b..4f3fda44a 100644
--- a/src/lib/dcp_video_frame.cc
+++ b/src/lib/dcp_video_frame.cc
@@ -59,15 +59,17 @@
using std::string;
using std::stringstream;
using std::ofstream;
+using std::cout;
using boost::shared_ptr;
+using libdcp::Size;
/** Construct a DCP video frame.
* @param input Input image.
* @param out Required size of output, in pixels (including any padding).
* @param s Scaler to use.
* @param p Number of pixels of padding either side of the image.
- * @param f Index of the frame within the Film.
- * @param fps Frames per second of the Film.
+ * @param f Index of the frame within the DCP's intrinsic duration.
+ * @param fps Frames per second of the Film's source.
* @param pp FFmpeg post-processing string to use.
* @param clut Colour look-up table to use (see Config::colour_lut_index ())
* @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
@@ -75,8 +77,8 @@ using boost::shared_ptr;
*/
DCPVideoFrame::DCPVideoFrame (
shared_ptr<const Image> yuv, shared_ptr<Subtitle> sub,
- libdcp::Size out, int p, int subtitle_offset, float subtitle_scale,
- Scaler const * s, SourceFrame f, float fps, string pp, int clut, int bw, Log* l
+ Size out, int p, int subtitle_offset, float subtitle_scale,
+ Scaler const * s, int f, float fps, string pp, int clut, int bw, Log* l
)
: _input (yuv)
, _subtitle (sub)
@@ -86,7 +88,7 @@ DCPVideoFrame::DCPVideoFrame (
, _subtitle_scale (subtitle_scale)
, _scaler (s)
, _frame (f)
- , _frames_per_second (dcp_frame_rate(fps).frames_per_second)
+ , _frames_per_second (DCPFrameRate(fps).frames_per_second)
, _post_process (pp)
, _colour_lut (clut)
, _j2k_bandwidth (bw)
@@ -371,34 +373,63 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
return e;
}
+EncodedData::EncodedData (int s)
+ : _data (new uint8_t[s])
+ , _size (s)
+{
+
+}
+
+EncodedData::EncodedData (string file)
+{
+ _size = boost::filesystem::file_size (file);
+ _data = new uint8_t[_size];
+
+ FILE* f = fopen (file.c_str(), "rb");
+ if (!f) {
+ throw FileError ("could not open file for reading", file);
+ }
+
+ fread (_data, 1, _size, f);
+ fclose (f);
+}
+
+
+EncodedData::~EncodedData ()
+{
+ delete[] _data;
+}
+
/** Write this data to a J2K file.
- * @param opt Options.
- * @param frame Frame index.
+ * @param Film Film.
+ * @param frame DCP frame index.
*/
void
-EncodedData::write (shared_ptr<const EncodeOptions> opt, SourceFrame frame)
+EncodedData::write (shared_ptr<const Film> film, int frame) const
{
- string const tmp_j2k = opt->frame_out_path (frame, true);
+ string const tmp_j2c = film->j2c_path (frame, true);
- FILE* f = fopen (tmp_j2k.c_str (), "wb");
+ FILE* f = fopen (tmp_j2c.c_str (), "wb");
if (!f) {
- throw WriteFileError (tmp_j2k, errno);
+ throw WriteFileError (tmp_j2c, errno);
}
fwrite (_data, 1, _size, f);
fclose (f);
- string const real_j2k = opt->frame_out_path (frame, false);
+ string const real_j2c = film->j2c_path (frame, false);
/* Rename the file from foo.j2c.tmp to foo.j2c now that it is complete */
- boost::filesystem::rename (tmp_j2k, real_j2k);
+ boost::filesystem::rename (tmp_j2c, real_j2c);
+}
- /* Write a file containing the hash */
- string const hash = opt->hash_out_path (frame, false);
- ofstream h (hash.c_str());
- h << md5_digest (_data, _size) << "\n";
- h.close ();
+void
+EncodedData::write_info (shared_ptr<const Film> film, int frame, libdcp::FrameInfo fin) const
+{
+ string const info = film->info_path (frame);
+ ofstream h (info.c_str());
+ fin.write (h);
}
/** Send this data to a socket.
@@ -413,14 +444,15 @@ EncodedData::send (shared_ptr<Socket> socket)
socket->write (_data, _size, 30);
}
-/** @param s Size of data in bytes */
-RemotelyEncodedData::RemotelyEncodedData (int s)
- : EncodedData (new uint8_t[s], s)
+LocallyEncodedData::LocallyEncodedData (uint8_t* d, int s)
+ : EncodedData (s)
{
-
+ memcpy (_data, d, s);
}
-RemotelyEncodedData::~RemotelyEncodedData ()
+/** @param s Size of data in bytes */
+RemotelyEncodedData::RemotelyEncodedData (int s)
+ : EncodedData (s)
{
- delete[] _data;
+
}
diff --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h
index c0eff3f35..be8a559b2 100644
--- a/src/lib/dcp_video_frame.h
+++ b/src/lib/dcp_video_frame.h
@@ -19,6 +19,7 @@
*/
#include <openjpeg.h>
+#include <libdcp/picture_asset.h>
#include "util.h"
/** @file src/dcp_video_frame.h
@@ -26,7 +27,7 @@
*/
class FilmState;
-class EncodeOptions;
+class Film;
class ServerDescription;
class Scaler;
class Image;
@@ -39,18 +40,16 @@ class Subtitle;
class EncodedData
{
public:
- /** @param d Data (will not be freed by this class, but may be by subclasses)
- * @param s libdcp::Size of data, in bytes.
- */
- EncodedData (uint8_t* d, int s)
- : _data (d)
- , _size (s)
- {}
+ /** @param s Size of data, in bytes */
+ EncodedData (int s);
+
+ EncodedData (std::string f);
- virtual ~EncodedData () {}
+ virtual ~EncodedData ();
void send (boost::shared_ptr<Socket> socket);
- void write (boost::shared_ptr<const EncodeOptions>, SourceFrame);
+ void write (boost::shared_ptr<const Film>, int) const;
+ void write_info (boost::shared_ptr<const Film>, int, libdcp::FrameInfo) const;
/** @return data */
uint8_t* data () const {
@@ -65,6 +64,10 @@ public:
protected:
uint8_t* _data; ///< data
int _size; ///< data size in bytes
+
+private:
+ /* No copy construction */
+ EncodedData (EncodedData const &);
};
/** @class LocallyEncodedData
@@ -75,12 +78,10 @@ protected:
class LocallyEncodedData : public EncodedData
{
public:
- /** @param d Data (which will not be freed by this class)
- * @param s libdcp::Size of data, in bytes.
+ /** @param d Data (which will be copied by this class)
+ * @param s Size of data, in bytes.
*/
- LocallyEncodedData (uint8_t* d, int s)
- : EncodedData (d, s)
- {}
+ LocallyEncodedData (uint8_t* d, int s);
};
/** @class RemotelyEncodedData
@@ -91,7 +92,6 @@ class RemotelyEncodedData : public EncodedData
{
public:
RemotelyEncodedData (int s);
- ~RemotelyEncodedData ();
};
/** @class DCPVideoFrame
@@ -108,7 +108,7 @@ class DCPVideoFrame
public:
DCPVideoFrame (
boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size,
- int, int, float, Scaler const *, SourceFrame, float, std::string, int, int, Log *
+ int, int, float, Scaler const *, int, float, std::string, int, int, Log *
);
virtual ~DCPVideoFrame ();
@@ -116,7 +116,7 @@ public:
boost::shared_ptr<EncodedData> encode_locally ();
boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
- SourceFrame frame () const {
+ int frame () const {
return _frame;
}
@@ -125,12 +125,12 @@ private:
boost::shared_ptr<const Image> _input; ///< the input image
boost::shared_ptr<Subtitle> _subtitle; ///< any subtitle that should be on the image
- libdcp::Size _out_size; ///< the required size of the output, in pixels
+ libdcp::Size _out_size; ///< the required size of the output, in pixels
int _padding;
int _subtitle_offset;
float _subtitle_scale;
Scaler const * _scaler; ///< scaler to use
- SourceFrame _frame; ///< frame index within the Film's source
+ int _frame; ///< frame index within the DCP's intrinsic duration
int _frames_per_second; ///< Frames per second that we will use for the DCP (rounded)
std::string _post_process; ///< FFmpeg post-processing string to use
int _colour_lut; ///< Colour look-up table to use
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 7066b488e..fd0abee41 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -46,10 +46,10 @@ using boost::shared_ptr;
using boost::optional;
/** @param f Film.
- * @param o Options.
+ * @param o Decode options.
* @param j Job that we are running within, or 0
*/
-Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
+Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o, Job* j)
: _film (f)
, _opt (o)
, _job (j)
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index 3908afa2f..cc4c87373 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -34,9 +34,9 @@
#include "video_source.h"
#include "audio_source.h"
#include "film.h"
+#include "options.h"
class Job;
-class DecodeOptions;
class Image;
class Log;
class DelayLine;
@@ -54,7 +54,7 @@ class FilterGraph;
class Decoder
{
public:
- Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+ Decoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
virtual ~Decoder () {}
virtual bool pass () = 0;
@@ -66,8 +66,8 @@ public:
protected:
/** our Film */
boost::shared_ptr<Film> _film;
- /** our options */
- boost::shared_ptr<const DecodeOptions> _opt;
+ /** our decode options */
+ DecodeOptions _opt;
/** associated Job, or 0 */
Job* _job;
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
index 2a0d828e2..c4d818f49 100644
--- a/src/lib/decoder_factory.cc
+++ b/src/lib/decoder_factory.cc
@@ -36,7 +36,7 @@ using boost::dynamic_pointer_cast;
Decoders
decoder_factory (
- shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j
+ shared_ptr<Film> f, DecodeOptions o, Job* j
)
{
if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
index 47d977ce7..445a1c8a2 100644
--- a/src/lib/decoder_factory.h
+++ b/src/lib/decoder_factory.h
@@ -24,8 +24,9 @@
* @brief A method to create appropriate decoders for some content.
*/
+#include "options.h"
+
class Film;
-class DecodeOptions;
class Job;
class VideoDecoder;
class AudioDecoder;
@@ -43,7 +44,7 @@ struct Decoders {
};
extern Decoders decoder_factory (
- boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *
+ boost::shared_ptr<Film>, DecodeOptions, Job *
);
#endif
diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc
index 45d8e9d9d..4ad172781 100644
--- a/src/lib/delay_line.cc
+++ b/src/lib/delay_line.cc
@@ -91,12 +91,3 @@ DelayLine::process_audio (shared_ptr<AudioBuffers> data)
Audio (data);
}
-
-void
-DelayLine::process_end ()
-{
- if (_frames < 0) {
- _buffers->make_silent ();
- Audio (_buffers);
- }
-}
diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h
index fa2870ae7..4d6f1313b 100644
--- a/src/lib/delay_line.h
+++ b/src/lib/delay_line.h
@@ -29,7 +29,6 @@ public:
DelayLine (Log* log, int channels, int frames);
void process_audio (boost::shared_ptr<AudioBuffers>);
- void process_end ();
private:
boost::shared_ptr<AudioBuffers> _buffers;
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index 910d7c58e..b92be84a8 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -24,6 +24,7 @@
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
+#include <libdcp/picture_asset.h>
#include "encoder.h"
#include "util.h"
#include "options.h"
@@ -34,7 +35,9 @@
#include "config.h"
#include "dcp_video_frame.h"
#include "server.h"
+#include "format.h"
#include "cross.h"
+#include "writer.h"
using std::pair;
using std::string;
@@ -50,41 +53,25 @@ 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 EncodeOptions> o)
+Encoder::Encoder (shared_ptr<Film> f)
: _film (f)
- , _opt (o)
- , _just_skipped (false)
- , _video_frame (0)
- , _audio_frame (0)
+ , _video_frames_in (0)
+ , _video_frames_out (0)
#ifdef HAVE_SWRESAMPLE
, _swr_context (0)
-#endif
- , _audio_frames_written (0)
- , _process_end (false)
+#endif
+ , _have_a_real_frame (false)
+ , _terminate_encoder (false)
{
- if (_film->audio_stream()) {
- /* Create sound output files with .tmp suffixes; we will rename
- them if and when we complete.
- */
- for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) {
- SF_INFO sf_info;
- sf_info.samplerate = dcp_audio_sample_rate (_film->audio_stream()->sample_rate());
- /* We write mono files */
- sf_info.channels = 1;
- sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24;
- SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info);
- if (f == 0) {
- throw CreateFileError (_opt->multichannel_audio_out_path (i, true));
- }
- _sound_files.push_back (f);
- }
- }
+
}
Encoder::~Encoder ()
{
- close_sound_files ();
terminate_worker_threads ();
+ if (_writer) {
+ _writer->finish ();
+ }
}
void
@@ -130,6 +117,8 @@ Encoder::process_begin ()
_worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
}
}
+
+ _writer.reset (new Writer (_film));
}
@@ -153,32 +142,20 @@ Encoder::process_end ()
}
out->set_frames (frames);
- write_audio (out);
+ _writer->write (out);
}
swr_free (&_swr_context);
}
#endif
- if (_film->audio_stream()) {
- close_sound_files ();
-
- /* Rename .wav.tmp files to .wav */
- for (int i = 0; i < dcp_audio_channels (_film->audio_channels()); ++i) {
- if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) {
- boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false));
- }
- boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false));
- }
- }
-
boost::mutex::scoped_lock lock (_worker_mutex);
- _film->log()->log ("Clearing queue of " + lexical_cast<string> (_queue.size ()));
+ _film->log()->log ("Clearing queue of " + lexical_cast<string> (_encode_queue.size ()));
/* Keep waking workers until the queue is empty */
- while (!_queue.empty ()) {
- _film->log()->log ("Waking with " + lexical_cast<string> (_queue.size ()), Log::VERBOSE);
+ while (!_encode_queue.empty ()) {
+ _film->log()->log ("Waking with " + lexical_cast<string> (_encode_queue.size ()), Log::VERBOSE);
_worker_condition.notify_all ();
_worker_condition.wait (lock);
}
@@ -187,7 +164,7 @@ Encoder::process_end ()
terminate_worker_threads ();
- _film->log()->log ("Mopping up " + lexical_cast<string> (_queue.size()));
+ _film->log()->log ("Mopping up " + lexical_cast<string> (_encode_queue.size()));
/* The following sequence of events can occur in the above code:
1. a remote worker takes the last image off the queue
@@ -198,22 +175,18 @@ Encoder::process_end ()
So just mop up anything left in the queue here.
*/
- for (list<shared_ptr<DCPVideoFrame> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
+ for (list<shared_ptr<DCPVideoFrame> >::iterator i = _encode_queue.begin(); i != _encode_queue.end(); ++i) {
_film->log()->log (String::compose ("Encode left-over frame %1", (*i)->frame ()));
try {
- shared_ptr<EncodedData> e = (*i)->encode_locally ();
- e->write (_opt, (*i)->frame ());
+ _writer->write ((*i)->encode_locally(), (*i)->frame ());
frame_done ();
} catch (std::exception& e) {
_film->log()->log (String::compose ("Local encode failed (%1)", e.what ()));
}
}
- /* Now do links (or copies on windows) to duplicate frames */
- for (list<pair<int, int> >::iterator i = _links_required.begin(); i != _links_required.end(); ++i) {
- link (_opt->frame_out_path (i->first, false), _opt->frame_out_path (i->second, false));
- link (_opt->hash_out_path (i->first, false), _opt->hash_out_path (i->second, false));
- }
+ _writer->finish ();
+ _writer.reset ();
}
/** @return an estimate of the current number of frames we are encoding per second,
@@ -233,20 +206,12 @@ Encoder::current_frames_per_second () const
return _history_size / (seconds (now) - seconds (_time_history.back ()));
}
-/** @return true if the last frame to be processed was skipped as it already existed */
-bool
-Encoder::skipping () const
+/** @return Number of video frames that have been sent out */
+int
+Encoder::video_frames_out () const
{
boost::mutex::scoped_lock (_history_mutex);
- return _just_skipped;
-}
-
-/** @return Number of video frames that have been received */
-SourceFrame
-Encoder::video_frame () const
-{
- boost::mutex::scoped_lock (_history_mutex);
- return _video_frame;
+ return _video_frames_out;
}
/** Should be called when a frame has been encoded successfully.
@@ -256,7 +221,6 @@ void
Encoder::frame_done ()
{
boost::mutex::scoped_lock lock (_history_mutex);
- _just_skipped = false;
struct timeval tv;
gettimeofday (&tv, 0);
@@ -266,105 +230,67 @@ Encoder::frame_done ()
}
}
-/** Called by a subclass when it has just skipped the processing
- of a frame because it has already been done.
-*/
-void
-Encoder::frame_skipped ()
-{
- boost::mutex::scoped_lock lock (_history_mutex);
- _just_skipped = true;
-}
-
void
Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub)
{
- if (_opt->video_skip != 0 && (_video_frame % _opt->video_skip) != 0) {
- ++_video_frame;
+ DCPFrameRate dfr (_film->frames_per_second ());
+
+ if (dfr.skip && (_video_frames_in % 2)) {
+ ++_video_frames_in;
return;
}
- 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;
- }
- }
-
boost::mutex::scoped_lock lock (_worker_mutex);
/* Wait until the queue has gone down a bit */
- while (_queue.size() >= _worker_threads.size() * 2 && !_process_end) {
- TIMING ("decoder sleeps with queue of %1", _queue.size());
+ while (_encode_queue.size() >= _worker_threads.size() * 2 && !_terminate_encoder) {
+ TIMING ("decoder sleeps with queue of %1", _encode_queue.size());
_worker_condition.wait (lock);
- TIMING ("decoder wakes with queue of %1", _queue.size());
+ TIMING ("decoder wakes with queue of %1", _encode_queue.size());
}
- if (_process_end) {
+ if (_terminate_encoder) {
return;
}
- /* Only do the processing if we don't already have a file for this frame */
- if (boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) {
- frame_skipped ();
- return;
- }
-
- if (same && _last_real_frame) {
- /* Use the last frame that we encoded. We need to postpone doing the actual link,
- as on windows the link is really a copy and the reference frame might not have
- finished encoding yet.
- */
- _links_required.push_back (make_pair (_last_real_frame.get(), _video_frame));
+ if (_writer->can_fake_write (_video_frames_out)) {
+ _writer->fake_write (_video_frames_out);
+ _have_a_real_frame = false;
+ } else if (same && _have_a_real_frame) {
+ /* Use the last frame that we encoded. */
+ _writer->repeat (_video_frames_out);
+ frame_done ();
} else {
/* Queue this new frame for encoding */
pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
- TIMING ("adding to queue of %1", _queue.size ());
- _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+ TIMING ("adding to queue of %1", _encode_queue.size ());
+ _encode_queue.push_back (boost::shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- image, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(),
- _film->scaler(), _video_frame, _film->frames_per_second(), s.second,
+ image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
+ _film->subtitle_offset(), _film->subtitle_scale(),
+ _film->scaler(), _video_frames_out, _film->frames_per_second(), s.second,
_film->colour_lut(), _film->j2k_bandwidth(),
_film->log()
)
));
_worker_condition.notify_all ();
- _last_real_frame = _video_frame;
+ _have_a_real_frame = true;
}
- ++_video_frame;
+ ++_video_frames_in;
+ ++_video_frames_out;
+
+ if (dfr.repeat) {
+ _writer->repeat (_video_frames_out);
+ ++_video_frames_out;
+ frame_done ();
+ }
}
void
Encoder::process_audio (shared_ptr<AudioBuffers> data)
{
- 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_range.get();
- /* Range of this block of data */
- pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames());
-
- if (this_range.second < required_range.first || required_range.second < this_range.first) {
- /* No part of this audio is within the required range */
- _audio_frame += data->frames();
- return;
- } else if (required_range.first >= this_range.first && required_range.first < this_range.second) {
- /* Trim start */
- int64_t const shift = required_range.first - this_range.first;
- trimmed->move (shift, 0, trimmed->frames() - shift);
- trimmed->set_frames (trimmed->frames() - shift);
- } else if (required_range.second >= this_range.first && required_range.second < this_range.second) {
- /* Trim end */
- trimmed->set_frames (required_range.second - this_range.first);
- }
-
- data = trimmed;
- }
-
#if HAVE_SWRESAMPLE
/* Maybe sample-rate convert */
if (_swr_context) {
@@ -406,36 +332,14 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data)
data = b;
}
- write_audio (data);
-
- _audio_frame += data->frames ();
-}
-
-void
-Encoder::write_audio (shared_ptr<const AudioBuffers> audio)
-{
- for (int i = 0; i < audio->channels(); ++i) {
- sf_write_float (_sound_files[i], audio->data(i), audio->frames());
- }
-
- _audio_frames_written += audio->frames ();
+ _writer->write (data);
}
void
-Encoder::close_sound_files ()
-{
- for (vector<SNDFILE*>::iterator i = _sound_files.begin(); i != _sound_files.end(); ++i) {
- sf_close (*i);
- }
-
- _sound_files.clear ();
-}
-
-void
Encoder::terminate_worker_threads ()
{
boost::mutex::scoped_lock lock (_worker_mutex);
- _process_end = true;
+ _terminate_encoder = true;
_worker_condition.notify_all ();
lock.unlock ();
@@ -458,18 +362,18 @@ Encoder::encoder_thread (ServerDescription* server)
TIMING ("encoder thread %1 sleeps", boost::this_thread::get_id());
boost::mutex::scoped_lock lock (_worker_mutex);
- while (_queue.empty () && !_process_end) {
+ while (_encode_queue.empty () && !_terminate_encoder) {
_worker_condition.wait (lock);
}
- if (_process_end) {
+ if (_terminate_encoder) {
return;
}
- TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
- boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+ TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _encode_queue.size());
+ boost::shared_ptr<DCPVideoFrame> vf = _encode_queue.front ();
_film->log()->log (String::compose ("Encoder thread %1 pops frame %2 from queue", boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
- _queue.pop_front ();
+ _encode_queue.pop_front ();
lock.unlock ();
@@ -509,14 +413,14 @@ Encoder::encoder_thread (ServerDescription* server)
}
if (encoded) {
- encoded->write (_opt, vf->frame ());
+ _writer->write (encoded, vf->frame ());
frame_done ();
} else {
lock.lock ();
_film->log()->log (
String::compose ("Encoder thread %1 pushes frame %2 back onto queue after failure", boost::this_thread::get_id(), vf->frame())
);
- _queue.push_front (vf);
+ _encode_queue.push_front (vf);
lock.unlock ();
}
@@ -528,18 +432,3 @@ Encoder::encoder_thread (ServerDescription* server)
_worker_condition.notify_all ();
}
}
-
-void
-Encoder::link (string a, string b) const
-{
-#ifdef DVDOMATIC_POSIX
- int const r = symlink (a.c_str(), b.c_str());
- if (r) {
- throw EncodeError (String::compose ("could not create symlink from %1 to %2", a, b));
- }
-#endif
-
-#ifdef DVDOMATIC_WINDOWS
- boost::filesystem::copy_file (a, b);
-#endif
-}
diff --git a/src/lib/encoder.h b/src/lib/encoder.h
index 52ccfc166..8b02f7004 100644
--- a/src/lib/encoder.h
+++ b/src/lib/encoder.h
@@ -39,18 +39,18 @@ extern "C" {
#include <libswresample/swresample.h>
}
#endif
-#include <sndfile.h>
#include "util.h"
#include "video_sink.h"
#include "audio_sink.h"
-class EncodeOptions;
class Image;
class Subtitle;
class AudioBuffers;
class Film;
class ServerDescription;
class DCPVideoFrame;
+class EncodedData;
+class Writer;
/** @class Encoder
* @brief Encoder to J2K and WAV for DCP.
@@ -62,7 +62,7 @@ class DCPVideoFrame;
class Encoder : public VideoSink, public AudioSink
{
public:
- Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const EncodeOptions> o);
+ Encoder (boost::shared_ptr<Film> f);
virtual ~Encoder ();
/** Called to indicate that a processing run is about to begin */
@@ -82,20 +82,21 @@ public:
virtual void process_end ();
float current_frames_per_second () const;
- bool skipping () const;
- SourceFrame video_frame () const;
+ int video_frames_out () const;
-protected:
+private:
void frame_done ();
- void frame_skipped ();
+ void write_audio (boost::shared_ptr<const AudioBuffers> audio);
+
+ void encoder_thread (ServerDescription *);
+ void terminate_worker_threads ();
+
/** Film that we are encoding */
- boost::shared_ptr<const Film> _film;
- /** Options */
- boost::shared_ptr<const EncodeOptions> _opt;
+ boost::shared_ptr<Film> _film;
- /** Mutex for _time_history, _just_skipped and _last_frame */
+ /** Mutex for _time_history and _last_frame */
mutable boost::mutex _history_mutex;
/** List of the times of completion of the last _history_size frames;
first is the most recently completed.
@@ -103,41 +104,24 @@ protected:
std::list<struct timeval> _time_history;
/** Number of frames that we should keep history for */
static int const _history_size;
- /** true if the last frame we processed was skipped (because it was already done) */
- bool _just_skipped;
/** Number of video frames received so far */
- SourceFrame _video_frame;
- /** Number of audio frames received so far */
- int64_t _audio_frame;
-
-private:
- void close_sound_files ();
- void write_audio (boost::shared_ptr<const AudioBuffers> audio);
-
- void encoder_thread (ServerDescription *);
- void terminate_worker_threads ();
- void link (std::string, std::string) const;
+ SourceFrame _video_frames_in;
+ /** Number of video frames written for the DCP so far */
+ int _video_frames_out;
#if HAVE_SWRESAMPLE
SwrContext* _swr_context;
#endif
- /** List of links that we need to create when all frames have been processed;
- * such that we need to call link (first, second) for each member of this list.
- * In other words, `first' is a `real' frame and `second' should be a link to `first'.
- */
- std::list<std::pair<int, int> > _links_required;
-
- std::vector<SNDFILE*> _sound_files;
- int64_t _audio_frames_written;
-
- boost::optional<int> _last_real_frame;
- bool _process_end;
- std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
+ bool _have_a_real_frame;
+ bool _terminate_encoder;
+ std::list<boost::shared_ptr<DCPVideoFrame> > _encode_queue;
std::list<boost::thread *> _worker_threads;
mutable boost::mutex _worker_mutex;
boost::condition _worker_condition;
+
+ boost::shared_ptr<Writer> _writer;
};
#endif
diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc
index a783cde33..69a757e2b 100644
--- a/src/lib/examine_content_job.cc
+++ b/src/lib/examine_content_job.cc
@@ -78,8 +78,8 @@ ExamineContentJob::run ()
_film->unset_length ();
_film->set_crop (Crop ());
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- o->decode_audio = false;
+ DecodeOptions o;
+ o.decode_audio = false;
Decoders decoders = decoder_factory (_film, o, this);
@@ -96,8 +96,7 @@ ExamineContentJob::run ()
/* Get a quick decoder to get the content's length from its header */
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- Decoders d = decoder_factory (_film, o, 0);
+ Decoders d = decoder_factory (_film, DecodeOptions(), 0);
_film->set_length (d.video->length());
_film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get()));
diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc
index 25c8068b6..366051418 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 DecodeOptions> o, Job* j)
+ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, 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 2558955eb..37e53bca7 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 DecodeOptions>, Job *);
+ ExternalAudioDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
bool pass ();
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index aff3ff666..81f405644 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -58,8 +58,9 @@ using std::list;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
+using libdcp::Size;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
: Decoder (f, o, j)
, VideoDecoder (f, o, j)
, AudioDecoder (f, o, j)
@@ -78,7 +79,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions
setup_audio ();
setup_subtitle ();
- if (!o->video_sync) {
+ if (!o.video_sync) {
_first_video = 0;
}
}
@@ -239,7 +240,7 @@ FFmpegDecoder::pass ()
filter_and_emit_video (_frame);
}
- if (_audio_stream && _opt->decode_audio) {
+ if (_audio_stream && _opt.decode_audio) {
while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
int const data_size = av_samples_get_buffer_size (
0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
@@ -267,14 +268,14 @@ FFmpegDecoder::pass ()
_film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size));
}
- if (_opt->video_sync) {
+ if (_opt.video_sync) {
out_with_sync ();
} else {
filter_and_emit_video (_frame);
}
}
- } else if (ffa && _packet.stream_index == ffa->id() && _opt->decode_audio) {
+ } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
int frame_finished;
if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
@@ -323,7 +324,7 @@ FFmpegDecoder::pass ()
}
}
- } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) {
+ } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles && _first_video) {
int got_subtitle;
AVSubtitle sub;
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 3b564b826..9a4e65ebc 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -86,7 +86,7 @@ private:
class FFmpegDecoder : public VideoDecoder, public AudioDecoder
{
public:
- FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+ FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
~FFmpegDecoder ();
float frames_per_second () const;
diff --git a/src/lib/film.cc b/src/lib/film.cc
index f5522b74a..8720e79e4 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -39,7 +39,6 @@
#include "ab_transcode_job.h"
#include "transcode_job.h"
#include "scp_dcp_job.h"
-#include "make_dcp_job.h"
#include "log.h"
#include "options.h"
#include "exceptions.h"
@@ -47,7 +46,6 @@
#include "scaler.h"
#include "decoder_factory.h"
#include "config.h"
-#include "check_hashes_job.h"
#include "version.h"
#include "ui_signaller.h"
#include "video_decoder.h"
@@ -72,8 +70,9 @@ using boost::to_upper_copy;
using boost::ends_with;
using boost::starts_with;
using boost::optional;
+using libdcp::Size;
-int const Film::state_version = 1;
+int const Film::state_version = 2;
/** Construct a Film object in a given directory, reading any metadata
* file that exists in that directory. An exception will be thrown if
@@ -89,8 +88,8 @@ Film::Film (string d, bool must_exist)
, _dcp_content_type (0)
, _format (0)
, _scaler (Scaler::from_id ("bicubic"))
- , _dcp_trim_start (0)
- , _dcp_trim_end (0)
+ , _trim_start (0)
+ , _trim_end (0)
, _dcp_ab (false)
, _use_content_audio (true)
, _audio_gain (0)
@@ -156,8 +155,8 @@ Film::Film (Film const & o)
, _crop (o._crop)
, _filters (o._filters)
, _scaler (o._scaler)
- , _dcp_trim_start (o._dcp_trim_start)
- , _dcp_trim_end (o._dcp_trim_end)
+ , _trim_start (o._trim_start)
+ , _trim_end (o._trim_end)
, _dcp_ab (o._dcp_ab)
, _content_audio_stream (o._content_audio_stream)
, _external_audio (o._external_audio)
@@ -174,6 +173,7 @@ Film::Film (Film const & o)
, _dci_metadata (o._dci_metadata)
, _size (o._size)
, _length (o._length)
+ , _dcp_intrinsic_duration (o._dcp_intrinsic_duration)
, _content_digest (o._content_digest)
, _content_audio_streams (o._content_audio_streams)
, _external_audio_stream (o._external_audio_stream)
@@ -188,24 +188,14 @@ Film::~Film ()
{
delete _log;
}
-
-/** @return The path to the directory to write JPEG2000 files to */
+
string
-Film::j2k_dir () const
+Film::video_state_identifier () const
{
- assert (format());
-
- boost::filesystem::path p;
-
- /* Start with j2c */
- p /= "j2c";
+ assert (format ());
pair<string, string> f = Filter::ffmpeg_strings (filters());
- /* Write stuff to specify the filter / post-processing settings that are in use,
- so that we don't get confused about J2K files generated using different
- settings.
- */
stringstream s;
s << format()->id()
<< "_" << content_digest()
@@ -215,19 +205,37 @@ Film::j2k_dir () const
<< "_" << j2k_bandwidth()
<< "_" << boost::lexical_cast<int> (colour_lut());
- p /= s.str ();
-
- /* Similarly for the A/B case */
if (dcp_ab()) {
- stringstream s;
pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
- p /= s.str ();
}
-
+
+ return s.str ();
+}
+
+/** @return The path to the directory to write video frame info files to */
+string
+Film::info_dir () const
+{
+ boost::filesystem::path p;
+ p /= "info";
+ p /= video_state_identifier ();
return dir (p.string());
}
+string
+Film::video_mxf_dir () const
+{
+ boost::filesystem::path p;
+ return dir ("video");
+}
+
+string
+Film::video_mxf_filename () const
+{
+ return video_state_identifier() + ".mxf";
+}
+
/** Add suitable Jobs to the JobManager to create a DCP for this Film.
* @param true to transcode, false to use the WAV and J2K files that are already there.
*/
@@ -249,7 +257,9 @@ Film::make_dcp (bool transcode)
}
log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? "still" : "video")));
- log()->log (String::compose ("Content length %1", length().get()));
+ if (length()) {
+ log()->log (String::compose ("Content length %1", length().get()));
+ }
log()->log (String::compose ("Content digest %1", content_digest()));
log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
@@ -282,47 +292,18 @@ Film::make_dcp (bool transcode)
throw MissingSettingError ("name");
}
- 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 ()) {
- oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
- if (audio_stream()) {
- oe->audio_range = make_pair (
-
- video_frames_to_audio_frames (
- oe->video_range.get().first,
- dcp_audio_sample_rate (audio_stream()->sample_rate()),
- dcp_frame_rate (frames_per_second()).frames_per_second
- ),
-
- video_frames_to_audio_frames (
- oe->video_range.get().second,
- dcp_audio_sample_rate (audio_stream()->sample_rate()),
- dcp_frame_rate (frames_per_second()).frames_per_second
- )
- );
- }
-
- }
-
- oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
-
- shared_ptr<DecodeOptions> od (new DecodeOptions);
- od->decode_subtitles = with_subtitles ();
+ DecodeOptions od;
+ 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(), od, oe, shared_ptr<Job> ())));
+ r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
} else {
- 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 TranscodeJob (shared_from_this(), od, shared_ptr<Job> ())));
}
}
-
- 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 */
@@ -363,7 +344,7 @@ Film::encoded_frames () const
}
int N = 0;
- for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (j2k_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
+ for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (info_dir ()); i != boost::filesystem::directory_iterator(); ++i) {
++N;
boost::this_thread::interruption_point ();
}
@@ -406,8 +387,8 @@ Film::write_metadata () const
f << "filter " << (*i)->id () << "\n";
}
f << "scaler " << _scaler->id () << "\n";
- f << "dcp_trim_start " << _dcp_trim_start << "\n";
- f << "dcp_trim_end " << _dcp_trim_end << "\n";
+ f << "trim_start " << _trim_start << "\n";
+ f << "trim_end " << _trim_end << "\n";
f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n";
if (_content_audio_stream) {
f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n";
@@ -431,6 +412,7 @@ Film::write_metadata () const
f << "width " << _size.width << "\n";
f << "height " << _size.height << "\n";
f << "length " << _length.get_value_or(0) << "\n";
+ f << "dcp_intrinsic_duration " << _dcp_intrinsic_duration.get_value_or(0) << "\n";
f << "content_digest " << _content_digest << "\n";
for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
@@ -511,10 +493,10 @@ Film::read_metadata ()
_filters.push_back (Filter::from_id (v));
} else if (k == "scaler") {
_scaler = Scaler::from_id (v);
- } else if (k == "dcp_trim_start") {
- _dcp_trim_start = atoi (v.c_str ());
- } else if (k == "dcp_trim_end") {
- _dcp_trim_end = atoi (v.c_str ());
+ } else if ( ((!version || version < 2) && k == "trim_start") || k == "trim_start") {
+ _trim_start = atoi (v.c_str ());
+ } else if ( ((!version || version < 2) && k == "trim_end") || k == "trim_end") {
+ _trim_end = atoi (v.c_str ());
} else if (k == "dcp_ab") {
_dcp_ab = (v == "1");
} else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
@@ -563,6 +545,11 @@ Film::read_metadata ()
if (vv) {
_length = vv;
}
+ } else if (k == "dcp_intrinsic_duration") {
+ int const vv = atoi (v.c_str ());
+ if (vv) {
+ _dcp_intrinsic_duration = vv;
+ }
} else if (k == "content_digest") {
_content_digest = v;
} else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
@@ -674,30 +661,25 @@ Film::target_audio_sample_rate () const
/* Resample to a DCI-approved sample rate */
double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
- DCPFrameRate dfr = dcp_frame_rate (frames_per_second ());
+ DCPFrameRate dfr (frames_per_second ());
- /* Compensate for the fact that video will be rounded to the
- nearest integer number of frames per second.
+ /* Compensate if the DCP is being run at a different frame rate
+ to the source; that is, if the video is run such that it will
+ look different in the DCP compared to the source (slower or faster).
+ skip/repeat doesn't come into effect here.
*/
- if (dfr.run_fast) {
- t *= _frames_per_second * dfr.skip / dfr.frames_per_second;
+
+ if (dfr.change_speed) {
+ t *= _frames_per_second * dfr.factor() / dfr.frames_per_second;
}
return rint (t);
}
-boost::optional<int>
-Film::dcp_length () const
+int
+Film::still_duration_in_frames () const
{
- if (content_type() == STILL) {
- return _still_duration * frames_per_second();
- }
-
- if (!length()) {
- return boost::optional<int> ();
- }
-
- return length().get() - dcp_trim_start() - dcp_trim_end();
+ return still_duration() * frames_per_second();
}
/** @return a DCI-compliant name for a DCP of this film */
@@ -828,16 +810,8 @@ Film::set_content (string c)
{
string check = directory ();
-#if BOOST_FILESYSTEM_VERSION == 3
boost::filesystem::path slash ("/");
string platform_slash = slash.make_preferred().string ();
-#else
-#ifdef DVDOMATIC_WINDOWS
- string platform_slash = "\\";
-#else
- string platform_slash = "/";
-#endif
-#endif
if (!ends_with (check, platform_slash)) {
check += platform_slash;
@@ -871,8 +845,7 @@ Film::set_content (string c)
*/
try {
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- Decoders d = decoder_factory (shared_from_this(), o, 0);
+ Decoders d = decoder_factory (shared_from_this(), DecodeOptions(), 0);
set_size (d.video->native_size ());
set_frames_per_second (d.video->frames_per_second ());
@@ -1047,23 +1020,23 @@ Film::set_scaler (Scaler const * s)
}
void
-Film::set_dcp_trim_start (int t)
+Film::set_trim_start (int t)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_trim_start = t;
+ _trim_start = t;
}
- signal_changed (DCP_TRIM_START);
+ signal_changed (TRIM_START);
}
void
-Film::set_dcp_trim_end (int t)
+Film::set_trim_end (int t)
{
{
boost::mutex::scoped_lock lm (_state_mutex);
- _dcp_trim_end = t;
+ _trim_end = t;
}
- signal_changed (DCP_TRIM_END);
+ signal_changed (TRIM_END);
}
void
@@ -1094,8 +1067,7 @@ Film::set_external_audio (vector<string> a)
_external_audio = a;
}
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
+ shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), DecodeOptions(), 0));
if (decoder->audio_stream()) {
_external_audio_stream = decoder->audio_stream ();
}
@@ -1242,7 +1214,17 @@ Film::unset_length ()
_length = boost::none;
}
signal_changed (LENGTH);
-}
+}
+
+void
+Film::set_dcp_intrinsic_duration (int d)
+{
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _dcp_intrinsic_duration = d;
+ }
+ signal_changed (DCP_INTRINSIC_DURATION);
+}
void
Film::set_content_digest (string d)
@@ -1323,3 +1305,38 @@ Film::audio_stream () const
return _external_audio_stream;
}
+
+string
+Film::info_path (int f) const
+{
+ boost::filesystem::path p;
+ p /= info_dir ();
+
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << f << ".md5";
+
+ p /= s.str();
+ return p.string ();
+}
+
+string
+Film::j2c_path (int f, bool t) const
+{
+ boost::filesystem::path p;
+ p /= "j2c";
+ p /= video_state_identifier ();
+
+ stringstream s;
+ s.width (8);
+ s << setfill('0') << f << ".j2c";
+
+ if (t) {
+ s << ".tmp";
+ }
+
+ p /= s.str();
+ return p.string ();
+}
+
+
diff --git a/src/lib/film.h b/src/lib/film.h
index af7ec6701..5b65a099d 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -60,7 +60,11 @@ public:
Film (Film const &);
~Film ();
- std::string j2k_dir () const;
+ std::string info_dir () const;
+ std::string j2c_path (int f, bool t) const;
+ std::string info_path (int f) const;
+ std::string video_mxf_dir () const;
+ std::string video_mxf_filename () const;
void examine_content ();
void send_dcp_to_tms ();
@@ -88,10 +92,13 @@ public:
void read_metadata ();
libdcp::Size cropped_size (libdcp::Size) const;
- boost::optional<int> dcp_length () const;
std::string dci_name () const;
std::string dcp_name () const;
+ boost::optional<int> dcp_intrinsic_duration () const {
+ return _dcp_intrinsic_duration;
+ }
+
/** @return true if our state has changed since we last saved it */
bool dirty () const {
return _dirty;
@@ -115,8 +122,8 @@ public:
CROP,
FILTERS,
SCALER,
- DCP_TRIM_START,
- DCP_TRIM_END,
+ TRIM_START,
+ TRIM_END,
DCP_AB,
CONTENT_AUDIO_STREAM,
EXTERNAL_AUDIO,
@@ -133,6 +140,7 @@ public:
DCI_METADATA,
SIZE,
LENGTH,
+ DCP_INTRINSIC_DURATION,
CONTENT_AUDIO_STREAMS,
SUBTITLE_STREAMS,
FRAMES_PER_SECOND,
@@ -191,14 +199,14 @@ public:
return _scaler;
}
- SourceFrame dcp_trim_start () const {
+ int trim_start () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_trim_start;
+ return _trim_start;
}
- SourceFrame dcp_trim_end () const {
+ int trim_end () const {
boost::mutex::scoped_lock lm (_state_mutex);
- return _dcp_trim_end;
+ return _trim_end;
}
bool dcp_ab () const {
@@ -236,6 +244,8 @@ public:
return _still_duration;
}
+ int still_duration_in_frames () const;
+
boost::shared_ptr<SubtitleStream> subtitle_stream () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _subtitle_stream;
@@ -324,8 +334,8 @@ public:
void set_bottom_crop (int);
void set_filters (std::vector<Filter const *>);
void set_scaler (Scaler const *);
- void set_dcp_trim_start (int);
- void set_dcp_trim_end (int);
+ void set_trim_start (int);
+ void set_trim_end (int);
void set_dcp_ab (bool);
void set_content_audio_stream (boost::shared_ptr<AudioStream>);
void set_external_audio (std::vector<std::string>);
@@ -343,6 +353,7 @@ public:
void set_size (libdcp::Size);
void set_length (SourceFrame);
void unset_length ();
+ void set_dcp_intrinsic_duration (int);
void set_content_digest (std::string);
void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
@@ -367,6 +378,7 @@ private:
void signal_changed (Property);
void examine_content_finished ();
+ std::string video_state_identifier () const;
/** Complete path to directory containing the film metadata;
* must not be relative.
@@ -399,9 +411,9 @@ private:
/** Scaler algorithm to use */
Scaler const * _scaler;
/** Frames to trim off the start of the DCP */
- int _dcp_trim_start;
+ int _trim_start;
/** Frames to trim off the end of the DCP */
- int _dcp_trim_end;
+ int _trim_end;
/** true to create an A/B comparison DCP, where the left half of the image
is the video without any filters or post-processing, and the right half
has the specified filters and post-processing.
@@ -443,10 +455,11 @@ private:
/* Data which are cached to speed things up */
- /** libdcp::Size, in pixels, of the source (ignoring cropping) */
+ /** Size, in pixels, of the source (ignoring cropping) */
libdcp::Size _size;
/** The length of the source, in video frames (as far as we know) */
boost::optional<SourceFrame> _length;
+ boost::optional<int> _dcp_intrinsic_duration;
/** MD5 digest of our content file */
std::string _content_digest;
/** The audio streams in our content */
diff --git a/src/lib/filter.cc b/src/lib/filter.cc
index 446cc111d..9a662f90f 100644
--- a/src/lib/filter.cc
+++ b/src/lib/filter.cc
@@ -22,6 +22,10 @@
*/
#include "filter.h"
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libpostproc/postprocess.h>
+}
using namespace std;
@@ -29,12 +33,14 @@ vector<Filter const *> Filter::_filters;
/** @param i Our id.
* @param n User-visible name.
+ * @param c User-visible category.
* @param v String for a FFmpeg video filter descriptor, or "".
* @param p String for a FFmpeg post-processing descriptor, or "".
*/
-Filter::Filter (string i, string n, string v, string p)
+Filter::Filter (string i, string n, string c, string v, string p)
: _id (i)
, _name (n)
+ , _category (c)
, _vf (v)
, _pp (p)
{
@@ -57,30 +63,46 @@ Filter::setup_filters ()
{
/* Note: "none" is a magic id name, so don't use it here */
- _filters.push_back (new Filter ("pphb", "Horizontal deblocking filter", "", "hb"));
- _filters.push_back (new Filter ("ppvb", "Vertical deblocking filter", "", "vb"));
- _filters.push_back (new Filter ("ppha", "Horizontal deblocking filter A", "", "ha"));
- _filters.push_back (new Filter ("ppva", "Vertical deblocking filter A", "", "va"));
- _filters.push_back (new Filter ("pph1", "Experimental horizontal deblocking filter 1", "", "h1"));
- _filters.push_back (new Filter ("pphv", "Experimental vertical deblocking filter 1", "", "v1"));
- _filters.push_back (new Filter ("ppdr", "Deringing filter", "", "dr"));
- _filters.push_back (new Filter ("pplb", "Linear blend deinterlacer", "", "lb"));
- _filters.push_back (new Filter ("ppli", "Linear interpolating deinterlacer", "", "li"));
- _filters.push_back (new Filter ("ppci", "Cubic interpolating deinterlacer", "", "ci"));
- _filters.push_back (new Filter ("ppmd", "Median deinterlacer", "", "md"));
- _filters.push_back (new Filter ("ppfd", "FFMPEG deinterlacer", "", "fd"));
- _filters.push_back (new Filter ("ppl5", "FIR low-pass deinterlacer", "", "l5"));
- _filters.push_back (new Filter ("mcdeint", "Motion compensating deinterlacer", "mcdeint", ""));
- _filters.push_back (new Filter ("kerndeint", "Kernel deinterlacer", "kerndeint", ""));
- _filters.push_back (new Filter ("yadif", "Yet Another Deinterlacing Filter", "yadif", ""));
- _filters.push_back (new Filter ("pptn", "Temporal noise reducer", "", "tn"));
- _filters.push_back (new Filter ("ppfq", "Force quantizer", "", "fq"));
- _filters.push_back (new Filter ("gradfun", "Gradient debander", "gradfun", ""));
- _filters.push_back (new Filter ("unsharp", "Unsharp mask and Gaussian blur", "unsharp", ""));
- _filters.push_back (new Filter ("denoise3d", "3D denoiser", "denoise3d", ""));
- _filters.push_back (new Filter ("hqdn3d", "High quality 3D denoiser", "hqdn3d", ""));
- _filters.push_back (new Filter ("telecine", "Telecine filter", "telecine", ""));
- _filters.push_back (new Filter ("ow", "Overcomplete wavelet denoiser", "mp=ow", ""));
+ maybe_add ("pphb", "Horizontal deblocking filter", "De-blocking", "", "hb");
+ maybe_add ("ppvb", "Vertical deblocking filter", "De-blocking", "", "vb");
+ maybe_add ("ppha", "Horizontal deblocking filter A", "De-blocking", "", "ha");
+ maybe_add ("ppva", "Vertical deblocking filter A", "De-blocking", "", "va");
+ maybe_add ("pph1", "Experimental horizontal deblocking filter 1", "De-blocking", "", "h1");
+ maybe_add ("pphv", "Experimental vertical deblocking filter 1", "De-blocking", "", "v1");
+ maybe_add ("ppdr", "Deringing filter", "Misc", "", "dr");
+ maybe_add ("pplb", "Linear blend deinterlacer", "De-interlacing", "", "lb");
+ maybe_add ("ppli", "Linear interpolating deinterlacer", "De-interlacing", "", "li");
+ maybe_add ("ppci", "Cubic interpolating deinterlacer", "De-interlacing", "", "ci");
+ maybe_add ("ppmd", "Median deinterlacer", "De-interlacing", "", "md");
+ maybe_add ("ppfd", "FFMPEG deinterlacer", "De-interlacing", "", "fd");
+ maybe_add ("ppl5", "FIR low-pass deinterlacer", "De-interlacing", "", "l5");
+ maybe_add ("mcdeint", "Motion compensating deinterlacer", "De-interlacing", "mcdeint", "");
+ maybe_add ("kerndeint", "Kernel deinterlacer", "De-interlacing", "kerndeint", "");
+ maybe_add ("yadif", "Yet Another Deinterlacing Filter", "De-interlacing", "yadif", "");
+ maybe_add ("pptn", "Temporal noise reducer", "Noise reduction", "", "tn");
+ maybe_add ("ppfq", "Force quantizer", "Misc", "", "fq");
+ maybe_add ("gradfun", "Gradient debander", "Misc", "gradfun", "");
+ maybe_add ("unsharp", "Unsharp mask and Gaussian blur", "Misc", "unsharp", "");
+ maybe_add ("denoise3d", "3D denoiser", "Noise reduction", "denoise3d", "");
+ maybe_add ("hqdn3d", "High quality 3D denoiser", "Noise reduction", "hqdn3d", "");
+ maybe_add ("telecine", "Telecine filter", "Misc", "telecine", "");
+ maybe_add ("ow", "Overcomplete wavelet denoiser", "Noise reduction", "mp=ow", "");
+}
+
+void
+Filter::maybe_add (string i, string n, string c, string v, string p)
+{
+ if (!v.empty ()) {
+ if (avfilter_get_by_name (i.c_str())) {
+ _filters.push_back (new Filter (i, n, c, v, p));
+ }
+ } else if (!p.empty ()) {
+ pp_mode* m = pp_get_mode_by_name_and_quality (p.c_str(), PP_QUALITY_MAX);
+ if (m) {
+ _filters.push_back (new Filter (i, n, c, v, p));
+ pp_free_mode (m);
+ }
+ }
}
/** @param filters Set of filters.
diff --git a/src/lib/filter.h b/src/lib/filter.h
index 20c55049c..205d92482 100644
--- a/src/lib/filter.h
+++ b/src/lib/filter.h
@@ -33,7 +33,7 @@
class Filter
{
public:
- Filter (std::string, std::string, std::string, std::string);
+ Filter (std::string, std::string, std::string, std::string, std::string);
/** @return our id */
std::string id () const {
@@ -54,6 +54,10 @@ public:
std::string pp () const {
return _pp;
}
+
+ std::string category () const {
+ return _category;
+ }
static std::vector<Filter const *> all ();
static Filter const * from_id (std::string);
@@ -66,6 +70,7 @@ private:
std::string _id;
/** user-visible name */
std::string _name;
+ std::string _category;
/** string for a FFmpeg video filter descriptor */
std::string _vf;
/** string for a FFmpeg post-processing descriptor */
@@ -73,6 +78,7 @@ private:
/** all available filters */
static std::vector<Filter const *> _filters;
+ static void maybe_add (std::string, std::string, std::string, std::string, std::string);
};
#endif
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index 6cd7dc2cb..b0991a2da 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -47,6 +47,7 @@ using std::stringstream;
using std::string;
using std::list;
using boost::shared_ptr;
+using libdcp::Size;
/** Construct a FilterGraph for the settings in a film.
* @param film Film.
@@ -67,8 +68,6 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp:
filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size()));
- avfilter_register_all ();
-
AVFilterGraph* graph = avfilter_graph_alloc();
if (graph == 0) {
throw DecodeError ("Could not create filter graph.");
diff --git a/src/lib/format.cc b/src/lib/format.cc
index 6615e16e0..016c21fde 100644
--- a/src/lib/format.cc
+++ b/src/lib/format.cc
@@ -35,6 +35,7 @@ using std::setprecision;
using std::stringstream;
using std::vector;
using boost::shared_ptr;
+using libdcp::Size;
vector<Format const *> Format::_formats;
diff --git a/src/lib/image.cc b/src/lib/image.cc
index feda09ec5..0ec6bd26c 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -42,6 +42,7 @@ extern "C" {
using namespace std;
using namespace boost;
+using libdcp::Size;
void
Image::swap (Image& other)
@@ -95,11 +96,15 @@ Image::components () const
}
shared_ptr<Image>
-Image::scale (libdcp::Size out_size, Scaler const * scaler, bool aligned) const
+Image::scale (libdcp::Size out_size, Scaler const * scaler, bool result_aligned) const
{
assert (scaler);
+ /* Empirical testing suggests that sws_scale() will crash if
+ the input image is not aligned.
+ */
+ assert (aligned ());
- shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, aligned));
+ shared_ptr<Image> scaled (new SimpleImage (pixel_format(), out_size, result_aligned));
struct SwsContext* scale_context = sws_getContext (
size().width, size().height, pixel_format(),
@@ -124,14 +129,18 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, bool aligned) const
* @param scaler Scaler to use.
*/
shared_ptr<Image>
-Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool aligned) const
+Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler const * scaler, bool result_aligned) const
{
assert (scaler);
+ /* Empirical testing suggests that sws_scale() will crash if
+ the input image is not aligned.
+ */
+ assert (aligned ());
libdcp::Size content_size = out_size;
content_size.width -= (padding * 2);
- shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, aligned));
+ shared_ptr<Image> rgb (new SimpleImage (PIX_FMT_RGB24, content_size, result_aligned));
struct SwsContext* scale_context = sws_getContext (
size().width, size().height, pixel_format(),
@@ -152,7 +161,7 @@ Image::scale_and_convert_to_rgb (libdcp::Size out_size, int padding, Scaler cons
scheme of things.
*/
if (padding > 0) {
- shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, aligned));
+ shared_ptr<Image> padded_rgb (new SimpleImage (PIX_FMT_RGB24, out_size, result_aligned));
padded_rgb->make_black ();
/* XXX: we are cheating a bit here; we know the frame is RGB so we can
@@ -231,8 +240,8 @@ Image::crop (Crop crop, bool aligned) const
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];
+ in_p += stride()[c];
+ out_p += out->stride()[c];
}
}
@@ -244,13 +253,25 @@ Image::make_black ()
{
switch (_pixel_format) {
case PIX_FMT_YUV420P:
- case PIX_FMT_YUV422P10LE:
case PIX_FMT_YUV422P:
memset (data()[0], 0, lines(0) * stride()[0]);
- memset (data()[1], 0x80, lines(1) * stride()[1]);
- memset (data()[2], 0x80, lines(2) * stride()[2]);
+ memset (data()[1], 0x7f, lines(1) * stride()[1]);
+ memset (data()[2], 0x7f, lines(2) * stride()[2]);
break;
+ case PIX_FMT_YUV422P10LE:
+ memset (data()[0], 0, lines(0) * stride()[0]);
+ for (int i = 1; i < 3; ++i) {
+ int16_t* p = reinterpret_cast<int16_t*> (data()[i]);
+ for (int y = 0; y < size().height; ++y) {
+ for (int x = 0; x < line_size()[i] / 2; ++x) {
+ p[x] = (1 << 9) - 1;
+ }
+ p += stride()[i] / 2;
+ }
+ }
+ break;
+
case PIX_FMT_RGB24:
memset (data()[0], 0, lines(0) * stride()[0]);
break;
@@ -349,7 +370,7 @@ Image::bytes_per_pixel (int c) const
return 0.5;
}
case PIX_FMT_YUV422P10LE:
- if (c == 1) {
+ if (c == 0) {
return 2;
} else {
return 1;
@@ -472,6 +493,12 @@ SimpleImage::size () const
return _size;
}
+bool
+SimpleImage::aligned () const
+{
+ return _aligned;
+}
+
FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b)
: Image (p)
, _buffer (b)
@@ -509,6 +536,13 @@ FilterBufferImage::size () const
return libdcp::Size (_buffer->video->w, _buffer->video->h);
}
+bool
+FilterBufferImage::aligned () const
+{
+ /* XXX? */
+ return true;
+}
+
RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im)
: SimpleImage (im->pixel_format(), im->size(), false)
{
diff --git a/src/lib/image.h b/src/lib/image.h
index adee8bc4d..23f13a648 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -65,9 +65,11 @@ public:
/** @return Array of strides for each line (including any alignment padding bytes) */
virtual int * stride () const = 0;
- /** @return libdcp::Size of the image, in pixels */
+ /** @return Size of the image, in pixels */
virtual libdcp::Size size () const = 0;
+ virtual bool aligned () const = 0;
+
int components () const;
int lines (int) const;
@@ -107,6 +109,7 @@ public:
int * line_size () const;
int * stride () const;
libdcp::Size size () const;
+ bool aligned () const;
private:
/* Not allowed */
@@ -131,6 +134,7 @@ public:
int * line_size () const;
int * stride () const;
libdcp::Size size () const;
+ bool aligned () const;
protected:
void allocate ();
diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc
index 5ebd6c8e1..99b9e1d34 100644
--- a/src/lib/imagemagick_decoder.cc
+++ b/src/lib/imagemagick_decoder.cc
@@ -27,9 +27,10 @@
using std::cout;
using boost::shared_ptr;
+using libdcp::Size;
ImageMagickDecoder::ImageMagickDecoder (
- boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> o, Job* j)
+ boost::shared_ptr<Film> f, DecodeOptions o, Job* j)
: Decoder (f, o, j)
, VideoDecoder (f, o, j)
{
@@ -70,7 +71,7 @@ bool
ImageMagickDecoder::pass ()
{
if (_iter == _files.end()) {
- if (!_film->dcp_length() || video_frame() >= _film->dcp_length().get()) {
+ if (video_frame() >= _film->still_duration_in_frames()) {
return true;
}
@@ -97,7 +98,7 @@ ImageMagickDecoder::pass ()
delete magick_image;
- image = image->crop (_film->crop(), false);
+ image = image->crop (_film->crop(), true);
emit_video (image, 0);
diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h
index c4795b003..84a6f15f9 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 DecodeOptions>, Job *);
+ ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
float frames_per_second () const {
/* We don't know */
diff --git a/src/lib/log.cc b/src/lib/log.cc
index 06cff0495..7459700ea 100644
--- a/src/lib/log.cc
+++ b/src/lib/log.cc
@@ -28,7 +28,7 @@
using namespace std;
Log::Log ()
- : _level (VERBOSE)
+ : _level (STANDARD)
{
}
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
index 2b7a080fc..182fb306c 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 SimpleImage (_pixel_format.get(), _size.get(), false));
+ shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
black->make_black ();
for (int i = 0; i < black_video_frames; ++i) {
Video (black, i != 0, shared_ptr<Subtitle>());
diff --git a/src/lib/options.h b/src/lib/options.h
index 2f2f44b64..2cd7dffde 100644
--- a/src/lib/options.h
+++ b/src/lib/options.h
@@ -17,101 +17,12 @@
*/
-/** @file src/options.h
- * @brief Options for a transcoding operation.
- */
-
-#include <string>
-#include <iomanip>
-#include <sstream>
-#include <boost/optional.hpp>
-#include "util.h"
+#ifndef DVDOMATIC_OPTIONS_H
+#define DVDOMATIC_OPTIONS_H
-/** @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 operation.
+/** @file src/options.h
+ * @brief Options for a decoding operation.
*/
-class EncodeOptions
-{
-public:
-
- EncodeOptions (std::string f, std::string e, std::string m)
- : padding (0)
- , video_skip (0)
- , _frame_out_path (f)
- , _frame_out_extension (e)
- , _multichannel_audio_out_path (m)
- {}
-
- /** @return The path to write video frames to */
- std::string frame_out_path () const {
- return _frame_out_path;
- }
-
- /** @param f Source frame index.
- * @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) const {
- std::stringstream s;
- s << _frame_out_path << "/";
- s.width (8);
- s << std::setfill('0') << f << _frame_out_extension;
-
- if (t) {
- s << ".tmp";
- }
-
- 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;
- }
-
- /** @param c Audio channel index.
- * @param t true to return a temporary file path, otherwise a permanent one.
- * @return The path to write this audio file to.
- */
- std::string multichannel_audio_out_path (int c, bool t) const {
- std::stringstream s;
- s << _multichannel_audio_out_path << "/" << (c + 1) << ".wav";
- if (t) {
- s << ".tmp";
- }
-
- return s.str ();
- }
-
- libdcp::Size out_size; ///< size of output images
- int padding; ///< number of pixels of padding (in terms of the output size) each side of the image
-
- /** Range of video frames to encode (in DCP frames) */
- boost::optional<std::pair<int, int> > video_range;
- /** Range of audio frames to decode (in the DCP's sampling rate) */
- 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 video_skip;
-
-private:
- /** Path of the directory to write video frames to */
- std::string _frame_out_path;
- /** Extension to use for video frame files (including the leading .) */
- std::string _frame_out_extension;
- /** Path of the directory to write audio files to */
- std::string _multichannel_audio_out_path;
-};
-
class DecodeOptions
{
@@ -126,3 +37,5 @@ public:
bool decode_subtitles;
bool video_sync;
};
+
+#endif
diff --git a/src/lib/scp_dcp_job.cc b/src/lib/scp_dcp_job.cc
index 22129f56c..3d941888e 100644
--- a/src/lib/scp_dcp_job.cc
+++ b/src/lib/scp_dcp_job.cc
@@ -161,12 +161,7 @@ SCPDCPJob::run ()
for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (dcp_dir); i != boost::filesystem::directory_iterator(); ++i) {
- /* Aah, the sweet smell of progress */
-#if BOOST_FILESYSTEM_VERSION == 3
string const leaf = boost::filesystem::path(*i).leaf().generic_string ();
-#else
- string const leaf = i->leaf ();
-#endif
set_status ("copying " + leaf);
diff --git a/src/lib/server.cc b/src/lib/server.cc
index 1bb8f205e..d75ab0fb6 100644
--- a/src/lib/server.cc
+++ b/src/lib/server.cc
@@ -45,6 +45,7 @@ using boost::algorithm::is_any_of;
using boost::algorithm::split;
using boost::thread;
using boost::bind;
+using libdcp::Size;
/** Create a server description from a string of metadata returned from as_metadata().
* @param v Metadata.
diff --git a/src/lib/subtitle.cc b/src/lib/subtitle.cc
index b4ac14285..bd5f0c879 100644
--- a/src/lib/subtitle.cc
+++ b/src/lib/subtitle.cc
@@ -27,6 +27,7 @@
using namespace std;
using namespace boost;
+using libdcp::Size;
/** Construct a TimedSubtitle. This is a subtitle image, position,
* and a range of time over which it should be shown.
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index dfb9b1071..e9a59c743 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -37,13 +37,12 @@ using std::setprecision;
using boost::shared_ptr;
/** @param s Film to use.
- * @param o Options.
+ * @param o Decode options.
* @param req Job that must be completed before this job is run.
*/
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const DecodeOptions> od, shared_ptr<const EncodeOptions> oe, shared_ptr<Job> req)
+TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o, shared_ptr<Job> req)
: Job (f, req)
- , _decode_opt (od)
- , _encode_opt (oe)
+ , _decode_opt (o)
{
}
@@ -62,13 +61,16 @@ TranscodeJob::run ()
_film->log()->log ("Transcode job starting");
_film->log()->log (String::compose ("Audio delay is %1ms", _film->audio_delay()));
- _encoder.reset (new Encoder (_film, _encode_opt));
+ _encoder.reset (new Encoder (_film));
Transcoder w (_film, _decode_opt, this, _encoder);
w.go ();
set_progress (1);
set_state (FINISHED_OK);
+ _film->set_dcp_intrinsic_duration (_encoder->video_frames_out ());
+
_film->log()->log ("Transcode job completed successfully");
+ _film->log()->log (String::compose ("DCP intrinsic duration is %1", _encoder->video_frames_out()));
} catch (std::exception& e) {
@@ -87,11 +89,6 @@ TranscodeJob::status () const
return "0%";
}
- if (_encoder->skipping () && !finished ()) {
- return "skipping already-encoded frames";
- }
-
-
float const fps = _encoder->current_frames_per_second ();
if (fps == 0) {
return Job::status ();
@@ -116,11 +113,19 @@ TranscodeJob::remaining_time () const
return 0;
}
- if (!_film->dcp_length()) {
+ if (!_film->length()) {
return 0;
}
+ /* Compute approximate proposed length here, as it's only here that we need it */
+ int length = _film->length().get();
+ DCPFrameRate const dfr (_film->frames_per_second ());
+ if (dfr.skip) {
+ length /= 2;
+ }
+ /* If we are repeating it shouldn't affect transcode time, so don't take it into account */
+
/* 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();
+ int const left = length - _encoder->video_frames_out();
return left / fps;
}
diff --git a/src/lib/transcode_job.h b/src/lib/transcode_job.h
index 97f655e15..8f78e7f6a 100644
--- a/src/lib/transcode_job.h
+++ b/src/lib/transcode_job.h
@@ -23,10 +23,9 @@
#include <boost/shared_ptr.hpp>
#include "job.h"
+#include "options.h"
class Encoder;
-class DecodeOptions;
-class EncodeOptions;
/** @class TranscodeJob
* @brief A job which transcodes from one format to another.
@@ -34,7 +33,7 @@ class EncodeOptions;
class TranscodeJob : public Job
{
public:
- TranscodeJob (boost::shared_ptr<Film> f, boost::shared_ptr<const DecodeOptions> od, boost::shared_ptr<const EncodeOptions> oe, boost::shared_ptr<Job> req);
+ TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od, boost::shared_ptr<Job> req);
std::string name () const;
void run ();
@@ -44,7 +43,6 @@ protected:
int remaining_time () const;
private:
- boost::shared_ptr<const DecodeOptions> _decode_opt;
- boost::shared_ptr<const EncodeOptions> _encode_opt;
+ DecodeOptions _decode_opt;
boost::shared_ptr<Encoder> _encoder;
};
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 87a1fb3f2..93963761e 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -48,7 +48,7 @@ using boost::dynamic_pointer_cast;
* @param j Job that we are running under, or 0.
* @param e Encoder to use.
*/
-Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
: _job (j)
, _encoder (e)
, _decoders (decoder_factory (f, o, j))
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index b50113742..786010869 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -36,8 +36,6 @@ class Gain;
class VideoDecoder;
class AudioDecoder;
class DelayLine;
-class EncodeOptions;
-class DecodeOptions;
/** @class Transcoder
* @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
@@ -50,7 +48,7 @@ class Transcoder
public:
Transcoder (
boost::shared_ptr<Film> f,
- boost::shared_ptr<const DecodeOptions> o,
+ DecodeOptions o,
Job* j,
boost::shared_ptr<Encoder> e
);
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 7f370b896..340b76b57 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -26,6 +26,7 @@
#include <iomanip>
#include <iostream>
#include <fstream>
+#include <climits>
#ifdef DVDOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
@@ -58,9 +59,11 @@ extern "C" {
#include "dcp_content_type.h"
#include "filter.h"
#include "sound_processor.h"
+#include "config.h"
using namespace std;
using namespace boost;
+using libdcp::Size;
thread::id ui_thread;
@@ -230,6 +233,8 @@ seconds (struct timeval t)
void
dvdomatic_setup ()
{
+ avfilter_register_all ();
+
Format::setup_formats ();
DCPContentType::setup_dcp_content_types ();
Scaler::setup_scalers ();
@@ -330,25 +335,100 @@ md5_digest (string file)
return s.str ();
}
-/** @param fps Arbitrary frames-per-second value.
- * @return DCPFrameRate for this frames-per-second.
- */
-DCPFrameRate
-dcp_frame_rate (float fps)
+static bool about_equal (float a, float b)
+{
+ /* A film of F seconds at f FPS will be Ff frames;
+ Consider some delta FPS d, so if we run the same
+ film at (f + d) FPS it will last F(f + d) seconds.
+
+ Hence the difference in length over the length of the film will
+ be F(f + d) - Ff frames
+ = Ff + Fd - Ff frames
+ = Fd frames
+ = Fd/f seconds
+
+ So if we accept a difference of 1 frame, ie 1/f seconds, we can
+ say that
+
+ 1/f = Fd/f
+ ie 1 = Fd
+ ie d = 1/F
+
+ So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+ FPS error is 1/F ~= 0.0001 ~= 10-e4
+ */
+
+ return (fabs (a - b) < 1e-4);
+}
+
+class FrameRateCandidate
+{
+public:
+ FrameRateCandidate (float source_, int dcp_)
+ : source (source_)
+ , dcp (dcp_)
+ {}
+
+ bool skip () const {
+ return !about_equal (source, dcp) && source > dcp;
+ }
+
+ bool repeat () const {
+ return !about_equal (source, dcp) && source < dcp;
+ }
+
+ float source;
+ int dcp;
+};
+
+/** @param fps Arbitrary source frames-per-second value */
+/** XXX: this could be slow-ish */
+DCPFrameRate::DCPFrameRate (float source_fps)
{
- DCPFrameRate dfr;
+ list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
+
+ /* Work out what rates we could manage, including those achieved by using skip / repeat. */
+ list<FrameRateCandidate> candidates;
+
+ /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
+ for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+ candidates.push_back (FrameRateCandidate (*i, *i));
+ }
+
+ /* Then the skip/repeat ones */
+ for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
+ candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
+ candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
+ }
+
+ /* Pick the best one, bailing early if we hit an exact match */
+ float error = numeric_limits<float>::max ();
+ boost::optional<FrameRateCandidate> best;
+ list<FrameRateCandidate>::iterator i = candidates.begin();
+ while (i != candidates.end()) {
+
+ if (about_equal (i->source, source_fps)) {
+ best = *i;
+ break;
+ }
- dfr.run_fast = (fps != rint (fps));
- dfr.frames_per_second = rint (fps);
- dfr.skip = 1;
+ float const e = fabs (i->source - source_fps);
+ if (e < error) {
+ error = e;
+ best = *i;
+ }
- /* XXX: somewhat arbitrary */
- if (fps == 50) {
- dfr.frames_per_second = 25;
- dfr.skip = 2;
+ ++i;
}
- return dfr;
+ if (!best) {
+ throw EncodeError ("cannot find a suitable DCP frame rate for this source");
+ }
+
+ frames_per_second = best->dcp;
+ skip = best->skip ();
+ repeat = best->repeat ();
+ change_speed = !about_equal (source_fps * factor(), frames_per_second);
}
/** @param An arbitrary sampling rate.
@@ -542,6 +622,9 @@ Socket::read_definite_and_consume (uint8_t* data, int size, int timeout)
/** Read as much data as is available, up to some limit.
* @param data Where to put the data.
* @param size Maximum amount of data to read.
+ *
+ * XXX This method assumes that there is always lots of data to read();
+ * if there isn't, it will hang waiting for data that will never arrive.
*/
void
Socket::read_indefinite (uint8_t* data, int size, int timeout)
@@ -854,11 +937,7 @@ video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float fram
bool
still_image_file (string f)
{
-#if BOOST_FILESYSTEM_VERSION == 3
string ext = boost::filesystem::path(f).extension().string();
-#else
- string ext = boost::filesystem::path(f).extension();
-#endif
transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
diff --git a/src/lib/util.h b/src/lib/util.h
index 77fb943e0..c4940a5d7 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -62,16 +62,37 @@ typedef int SourceFrame;
struct DCPFrameRate
{
+ DCPFrameRate (float);
+
+ /** @return factor by which to multiply a source frame rate
+ to get the effective rate after any skip or repeat has happened.
+ */
+ float factor () const {
+ if (skip) {
+ return 0.5;
+ } else if (repeat) {
+ return 2;
+ }
+
+ return 1;
+ }
+
/** frames per second for the DCP */
int frames_per_second;
- /** Skip every `skip' frames. e.g. if this is 1, we skip nothing;
- * if it's 2, we skip every other frame.
- */
- int skip;
- /** true if this DCP will run its video faster than the source
- * (e.g. if the source is 29.97fps and we will run the DCP at 30fps)
+ /** true to skip every other frame */
+ bool skip;
+ /** true to repeat every frame once */
+ bool repeat;
+ /** true if this DCP will run its video faster or slower than the source
+ * without taking into account `repeat'.
+ * (e.g. change_speed will be true if
+ * source is 29.97fps, DCP is 30fps
+ * source is 14.50fps, DCP is 30fps
+ * but not if
+ * source is 15.00fps, DCP is 30fps
+ * source is 12.50fps, DCP is 25fps)
*/
- bool run_fast;
+ bool change_speed;
};
enum ContentType {
@@ -157,7 +178,6 @@ struct Rect
extern std::string crop_string (Position, libdcp::Size);
extern int dcp_audio_sample_rate (int);
-extern DCPFrameRate dcp_frame_rate (float);
extern int dcp_audio_channels (int);
extern std::string colour_lut_index_to_name (int index);
extern int stride_round_up (int, int const *, int);
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index e0a7576ee..0fa16bc32 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -28,7 +28,7 @@
using boost::shared_ptr;
using boost::optional;
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
+VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o, Job* j)
: Decoder (f, o, j)
, _video_frame (0)
, _last_source_time (0)
@@ -57,7 +57,7 @@ void
VideoDecoder::repeat_last_video ()
{
if (!_last_image) {
- _last_image.reset (new SimpleImage (pixel_format(), native_size(), false));
+ _last_image.reset (new SimpleImage (pixel_format(), native_size(), true));
_last_image->make_black ();
}
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index b18082c69..ef1ab041a 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -27,7 +27,7 @@
class VideoDecoder : public VideoSource, public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
+ VideoDecoder (boost::shared_ptr<Film>, DecodeOptions, Job *);
/** @return video frames per second, or 0 if unknown */
virtual float frames_per_second () const = 0;
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
new file mode 100644
index 000000000..5bd32147a
--- /dev/null
+++ b/src/lib/writer.cc
@@ -0,0 +1,361 @@
+/*
+ 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.
+
+*/
+
+#include <fstream>
+#include <libdcp/picture_asset.h>
+#include <libdcp/sound_asset.h>
+#include <libdcp/picture_frame.h>
+#include <libdcp/reel.h>
+#include "writer.h"
+#include "compose.hpp"
+#include "film.h"
+#include "format.h"
+#include "log.h"
+#include "dcp_video_frame.h"
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::ifstream;
+using std::cout;
+using boost::shared_ptr;
+
+unsigned int const Writer::_maximum_frames_in_memory = 8;
+
+Writer::Writer (shared_ptr<Film> f)
+ : _film (f)
+ , _first_nonexistant_frame (0)
+ , _thread (0)
+ , _finish (false)
+ , _last_written_frame (-1)
+{
+ check_existing_picture_mxf ();
+
+ /* Create our picture asset in a subdirectory, named according to those
+ film's parameters which affect the video output. We will hard-link
+ it into the DCP later.
+ */
+
+ _picture_asset.reset (
+ new libdcp::MonoPictureAsset (
+ _film->video_mxf_dir (),
+ _film->video_mxf_filename (),
+ DCPFrameRate (_film->frames_per_second()).frames_per_second,
+ _film->format()->dcp_size()
+ )
+ );
+
+ _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+
+ if (_film->audio_channels() > 0) {
+ _sound_asset.reset (
+ new libdcp::SoundAsset (
+ _film->dir (_film->dcp_name()),
+ "audio.mxf",
+ DCPFrameRate (_film->frames_per_second()).frames_per_second,
+ _film->audio_channels(),
+ dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
+ )
+ );
+
+ _sound_asset_writer = _sound_asset->start_write ();
+ }
+
+ _thread = new boost::thread (boost::bind (&Writer::thread, this));
+}
+
+void
+Writer::write (shared_ptr<const EncodedData> encoded, int frame)
+{
+ boost::mutex::scoped_lock lock (_mutex);
+
+ QueueItem qi;
+ qi.type = QueueItem::FULL;
+ qi.encoded = encoded;
+ qi.frame = frame;
+ _queue.push_back (qi);
+
+ _condition.notify_all ();
+}
+
+void
+Writer::fake_write (int frame)
+{
+ boost::mutex::scoped_lock lock (_mutex);
+
+ ifstream ifi (_film->info_path (frame).c_str());
+ libdcp::FrameInfo info (ifi);
+
+ QueueItem qi;
+ qi.type = QueueItem::FAKE;
+ qi.size = info.size;
+ qi.frame = frame;
+ _queue.push_back (qi);
+
+ _condition.notify_all ();
+}
+
+/** This method is not thread safe */
+void
+Writer::write (shared_ptr<const AudioBuffers> audio)
+{
+ _sound_asset_writer->write (audio->data(), audio->frames());
+}
+
+void
+Writer::thread ()
+{
+ while (1)
+ {
+ boost::mutex::scoped_lock lock (_mutex);
+
+ while (1) {
+ if (_finish ||
+ _queue.size() > _maximum_frames_in_memory ||
+ (!_queue.empty() && _queue.front().frame == (_last_written_frame + 1))) {
+
+ break;
+ }
+
+ TIMING ("writer sleeps with a queue of %1; %2 pending", _queue.size(), _pending.size());
+ _condition.wait (lock);
+ TIMING ("writer wakes with a queue of %1", _queue.size());
+
+ _queue.sort ();
+ }
+
+ if (_finish && _queue.empty() && _pending.empty()) {
+ return;
+ }
+
+ /* Write any frames that we can write; i.e. those that are in sequence */
+ while (!_queue.empty() && _queue.front().frame == (_last_written_frame + 1)) {
+ QueueItem qi = _queue.front ();
+ _queue.pop_front ();
+
+ lock.unlock ();
+ switch (qi.type) {
+ case QueueItem::FULL:
+ {
+ _film->log()->log (String::compose ("Writer FULL-writes %1 to MXF", qi.frame));
+ libdcp::FrameInfo const fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
+ qi.encoded->write_info (_film, qi.frame, fin);
+ _last_written = qi.encoded;
+ break;
+ }
+ case QueueItem::FAKE:
+ _film->log()->log (String::compose ("Writer FAKE-writes %1 to MXF", qi.frame));
+ _picture_asset_writer->fake_write (qi.size);
+ _last_written.reset ();
+ break;
+ case QueueItem::REPEAT:
+ {
+ _film->log()->log (String::compose ("Writer REPEAT-writes %1 to MXF", qi.frame));
+ libdcp::FrameInfo const fin = _picture_asset_writer->write (_last_written->data(), _last_written->size());
+ _last_written->write_info (_film, qi.frame, fin);
+ break;
+ }
+ }
+ lock.lock ();
+
+ ++_last_written_frame;
+ }
+
+ while (_queue.size() > _maximum_frames_in_memory) {
+ /* Too many frames in memory which can't yet be written to the stream.
+ Put some in our pending list (and write FULL queue items' data to disk)
+ */
+
+ QueueItem qi = _queue.back ();
+ _queue.pop_back ();
+
+ if (qi.type == QueueItem::FULL) {
+ lock.unlock ();
+ _film->log()->log (String::compose ("Writer full (awaiting %1); pushes %2 to disk", _last_written_frame + 1, qi.frame));
+ qi.encoded->write (_film, qi.frame);
+ lock.lock ();
+ qi.encoded.reset ();
+ }
+
+ _pending.push_back (qi);
+ }
+
+ while (_queue.size() < _maximum_frames_in_memory && !_pending.empty()) {
+ /* We have some space in memory. Fetch some frames back off disk. */
+
+ _pending.sort ();
+ QueueItem qi = _pending.front ();
+
+ if (qi.type == QueueItem::FULL) {
+ lock.unlock ();
+ _film->log()->log (String::compose ("Writer pulls %1 back from disk", qi.frame));
+ shared_ptr<const EncodedData> encoded;
+ qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, false)));
+ lock.lock ();
+ }
+
+ _queue.push_back (qi);
+ _pending.remove (qi);
+ }
+ }
+
+}
+
+void
+Writer::finish ()
+{
+ if (!_thread) {
+ return;
+ }
+
+ boost::mutex::scoped_lock lock (_mutex);
+ _finish = true;
+ _condition.notify_all ();
+ lock.unlock ();
+
+ _thread->join ();
+ delete _thread;
+ _thread = 0;
+
+ _picture_asset_writer->finalize ();
+
+ if (_sound_asset_writer) {
+ _sound_asset_writer->finalize ();
+ }
+
+ int const frames = _last_written_frame + 1;
+ int const duration = frames - _film->trim_start() - _film->trim_end();
+
+ _film->set_dcp_intrinsic_duration (frames);
+
+ _picture_asset->set_entry_point (_film->trim_start ());
+ _picture_asset->set_duration (duration);
+
+ /* Hard-link the video MXF into the DCP */
+
+ boost::filesystem::path from;
+ from /= _film->video_mxf_dir();
+ from /= _film->video_mxf_filename();
+
+ boost::filesystem::path to;
+ to /= _film->dir (_film->dcp_name());
+ to /= "video.mxf";
+
+ boost::filesystem::create_hard_link (from, to);
+
+ /* And update the asset */
+
+ _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
+ _picture_asset->set_file_name ("video.mxf");
+
+ if (_sound_asset) {
+ _sound_asset->set_entry_point (_film->trim_start ());
+ _sound_asset->set_duration (duration);
+ }
+
+ libdcp::DCP dcp (_film->dir (_film->dcp_name()));
+ DCPFrameRate dfr (_film->frames_per_second ());
+
+ shared_ptr<libdcp::CPL> cpl (
+ new libdcp::CPL (_film->dir (_film->dcp_name()), _film->dcp_name(), _film->dcp_content_type()->libdcp_kind (), frames, dfr.frames_per_second)
+ );
+
+ dcp.add_cpl (cpl);
+
+ cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
+ _picture_asset,
+ _sound_asset,
+ shared_ptr<libdcp::SubtitleAsset> ()
+ )
+ ));
+
+ dcp.write_xml ();
+}
+
+/** Tell the writer that frame `f' should be a repeat of the frame before it */
+void
+Writer::repeat (int f)
+{
+ boost::mutex::scoped_lock lock (_mutex);
+
+ QueueItem qi;
+ qi.type = QueueItem::REPEAT;
+ qi.frame = f;
+
+ _queue.push_back (qi);
+
+ _condition.notify_all ();
+}
+
+
+void
+Writer::check_existing_picture_mxf ()
+{
+ /* Try to open the existing MXF */
+ boost::filesystem::path p;
+ p /= _film->video_mxf_dir ();
+ p /= _film->video_mxf_filename ();
+ FILE* mxf = fopen (p.string().c_str(), "rb");
+ if (!mxf) {
+ return;
+ }
+
+ while (1) {
+
+ /* Read the frame info as written */
+ ifstream ifi (_film->info_path (_first_nonexistant_frame).c_str());
+ libdcp::FrameInfo info (ifi);
+
+ /* Read the data from the MXF and hash it */
+ fseek (mxf, info.offset, SEEK_SET);
+ EncodedData data (info.size);
+ fread (data.data(), 1, data.size(), mxf);
+ string const existing_hash = md5_digest (data.data(), data.size());
+
+ if (existing_hash != info.hash) {
+ _film->log()->log (String::compose ("Existing frame %1 failed hash check", _first_nonexistant_frame));
+ break;
+ }
+
+ _film->log()->log (String::compose ("Have existing frame %1", _first_nonexistant_frame));
+ ++_first_nonexistant_frame;
+ }
+
+ fclose (mxf);
+}
+
+/** @return true if the fake write succeeded, otherwise false */
+bool
+Writer::can_fake_write (int frame) const
+{
+ return (frame != 0 && frame < _first_nonexistant_frame);
+}
+
+
+bool
+operator< (QueueItem const & a, QueueItem const & b)
+{
+ return a.frame < b.frame;
+}
+
+bool
+operator== (QueueItem const & a, QueueItem const & b)
+{
+ return a.frame == b.frame;
+}
diff --git a/src/lib/writer.h b/src/lib/writer.h
new file mode 100644
index 000000000..57609825d
--- /dev/null
+++ b/src/lib/writer.h
@@ -0,0 +1,91 @@
+/*
+ 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.
+
+*/
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/condition.hpp>
+
+class Film;
+class EncodedData;
+class AudioBuffers;
+
+namespace libdcp {
+ class MonoPictureAsset;
+ class MonoPictureAssetWriter;
+ class SoundAsset;
+ class SoundAssetWriter;
+}
+
+struct QueueItem
+{
+public:
+ enum Type {
+ FULL,
+ FAKE,
+ REPEAT
+ } type;
+
+ /** encoded data for FULL */
+ boost::shared_ptr<const EncodedData> encoded;
+ /** size of data for FAKE */
+ int size;
+ /** frame index */
+ int frame;
+};
+
+bool operator< (QueueItem const & a, QueueItem const & b);
+bool operator== (QueueItem const & a, QueueItem const & b);
+
+class Writer
+{
+public:
+ Writer (boost::shared_ptr<Film>);
+
+ bool can_fake_write (int) const;
+
+ void write (boost::shared_ptr<const EncodedData>, int);
+ void fake_write (int);
+ void write (boost::shared_ptr<const AudioBuffers>);
+ void repeat (int f);
+ void finish ();
+
+private:
+
+ void thread ();
+ void check_existing_picture_mxf ();
+
+ boost::shared_ptr<Film> _film;
+ int _first_nonexistant_frame;
+
+ boost::thread* _thread;
+ bool _finish;
+ std::list<QueueItem> _queue;
+ mutable boost::mutex _mutex;
+ boost::condition _condition;
+ boost::shared_ptr<const EncodedData> _last_written;
+ std::list<QueueItem> _pending;
+ int _last_written_frame;
+ static const unsigned int _maximum_frames_in_memory;
+
+ boost::shared_ptr<libdcp::MonoPictureAsset> _picture_asset;
+ boost::shared_ptr<libdcp::MonoPictureAssetWriter> _picture_asset_writer;
+ boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
+ boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+};
diff --git a/src/lib/wscript b/src/lib/wscript
index cada2b0c3..454565cdc 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -14,7 +14,6 @@ def build(bld):
ab_transcoder.cc
audio_decoder.cc
audio_source.cc
- check_hashes_job.cc
config.cc
combiner.cc
cross.cc
@@ -41,7 +40,6 @@ def build(bld):
job_manager.cc
log.cc
lut.cc
- make_dcp_job.cc
matcher.cc
scp_dcp_job.cc
scaler.cc
@@ -57,6 +55,7 @@ def build(bld):
version.cc
video_decoder.cc
video_source.cc
+ writer.cc
"""
obj.target = 'dvdomatic'