doc/manual/extensions.ent
.be/id-cache
*.pyc
-
+GPATH
+GRTAGS
+GSYMS
+GTAGS
+2013-02-02 Carl Hetherington <cth@carlh.net>
+
+ * Tidy up filters dialog by not showing those
+ that are not configured in FFmpeg, and by splitting
+ them up into categories.
+
+ * Fix infinite loop of error messages when
+ `playing back' using a non-existant filter (#39).
+
+ * Encode data straight to MXFs, rather
+ than going via .j2c files. Should roughly
+ halve required disk space and reduce time
+ taken.
+
2013-01-25 Carl Hetherington <cth@carlh.net>
* When using formats which pad smaller frames into
--- /dev/null
+
+... perhaps generate the CPL hash on the fly
+Make check of hashes optional; recovery in general
+Fix multi-reel or remove it
\ No newline at end of file
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 ());
{
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);
#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.
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
);
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;
*/
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)
class Encoder;
class VideoDecoder;
class AudioDecoder;
-class DecodeOptions;
class Image;
class Log;
class Subtitle;
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
);
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)
{
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>);
+++ /dev/null
-/*
- 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 ();
-}
+++ /dev/null
-/*
- 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;
-};
, _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)) {
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;
}
_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;
}
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;
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 ())
*/
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)
, _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)
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.
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;
+
}
*/
#include <openjpeg.h>
+#include <libdcp/picture_asset.h>
#include "util.h"
/** @file src/dcp_video_frame.h
*/
class FilmState;
-class EncodeOptions;
+class Film;
class ServerDescription;
class Scaler;
class Image;
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 {
protected:
uint8_t* _data; ///< data
int _size; ///< data size in bytes
+
+private:
+ /* No copy construction */
+ EncodedData (EncodedData const &);
};
/** @class LocallyEncodedData
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
{
public:
RemotelyEncodedData (int s);
- ~RemotelyEncodedData ();
};
/** @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 ();
boost::shared_ptr<EncodedData> encode_locally ();
boost::shared_ptr<EncodedData> encode_remotely (ServerDescription const *);
- SourceFrame frame () const {
+ int frame () const {
return _frame;
}
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
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)
#include "video_source.h"
#include "audio_source.h"
#include "film.h"
+#include "options.h"
class Job;
-class DecodeOptions;
class Image;
class Log;
class DelayLine;
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;
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;
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) {
* @brief A method to create appropriate decoders for some content.
*/
+#include "options.h"
+
class Film;
-class DecodeOptions;
class Job;
class VideoDecoder;
class AudioDecoder;
};
extern Decoders decoder_factory (
- boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *
+ boost::shared_ptr<Film>, DecodeOptions, Job *
);
#endif
Audio (data);
}
-
-void
-DelayLine::process_end ()
-{
- if (_frames < 0) {
- _buffers->make_silent ();
- Audio (_buffers);
- }
-}
DelayLine (Log* log, int channels, int frames);
void process_audio (boost::shared_ptr<AudioBuffers>);
- void process_end ();
private:
boost::shared_ptr<AudioBuffers> _buffers;
#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"
#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;
/** @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
_worker_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, *i)));
}
}
+
+ _writer.reset (new Writer (_film));
}
}
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);
}
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
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,
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.
Encoder::frame_done ()
{
boost::mutex::scoped_lock lock (_history_mutex);
- _just_skipped = false;
struct timeval tv;
gettimeofday (&tv, 0);
}
}
-/** 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) {
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 ();
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 ();
}
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 ();
}
_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
-}
#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.
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 */
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.
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
_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);
/* 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()));
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)
{
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 ();
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)
setup_audio ();
setup_subtitle ();
- if (!o->video_sync) {
+ if (!o.video_sync) {
_first_video = 0;
}
}
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
_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) {
}
}
- } 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;
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;
#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"
#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"
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
, _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)
, _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)
, _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)
{
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()
<< "_" << 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.
*/
}
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()));
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 */
}
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 ();
}
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";
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) {
_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")) {
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")) {
/* 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 */
{
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;
*/
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 ());
}
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
_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 ();
}
_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)
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 ();
+}
+
+
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 ();
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;
CROP,
FILTERS,
SCALER,
- DCP_TRIM_START,
- DCP_TRIM_END,
+ TRIM_START,
+ TRIM_END,
DCP_AB,
CONTENT_AUDIO_STREAM,
EXTERNAL_AUDIO,
DCI_METADATA,
SIZE,
LENGTH,
+ DCP_INTRINSIC_DURATION,
CONTENT_AUDIO_STREAMS,
SUBTITLE_STREAMS,
FRAMES_PER_SECOND,
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 {
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;
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>);
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> >);
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.
/** 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.
/* 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 */
*/
#include "filter.h"
+extern "C" {
+#include <libavfilter/avfilter.h>
+#include <libpostproc/postprocess.h>
+}
using namespace std;
/** @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)
{
{
/* 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.
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 {
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);
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 */
/** all available filters */
static std::vector<Filter const *> _filters;
+ static void maybe_add (std::string, std::string, std::string, std::string, std::string);
};
#endif
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.
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.");
using std::stringstream;
using std::vector;
using boost::shared_ptr;
+using libdcp::Size;
vector<Format const *> Format::_formats;
using namespace std;
using namespace boost;
+using libdcp::Size;
void
Image::swap (Image& other)
}
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(),
* @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(),
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
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];
}
}
{
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;
return 0.5;
}
case PIX_FMT_YUV422P10LE:
- if (c == 1) {
+ if (c == 0) {
return 2;
} else {
return 1;
return _size;
}
+bool
+SimpleImage::aligned () const
+{
+ return _aligned;
+}
+
FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b)
: Image (p)
, _buffer (b)
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)
{
/** @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;
int * line_size () const;
int * stride () const;
libdcp::Size size () const;
+ bool aligned () const;
private:
/* Not allowed */
int * line_size () const;
int * stride () const;
libdcp::Size size () const;
+ bool aligned () const;
protected:
void allocate ();
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)
{
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;
}
delete magick_image;
- image = image->crop (_film->crop(), false);
+ image = image->crop (_film->crop(), true);
emit_video (image, 0);
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 */
using namespace std;
Log::Log ()
- : _level (VERBOSE)
+ : _level (STANDARD)
{
}
_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>());
*/
-/** @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
{
bool decode_subtitles;
bool video_sync;
};
+
+#endif
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);
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.
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.
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)
{
}
_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) {
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 ();
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;
}
#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.
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 ();
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;
};
* @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))
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.
public:
Transcoder (
boost::shared_ptr<Film> f,
- boost::shared_ptr<const DecodeOptions> o,
+ DecodeOptions o,
Job* j,
boost::shared_ptr<Encoder> e
);
#include <iomanip>
#include <iostream>
#include <fstream>
+#include <climits>
#ifdef DVDOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
#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;
void
dvdomatic_setup ()
{
+ avfilter_register_all ();
+
Format::setup_formats ();
DCPContentType::setup_dcp_content_types ();
Scaler::setup_scalers ();
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.
/** 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)
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);
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 {
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);
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)
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 ();
}
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;
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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;
+};
ab_transcoder.cc
audio_decoder.cc
audio_source.cc
- check_hashes_job.cc
config.cc
combiner.cc
cross.cc
job_manager.cc
log.cc
lut.cc
- make_dcp_job.cc
matcher.cc
scp_dcp_job.cc
scaler.cc
version.cc
video_decoder.cc
video_source.cc
+ writer.cc
"""
obj.target = 'dvdomatic'
{
stringstream s;
s << "Save changes to film \"" << film->name() << "\" before closing?";
- _dialog = new wxMessageDialog (0, std_to_wx (s.str()), wxT ("Film changed"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
+ _dialog = new wxMessageDialog (0, std_to_wx (s.str()), _("Film changed"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
}
~FilmChangedDialog ()
if (r == wxID_OK) {
if (boost::filesystem::exists (d->get_path())) {
- error_dialog (this, String::compose ("The directory %1 already exists.", d->get_path()));
+ error_dialog (this, wxString::Format (_("The directory %s already exists"), d->get_path().c_str()));
return;
}
maybe_save_then_delete_film ();
film.reset (new Film (d->get_path (), false));
film->log()->set_level (log_level);
-#if BOOST_FILESYSTEM_VERSION == 3
film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
-#else
- film->set_name (boost::filesystem::path (d->get_path()).filename());
-#endif
set_film ();
}
void file_open (wxCommandEvent &)
{
- wxDirDialog* c = new wxDirDialog (this, wxT ("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST);
+ wxDirDialog* c = new wxDirDialog (this, _("Select film to open"), wxStandardPaths::Get().GetDocumentsDir(), wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST);
int const r = c->ShowModal ();
if (r == wxID_OK) {
film->log()->set_level (log_level);
set_film ();
} catch (std::exception& e) {
- error_dialog (this, String::compose ("Could not open film at %1 (%2)", wx_to_std (c->GetPath()), e.what()));
+ wxString p = c->GetPath ();
+ wxCharBuffer b = p.ToUTF8 ();
+ error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), e.what()));
}
}
#include "film.h"
#include "filter.h"
#include "transcode_job.h"
-#include "make_dcp_job.h"
#include "job_manager.h"
#include "ab_transcode_job.h"
#include "util.h"
bool should_stop = false;
bool first = true;
+ bool error = false;
while (!should_stop) {
dvdomatic_sleep (5);
if ((*i)->finished_in_error ()) {
++finished_in_error;
+ error = true;
}
if (!progress && (*i)->finished_in_error ()) {
}
}
- return 0;
+ return error ? EXIT_FAILURE : EXIT_SUCCESS;
}
wxFlexGridSizer* table = new wxFlexGridSizer (3, 6, 6);
table->AddGrowableCol (1, 1);
- add_label_to_sizer (table, this, "TMS IP address");
+ add_label_to_sizer (table, this, _("TMS IP address"));
_tms_ip = new wxTextCtrl (this, wxID_ANY);
table->Add (_tms_ip, 1, wxEXPAND);
table->AddSpacer (0);
- add_label_to_sizer (table, this, "TMS target path");
+ add_label_to_sizer (table, this, _("TMS target path"));
_tms_path = new wxTextCtrl (this, wxID_ANY);
table->Add (_tms_path, 1, wxEXPAND);
table->AddSpacer (0);
- add_label_to_sizer (table, this, "TMS user name");
+ add_label_to_sizer (table, this, _("TMS user name"));
_tms_user = new wxTextCtrl (this, wxID_ANY);
table->Add (_tms_user, 1, wxEXPAND);
table->AddSpacer (0);
- add_label_to_sizer (table, this, "TMS password");
+ add_label_to_sizer (table, this, _("TMS password"));
_tms_password = new wxTextCtrl (this, wxID_ANY);
table->Add (_tms_password, 1, wxEXPAND);
table->AddSpacer (0);
- add_label_to_sizer (table, this, "Threads to use for encoding on this host");
+ add_label_to_sizer (table, this, _("Threads to use for encoding on this host"));
_num_local_encoding_threads = new wxSpinCtrl (this);
table->Add (_num_local_encoding_threads, 1, wxEXPAND);
table->AddSpacer (0);
- add_label_to_sizer (table, this, "Default directory for new films");
+ add_label_to_sizer (table, this, _("Default directory for new films"));
#ifdef __WXMSW__
_default_directory = new DirPickerCtrl (this);
#else
table->Add (_default_directory, 1, wxEXPAND);
table->AddSpacer (0);
- add_label_to_sizer (table, this, "Default DCI name details");
+ add_label_to_sizer (table, this, _("Default DCI name details"));
_default_dci_metadata_button = new wxButton (this, wxID_ANY, _("Edit..."));
table->Add (_default_dci_metadata_button);
table->AddSpacer (1);
- add_label_to_sizer (table, this, "Reference scaler for A/B");
+ add_label_to_sizer (table, this, _("Reference scaler for A/B"));
_reference_scaler = new wxChoice (this, wxID_ANY);
vector<Scaler const *> const sc = Scaler::all ();
for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
table->AddSpacer (0);
{
- add_label_to_sizer (table, this, "Reference filters for A/B");
+ add_label_to_sizer (table, this, _("Reference filters for A/B"));
wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_reference_filters = new wxStaticText (this, wxID_ANY, wxT (""));
s->Add (_reference_filters, 1, wxEXPAND);
table->AddSpacer (0);
}
- add_label_to_sizer (table, this, "Encoding Servers");
+ add_label_to_sizer (table, this, _("Encoding Servers"));
_servers = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (220, 100), wxLC_REPORT | wxLC_SINGLE_SEL);
wxListItem ip;
ip.SetId (0);
wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
table->AddGrowableCol (1, 1);
- add_label_to_sizer (table, this, "Audio Language (e.g. EN)");
+ add_label_to_sizer (table, this, _("Audio Language (e.g. EN)"));
_audio_language = new wxTextCtrl (this, wxID_ANY);
table->Add (_audio_language, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Subtitle Language (e.g. FR)");
+ add_label_to_sizer (table, this, _("Subtitle Language (e.g. FR)"));
_subtitle_language = new wxTextCtrl (this, wxID_ANY);
table->Add (_subtitle_language, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Territory (e.g. UK)");
+ add_label_to_sizer (table, this, _("Territory (e.g. UK)"));
_territory = new wxTextCtrl (this, wxID_ANY);
table->Add (_territory, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Rating (e.g. 15)");
+ add_label_to_sizer (table, this, _("Rating (e.g. 15)"));
_rating = new wxTextCtrl (this, wxID_ANY);
table->Add (_rating, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Studio (e.g. TCF)");
+ add_label_to_sizer (table, this, _("Studio (e.g. TCF)"));
_studio = new wxTextCtrl (this, wxID_ANY);
table->Add (_studio, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Facility (e.g. DLA)");
+ add_label_to_sizer (table, this, _("Facility (e.g. DLA)"));
_facility = new wxTextCtrl (this, wxID_ANY);
table->Add (_facility, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Package Type (e.g. OV)");
+ add_label_to_sizer (table, this, _("Package Type (e.g. OV)"));
_package_type = new wxTextCtrl (this, wxID_ANY);
table->Add (_package_type, 1, wxEXPAND);
if (_path == wxStandardPaths::Get().GetDocumentsDir()) {
_folder->SetLabel (_("My Documents"));
} else {
-#if BOOST_FILESYSTEM_VERSION == 3
_folder->SetLabel (std_to_wx (filesystem::path (wx_to_std (_path)).leaf().string()));
-#else
- _folder->SetLabel (std_to_wx (filesystem::path (wx_to_std (_path)).leaf()));
-#endif
}
}
FilmEditor::make_film_panel ()
{
_film_panel = new wxPanel (_notebook);
- _film_sizer = new wxFlexGridSizer (2, 4, 4);
- wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
- pad->Add (_film_sizer, 0, wxALL, 8);
- _film_panel->SetSizer (pad);
+ _film_sizer = new wxBoxSizer (wxVERTICAL);
+ _film_panel->SetSizer (_film_sizer);
- add_label_to_sizer (_film_sizer, _film_panel, "Name");
+ wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+ _film_sizer->Add (grid, 0, wxALL, 8);
+
+ add_label_to_sizer (grid, _film_panel, _("Name"));
_name = new wxTextCtrl (_film_panel, wxID_ANY);
- _film_sizer->Add (_name, 1, wxEXPAND);
+ grid->Add (_name, 1, wxEXPAND);
- add_label_to_sizer (_film_sizer, _film_panel, "DCP Name");
+ add_label_to_sizer (grid, _film_panel, _("DCP Name"));
_dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
- _film_sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK);
+ grid->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK);
- _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Use DCI name"));
- _film_sizer->Add (_use_dci_name, 1, wxEXPAND);
- _edit_dci_button = new wxButton (_film_panel, wxID_ANY, wxT ("Details..."));
- _film_sizer->Add (_edit_dci_button, 0);
+ _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name"));
+ grid->Add (_use_dci_name, 1, wxEXPAND);
+ _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details..."));
+ grid->Add (_edit_dci_button, 0);
- add_label_to_sizer (_film_sizer, _film_panel, "Content");
- _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
- _film_sizer->Add (_content, 1, wxEXPAND);
+ add_label_to_sizer (grid, _film_panel, _("Content"));
+ _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*"));
+ grid->Add (_content, 1, wxEXPAND);
- _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header"));
+ _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
video_control (_trust_content_header);
- _film_sizer->Add (_trust_content_header, 1);
- _film_sizer->AddSpacer (0);
+ grid->Add (_trust_content_header, 1);
+ grid->AddSpacer (0);
- add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
+ add_label_to_sizer (grid, _film_panel, _("Content Type"));
_dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
- _film_sizer->Add (_dcp_content_type);
+ grid->Add (_dcp_content_type);
- video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second"));
+ video_control (add_label_to_sizer (grid, _film_panel, _("Frames Per Second")));
_frames_per_second = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
- _film_sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
+ grid->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
- video_control (add_label_to_sizer (_film_sizer, _film_panel, "Original Size"));
+ video_control (add_label_to_sizer (grid, _film_panel, _("Original Size")));
_original_size = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
- _film_sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
+ grid->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
- video_control (add_label_to_sizer (_film_sizer, _film_panel, "Length"));
+ video_control (add_label_to_sizer (grid, _film_panel, _("Length")));
_length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
- _film_sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
+ grid->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
{
- video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames"));
+ video_control (add_label_to_sizer (grid, _film_panel, _("Trim frames")));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- video_control (add_label_to_sizer (s, _film_panel, "Start"));
- _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (video_control (_dcp_trim_start));
- video_control (add_label_to_sizer (s, _film_panel, "End"));
- _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
- s->Add (video_control (_dcp_trim_end));
+ video_control (add_label_to_sizer (s, _film_panel, _("Start")));
+ _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ s->Add (video_control (_trim_start));
+ video_control (add_label_to_sizer (s, _film_panel, _("End")));
+ _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
+ s->Add (video_control (_trim_end));
- _film_sizer->Add (s);
+ grid->Add (s);
}
- _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, wxT ("A/B"));
+ _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
video_control (_dcp_ab);
- _film_sizer->Add (_dcp_ab, 1);
- _film_sizer->AddSpacer (0);
+ grid->Add (_dcp_ab, 1);
+ grid->AddSpacer (0);
/* STILL-only stuff */
{
- still_control (add_label_to_sizer (_film_sizer, _film_panel, "Duration"));
+ still_control (add_label_to_sizer (grid, _film_panel, _("Duration")));
wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_still_duration = new wxSpinCtrl (_film_panel);
still_control (_still_duration);
s->Add (_still_duration, 1, wxEXPAND);
- still_control (add_label_to_sizer (s, _film_panel, "s"));
- _film_sizer->Add (s);
+ /* TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time */
+ still_control (add_label_to_sizer (s, _film_panel, _("s")));
+ grid->Add (s);
}
vector<DCPContentType const *> const ct = DCPContentType::all ();
_dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
_dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
_still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
- _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
- _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
+ _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
+ _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
_with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
_subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
_subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
FilmEditor::make_video_panel ()
{
_video_panel = new wxPanel (_notebook);
- _video_sizer = new wxFlexGridSizer (2, 4, 4);
- wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
- pad->Add (_video_sizer, 0, wxALL, 8);
- _video_panel->SetSizer (pad);
+ _video_sizer = new wxBoxSizer (wxVERTICAL);
+ _video_panel->SetSizer (_video_sizer);
+
+ wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+ _video_sizer->Add (grid, 0, wxALL, 8);
- add_label_to_sizer (_video_sizer, _video_panel, "Format");
+ add_label_to_sizer (grid, _video_panel, _("Format"));
_format = new wxChoice (_video_panel, wxID_ANY);
- _video_sizer->Add (_format);
+ grid->Add (_format);
{
- add_label_to_sizer (_video_sizer, _video_panel, "Crop");
+ add_label_to_sizer (grid, _video_panel, _("Crop"));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- add_label_to_sizer (s, _video_panel, "L");
+ /* TRANSLATORS: L, R, T and B are abbreviations for Left, Right, Top, Bottom, the four edges
+ of the picture frame.
+ */
+ add_label_to_sizer (s, _video_panel, _("L"));
_left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_left_crop, 0);
- add_label_to_sizer (s, _video_panel, "R");
+ add_label_to_sizer (s, _video_panel, _("R"));
_right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_right_crop, 0);
- add_label_to_sizer (s, _video_panel, "T");
+ add_label_to_sizer (s, _video_panel, _("T"));
_top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_top_crop, 0);
- add_label_to_sizer (s, _video_panel, "B");
+ add_label_to_sizer (s, _video_panel, _("B"));
_bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
s->Add (_bottom_crop, 0);
- _video_sizer->Add (s);
+ grid->Add (s);
}
/* VIDEO-only stuff */
{
- video_control (add_label_to_sizer (_video_sizer, _video_panel, "Filters"));
+ video_control (add_label_to_sizer (grid, _video_panel, _("Filters")));
wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
- _filters = new wxStaticText (_video_panel, wxID_ANY, wxT ("None"));
+ _filters = new wxStaticText (_video_panel, wxID_ANY, _("None"));
video_control (_filters);
s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
- _filters_button = new wxButton (_video_panel, wxID_ANY, wxT ("Edit..."));
+ _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit..."));
video_control (_filters_button);
s->Add (_filters_button, 0);
- _video_sizer->Add (s, 1);
+ grid->Add (s, 1);
}
- video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler"));
+ video_control (add_label_to_sizer (grid, _video_panel, _("Scaler")));
_scaler = new wxChoice (_video_panel, wxID_ANY);
- _video_sizer->Add (video_control (_scaler), 1);
+ grid->Add (video_control (_scaler), 1);
vector<Scaler const *> const sc = Scaler::all ();
for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
_scaler->Append (std_to_wx ((*i)->name()));
}
- add_label_to_sizer (_video_sizer, _video_panel, "Colour look-up table");
+ add_label_to_sizer (grid, _video_panel, _("Colour look-up table"));
_colour_lut = new wxChoice (_video_panel, wxID_ANY);
for (int i = 0; i < 2; ++i) {
_colour_lut->Append (std_to_wx (colour_lut_index_to_name (i)));
}
_colour_lut->SetSelection (0);
- _video_sizer->Add (_colour_lut, 1, wxEXPAND);
+ grid->Add (_colour_lut, 1, wxEXPAND);
{
- add_label_to_sizer (_video_sizer, _video_panel, "JPEG2000 bandwidth");
+ add_label_to_sizer (grid, _video_panel, _("JPEG2000 bandwidth"));
wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY);
s->Add (_j2k_bandwidth, 1);
- add_label_to_sizer (s, _video_panel, "MBps");
- _video_sizer->Add (s, 1);
+ add_label_to_sizer (s, _video_panel, _("MBps"));
+ grid->Add (s, 1);
}
_left_crop->SetRange (0, 1024);
_right_crop->SetRange (0, 1024);
_bottom_crop->SetRange (0, 1024);
_still_duration->SetRange (1, 60 * 60);
- _dcp_trim_start->SetRange (0, 100);
- _dcp_trim_end->SetRange (0, 100);
+ _trim_start->SetRange (0, 100);
+ _trim_end->SetRange (0, 100);
_j2k_bandwidth->SetRange (50, 250);
}
FilmEditor::make_audio_panel ()
{
_audio_panel = new wxPanel (_notebook);
- _audio_sizer = new wxFlexGridSizer (2, 4, 4);
- wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
- pad->Add (_audio_sizer, 0, wxALL, 8);
- _audio_panel->SetSizer (pad);
+ _audio_sizer = new wxBoxSizer (wxVERTICAL);
+ _audio_panel->SetSizer (_audio_sizer);
+
+ wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+ _audio_sizer->Add (grid, 0, wxALL, 8);
{
- video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Gain"));
+ video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain")));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_audio_gain = new wxSpinCtrl (_audio_panel);
s->Add (video_control (_audio_gain), 1);
- video_control (add_label_to_sizer (s, _audio_panel, "dB"));
+ video_control (add_label_to_sizer (s, _audio_panel, _("dB")));
_audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
video_control (_audio_gain_calculate_button);
s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
- _audio_sizer->Add (s);
+ grid->Add (s);
}
{
- video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Delay"));
+ video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay")));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_audio_delay = new wxSpinCtrl (_audio_panel);
s->Add (video_control (_audio_delay), 1);
- video_control (add_label_to_sizer (s, _audio_panel, "ms"));
- _audio_sizer->Add (s);
+ /* TRANSLATORS: this is an abbreviation for milliseconds, the unit of time */
+ video_control (add_label_to_sizer (s, _audio_panel, _("ms")));
+ grid->Add (s);
}
{
_use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
- _audio_sizer->Add (video_control (_use_content_audio));
+ grid->Add (video_control (_use_content_audio));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_audio_stream = new wxChoice (_audio_panel, wxID_ANY);
s->Add (video_control (_audio_stream), 1);
_audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
- _audio_sizer->Add (s, 1, wxEXPAND);
+ grid->Add (s, 1, wxEXPAND);
}
_use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
- _audio_sizer->Add (_use_external_audio);
- _audio_sizer->AddSpacer (0);
+ grid->Add (_use_external_audio);
+ grid->AddSpacer (0);
assert (MAX_AUDIO_CHANNELS == 6);
- char const * channels[] = {
- "Left",
- "Right",
- "Centre",
- "Lfe (sub)",
- "Left surround",
- "Right surround"
+ /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
+ enhancement channel (sub-woofer)./
+ */
+ wxString const channels[] = {
+ _("Left"),
+ _("Right"),
+ _("Centre"),
+ _("Lfe (sub)"),
+ _("Left surround"),
+ _("Right surround"),
};
for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
- add_label_to_sizer (_audio_sizer, _audio_panel, channels[i]);
- _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), wxT ("Select Audio File"), wxT ("*.wav"));
- _audio_sizer->Add (_external_audio[i], 1, wxEXPAND);
+ add_label_to_sizer (grid, _audio_panel, channels[i]);
+ _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
+ grid->Add (_external_audio[i], 1, wxEXPAND);
}
_audio_gain->SetRange (-60, 60);
FilmEditor::make_subtitle_panel ()
{
_subtitle_panel = new wxPanel (_notebook);
- _subtitle_sizer = new wxFlexGridSizer (2, 4, 4);
- wxBoxSizer* pad = new wxBoxSizer (wxVERTICAL);
- pad->Add (_subtitle_sizer, 0, wxALL, 8);
- _subtitle_panel->SetSizer (pad);
+ _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
+ _subtitle_panel->SetSizer (_subtitle_sizer);
+ wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+ _subtitle_sizer->Add (grid, 0, wxALL, 8);
- _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, wxT("With Subtitles"));
+ _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
video_control (_with_subtitles);
- _subtitle_sizer->Add (_with_subtitles, 1);
+ grid->Add (_with_subtitles, 1);
_subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
- _subtitle_sizer->Add (video_control (_subtitle_stream));
+ grid->Add (video_control (_subtitle_stream));
- video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset"));
+ video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset")));
_subtitle_offset = new wxSpinCtrl (_subtitle_panel);
- _subtitle_sizer->Add (video_control (_subtitle_offset), 1);
+ grid->Add (video_control (_subtitle_offset), 1);
{
- video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Scale"));
+ video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale")));
wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
_subtitle_scale = new wxSpinCtrl (_subtitle_panel);
s->Add (video_control (_subtitle_scale));
- video_control (add_label_to_sizer (s, _subtitle_panel, "%"));
- _subtitle_sizer->Add (s);
+ video_control (add_label_to_sizer (s, _subtitle_panel, _("%")));
+ grid->Add (s);
}
_subtitle_offset->SetRange (-1024, 1024);
_film->set_content (wx_to_std (_content->GetPath ()));
} catch (std::exception& e) {
_content->SetPath (std_to_wx (_film->directory ()));
- error_dialog (this, String::compose ("Could not set content: %1", e.what ()));
+ error_dialog (this, wxString::Format (_("Could not set content: %s"), e.what ()));
}
}
}
_length->SetLabel (std_to_wx (s.str ()));
if (_film->length()) {
- _dcp_trim_start->SetRange (0, _film->length().get());
- _dcp_trim_end->SetRange (0, _film->length().get());
+ _trim_start->SetRange (0, _film->length().get());
+ _trim_end->SetRange (0, _film->length().get());
}
break;
+ case Film::DCP_INTRINSIC_DURATION:
+ break;
case Film::DCP_CONTENT_TYPE:
checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
_dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
case Film::SCALER:
checked_set (_scaler, Scaler::as_index (_film->scaler ()));
break;
- case Film::DCP_TRIM_START:
- checked_set (_dcp_trim_start, _film->dcp_trim_start());
+ case Film::TRIM_START:
+ checked_set (_trim_start, _film->trim_start());
break;
- case Film::DCP_TRIM_END:
- checked_set (_dcp_trim_end, _film->dcp_trim_end());
+ case Film::TRIM_END:
+ checked_set (_trim_end, _film->trim_end());
break;
case Film::AUDIO_GAIN:
checked_set (_audio_gain, _film->audio_gain ());
film_changed (Film::CROP);
film_changed (Film::FILTERS);
film_changed (Film::SCALER);
- film_changed (Film::DCP_TRIM_START);
- film_changed (Film::DCP_TRIM_END);
+ film_changed (Film::TRIM_START);
+ film_changed (Film::TRIM_END);
film_changed (Film::DCP_AB);
film_changed (Film::CONTENT_AUDIO_STREAM);
film_changed (Film::EXTERNAL_AUDIO);
_scaler->Enable (s);
_audio_stream->Enable (s);
_dcp_content_type->Enable (s);
- _dcp_trim_start->Enable (s);
- _dcp_trim_end->Enable (s);
+ _trim_start->Enable (s);
+ _trim_end->Enable (s);
_dcp_ab->Enable (s);
_colour_lut->Enable (s);
_j2k_bandwidth->Enable (s);
}
void
-FilmEditor::dcp_trim_start_changed (wxCommandEvent &)
+FilmEditor::trim_start_changed (wxCommandEvent &)
{
if (!_film) {
return;
}
- _film->set_dcp_trim_start (_dcp_trim_start->GetValue ());
+ _film->set_trim_start (_trim_start->GetValue ());
}
void
-FilmEditor::dcp_trim_end_changed (wxCommandEvent &)
+FilmEditor::trim_end_changed (wxCommandEvent &)
{
if (!_film) {
return;
}
- _film->set_dcp_trim_end (_dcp_trim_end->GetValue ());
+ _film->set_trim_end (_trim_end->GetValue ());
}
void
void content_changed (wxCommandEvent &);
void trust_content_header_changed (wxCommandEvent &);
void format_changed (wxCommandEvent &);
- void dcp_trim_start_changed (wxCommandEvent &);
- void dcp_trim_end_changed (wxCommandEvent &);
+ void trim_start_changed (wxCommandEvent &);
+ void trim_end_changed (wxCommandEvent &);
void dcp_content_type_changed (wxCommandEvent &);
void dcp_ab_toggled (wxCommandEvent &);
void scaler_changed (wxCommandEvent &);
/** The Film's duration for still sources */
wxSpinCtrl* _still_duration;
- wxSpinCtrl* _dcp_trim_start;
- wxSpinCtrl* _dcp_trim_end;
+ wxSpinCtrl* _trim_start;
+ wxSpinCtrl* _trim_end;
/** Selector to generate an A/B comparison DCP */
wxCheckBox* _dcp_ab;
#include "lib/scaler.h"
#include "lib/exceptions.h"
#include "lib/examine_content_job.h"
+#include "lib/filter.h"
#include "film_viewer.h"
#include "wx_util.h"
#include "video_decoder.h"
using std::cout;
using std::list;
using boost::shared_ptr;
+using libdcp::Size;
FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
: wxPanel (p)
break;
case Film::CONTENT:
{
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- o->decode_audio = false;
- o->decode_subtitles = true;
- o->video_sync = false;
+ DecodeOptions o;
+ o.decode_audio = false;
+ o.decode_subtitles = true;
+ o.video_sync = false;
_decoders = decoder_factory (_film, o, 0);
_decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
_decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
case Film::SUBTITLE_OFFSET:
case Film::SUBTITLE_SCALE:
case Film::SCALER:
+ case Film::FILTERS:
update_from_raw ();
break;
case Film::SUBTITLE_STREAM:
old_size = _display_frame->size();
}
+ boost::shared_ptr<Image> input = _raw_frame;
+
+ pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
+ if (!s.second.empty ()) {
+ input = input->post_process (s.second, true);
+ }
+
/* Get a compacted image as we have to feed it to wxWidgets */
- _display_frame = _raw_frame->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
+ _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false);
if (old_size != _display_frame->size()) {
_clear_required = true;
}
}
} catch (DecodeError& e) {
- error_dialog (this, String::compose ("Could not decode video for view (%1)", e.what()));
+ _play_button->SetValue (false);
+ check_play_state ();
+ error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), e.what()));
}
}
vector<Filter const *> filters = Filter::all ();
+ typedef map<string, list<Filter const *> > CategoryMap;
+ CategoryMap categories;
+
for (vector<Filter const *>::iterator i = filters.begin(); i != filters.end(); ++i) {
- wxCheckBox* b = new wxCheckBox (this, wxID_ANY, std_to_wx ((*i)->name ()));
- bool const a = find (active.begin(), active.end(), *i) != active.end ();
- b->SetValue (a);
- _filters[*i] = b;
- b->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilterView::filter_toggled), 0, this);
- sizer->Add (b);
+ CategoryMap::iterator j = categories.find ((*i)->category ());
+ if (j == categories.end ()) {
+ list<Filter const *> c;
+ c.push_back (*i);
+ categories[(*i)->category()] = c;
+ } else {
+ j->second.push_back (*i);
+ }
+ }
+
+ for (CategoryMap::iterator i = categories.begin(); i != categories.end(); ++i) {
+
+ wxStaticText* c = new wxStaticText (this, wxID_ANY, std_to_wx (i->first));
+ wxFont font = c->GetFont();
+ font.SetWeight(wxFONTWEIGHT_BOLD);
+ c->SetFont(font);
+ sizer->Add (c);
+
+ for (list<Filter const *>::iterator j = i->second.begin(); j != i->second.end(); ++j) {
+ wxCheckBox* b = new wxCheckBox (this, wxID_ANY, std_to_wx ((*j)->name ()));
+ bool const a = find (active.begin(), active.end(), *j) != active.end ();
+ b->SetValue (a);
+ _filters[*j] = b;
+ b->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilterView::filter_toggled), 0, this);
+ sizer->Add (b);
+ }
+
+ sizer->AddSpacer (6);
}
}
wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
table->AddGrowableCol (1, 1);
- add_label_to_sizer (table, this, "I want to play this back at fader");
+ add_label_to_sizer (table, this, _("I want to play this back at fader"));
_wanted = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
table->Add (_wanted, 1, wxEXPAND);
- add_label_to_sizer (table, this, "But I have to use fader");
+ add_label_to_sizer (table, this, _("But I have to use fader"));
_actual = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
table->Add (_actual, 1, wxEXPAND);
_job_records[*i].message->SetLabel (std_to_wx (st));
_job_records[*i].gauge->SetValue (p * 100);
} else {
- _job_records[*i].message->SetLabel (wxT ("Running"));
+ _job_records[*i].message->SetLabel (_("Running"));
_job_records[*i].gauge->Pulse ();
}
}
try {
film->make_dcp (transcode);
} catch (BadSettingError& e) {
- error_dialog (parent, String::compose ("Bad setting for %1 (%2)", e.setting(), e.what ()));
+ error_dialog (parent, wxString::Format (_("Bad setting for %s (%s)"), e.setting().c_str(), e.what()));
} catch (std::exception& e) {
- error_dialog (parent, String::compose ("Could not make DCP: %1", e.what ()));
+ error_dialog (parent, wxString::Format (_("Could not make DCP: %s"), e.what ()));
}
}
using namespace boost;
NewFilmDialog::NewFilmDialog (wxWindow* parent)
- : wxDialog (parent, wxID_ANY, wxString (_("New Film")))
+ : wxDialog (parent, wxID_ANY, _("New Film"))
{
wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
SetSizer (overall_sizer);
table->AddGrowableCol (1, 1);
overall_sizer->Add (table, 1, wxEXPAND | wxALL, 6);
- add_label_to_sizer (table, this, "Film name");
+ add_label_to_sizer (table, this, _("Film name"));
_name = new wxTextCtrl (this, wxID_ANY);
table->Add (_name, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Create in folder");
+ add_label_to_sizer (table, this, _("Create in folder"));
#ifdef __WXMSW__
_folder = new DirPickerCtrl (this);
#else
{
wxFlexGridSizer* table = new wxFlexGridSizer (2, 3, 6);
- add_label_to_sizer (table, this, "Frames");
- _frames = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+ add_label_to_sizer (table, this, _("Frames"));
+ _frames = new wxStaticText (this, wxID_ANY, wxT (""));
table->Add (_frames, 1, wxALIGN_CENTER_VERTICAL);
- add_label_to_sizer (table, this, "Disk space required for frames");
- _disk_for_frames = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+ add_label_to_sizer (table, this, _("Disk space required for frames"));
+ _disk_for_frames = new wxStaticText (this, wxID_ANY, wxT (""));
table->Add (_disk_for_frames, 1, wxALIGN_CENTER_VERTICAL);
- add_label_to_sizer (table, this, "Total disk space required");
- _total_disk = new wxStaticText (this, wxID_ANY, std_to_wx (""));
+ add_label_to_sizer (table, this, _("Total disk space required"));
+ _total_disk = new wxStaticText (this, wxID_ANY, wxT (""));
table->Add (_total_disk, 1, wxALIGN_CENTER_VERTICAL);
- add_label_to_sizer (table, this, "Frames already encoded");
- _encoded = new ThreadedStaticText (this, "counting...", boost::bind (&PropertiesDialog::frames_already_encoded, this));
+ add_label_to_sizer (table, this, _("Frames already encoded"));
+ _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
if (_film->length()) {
return "";
}
- if (_film->dcp_length()) {
+ if (_film->length()) {
/* XXX: encoded_frames() should check which frames have been encoded */
- u << " (" << ((_film->encoded_frames() - _film->dcp_trim_start()) * 100 / _film->dcp_length().get()) << "%)";
+ u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
}
return u.str ();
}
#include "wx_util.h"
ServerDialog::ServerDialog (wxWindow* parent, ServerDescription* server)
- : wxDialog (parent, wxID_ANY, wxString (_("Server")))
+ : wxDialog (parent, wxID_ANY, _("Server"))
{
if (server) {
_server = server;
wxFlexGridSizer* table = new wxFlexGridSizer (2, 4, 4);
table->AddGrowableCol (1, 1);
- add_label_to_sizer (table, this, "Host name or IP address");
+ add_label_to_sizer (table, this, _("Host name or IP address"));
_host = new wxTextCtrl (this, wxID_ANY);
table->Add (_host, 1, wxEXPAND);
- add_label_to_sizer (table, this, "Threads to use");
+ add_label_to_sizer (table, this, _("Threads to use"));
_threads = new wxSpinCtrl (this, wxID_ANY);
table->Add (_threads, 1, wxEXPAND);
* @param prop Proportion to pass when calling Add() on the wxSizer.
*/
wxStaticText *
-add_label_to_sizer (wxSizer* s, wxWindow* p, string t, int prop)
+add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, int prop)
{
- wxStaticText* m = new wxStaticText (p, wxID_ANY, std_to_wx (t));
+ wxStaticText* m = new wxStaticText (p, wxID_ANY, t);
s->Add (m, prop, wxALIGN_CENTER_VERTICAL | wxALL, 6);
return m;
}
* @param m Message.
*/
void
-error_dialog (wxWindow* parent, string m)
+error_dialog (wxWindow* parent, wxString m)
{
- wxMessageDialog* d = new wxMessageDialog (parent, std_to_wx (m), wxT ("DVD-o-matic"), wxOK);
+ wxMessageDialog* d = new wxMessageDialog (parent, m, _("DVD-o-matic"), wxOK);
d->ShowModal ();
d->Destroy ();
}
* @param initial Initial text for the wxStaticText while the computation is being run.
* @param fn Function which works out what the wxStaticText content should be and returns it.
*/
-ThreadedStaticText::ThreadedStaticText (wxWindow* parent, string initial, function<string ()> fn)
- : wxStaticText (parent, wxID_ANY, std_to_wx (initial))
+ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, function<string ()> fn)
+ : wxStaticText (parent, wxID_ANY, initial)
{
Connect (_update_event_id, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (ThreadedStaticText::thread_finished), 0, this);
_thread = new thread (bind (&ThreadedStaticText::run, this, fn));
* @brief Some utility functions and classes.
*/
-extern void error_dialog (wxWindow *, std::string);
-extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, std::string, int prop = 0);
+extern void error_dialog (wxWindow *, wxString);
+extern wxStaticText* add_label_to_sizer (wxSizer *, wxWindow *, wxString, int prop = 0);
extern std::string wx_to_std (wxString);
extern wxString std_to_wx (std::string);
class ThreadedStaticText : public wxStaticText
{
public:
- ThreadedStaticText (wxWindow* parent, std::string initial, boost::function<std::string ()> fn);
+ ThreadedStaticText (wxWindow* parent, wxString initial, boost::function<std::string ()> fn);
~ThreadedStaticText ();
private:
-version 1
+version 2
name fred
use_dci_name 1
content
filter pphb
filter unsharp
scaler bicubic
-dcp_trim_start 42
-dcp_trim_end 99
+trim_start 42
+trim_end 99
dcp_ab 1
use_content_audio 1
audio_gain 0
width 0
height 0
length 0
+dcp_intrinsic_duration 0
content_digest
external_audio_stream external 0 0
frames_per_second 0
return shared_ptr<Film> (new Film (d, false));
}
-BOOST_AUTO_TEST_CASE (film_metadata_test)
+
+/* Check that Image::make_black works, and doesn't use values which crash
+ sws_scale().
+*/
+BOOST_AUTO_TEST_CASE (make_black_test)
{
+ /* This needs to happen in the first test */
dvdomatic_setup ();
+
+ libdcp::Size in_size (512, 512);
+ libdcp::Size out_size (1024, 1024);
+
+ {
+ /* Plain RGB input */
+ boost::shared_ptr<Image> foo (new SimpleImage (AV_PIX_FMT_RGB24, in_size, true));
+ foo->make_black ();
+ boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
+
+ uint8_t* p = bar->data()[0];
+ for (int y = 0; y < bar->size().height; ++y) {
+ uint8_t* q = p;
+ for (int x = 0; x < bar->line_size()[0]; ++x) {
+ BOOST_CHECK_EQUAL (*q++, 0);
+ }
+ p += bar->stride()[0];
+ }
+ }
+
+ {
+ /* YUV420P input */
+ boost::shared_ptr<Image> foo (new SimpleImage (AV_PIX_FMT_YUV420P, in_size, true));
+ foo->make_black ();
+ boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
+
+ uint8_t* p = bar->data()[0];
+ for (int y = 0; y < bar->size().height; ++y) {
+ uint8_t* q = p;
+ for (int x = 0; x < bar->line_size()[0]; ++x) {
+ BOOST_CHECK_EQUAL (*q++, 0);
+ }
+ p += bar->stride()[0];
+ }
+ }
+
+ {
+ /* YUV422P10LE input */
+ boost::shared_ptr<Image> foo (new SimpleImage (AV_PIX_FMT_YUV422P10LE, in_size, true));
+ foo->make_black ();
+ boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true);
+
+ uint8_t* p = bar->data()[0];
+ for (int y = 0; y < bar->size().height; ++y) {
+ uint8_t* q = p;
+ for (int x = 0; x < bar->line_size()[0]; ++x) {
+ BOOST_CHECK_EQUAL (*q++, 0);
+ }
+ p += bar->stride()[0];
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE (film_metadata_test)
+{
setup_test_config ();
string const test_film = "build/test/film_metadata_test";
f_filters.push_back (Filter::from_id ("pphb"));
f_filters.push_back (Filter::from_id ("unsharp"));
f->set_filters (f_filters);
- f->set_dcp_trim_start (42);
- f->set_dcp_trim_end (99);
+ f->set_trim_start (42);
+ f->set_trim_end (99);
f->set_dcp_ab (true);
f->write_metadata ();
BOOST_CHECK_EQUAL (g_filters.size(), 2);
BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb"));
BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
- BOOST_CHECK_EQUAL (g->dcp_trim_start(), 42);
- BOOST_CHECK_EQUAL (g->dcp_trim_end(), 99);
+ BOOST_CHECK_EQUAL (g->trim_start(), 42);
+ BOOST_CHECK_EQUAL (g->trim_end(), 99);
BOOST_CHECK_EQUAL (g->dcp_ab(), true);
g->write_metadata ();
BOOST_AUTO_TEST_CASE (client_server_test)
{
- shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), false));
+ shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
uint8_t* p = image->data()[0];
for (int y = 0; y < 1080; ++y) {
+ uint8_t* q = p;
for (int x = 0; x < 1998; ++x) {
- *p++ = x % 256;
- *p++ = y % 256;
- *p++ = (x + y) % 256;
+ *q++ = x % 256;
+ *q++ = y % 256;
+ *q++ = (x + y) % 256;
}
+ p += image->stride()[0];
}
- shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), false));
+ shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
p = sub_image->data()[0];
for (int y = 0; y < 200; ++y) {
+ uint8_t* q = p;
for (int x = 0; x < 100; ++x) {
- *p++ = y % 256;
- *p++ = x % 256;
- *p++ = (x + y) % 256;
- *p++ = 1;
+ *q++ = y % 256;
+ *q++ = x % 256;
+ *q++ = (x + y) % 256;
+ *q++ = 1;
}
+ p += sub_image->stride()[0];
}
shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image));
film->examine_content ();
film->set_format (Format::from_nickname ("Flat"));
film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
- film->set_dcp_trim_end (42);
+ film->set_trim_end (42);
film->make_dcp (true);
while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) {
BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false);
}
+/* Test the constructor of DCPFrameRate */
+BOOST_AUTO_TEST_CASE (dcp_frame_rate_test)
+{
+ /* Run some tests with a limited range of allowed rates */
+
+ std::list<int> afr;
+ afr.push_back (24);
+ afr.push_back (25);
+ afr.push_back (30);
+ Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+ DCPFrameRate dfr = DCPFrameRate (60);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.skip, true);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (50);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.skip, true);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (48);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+ BOOST_CHECK_EQUAL (dfr.skip, true);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (30);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (29.97);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (25);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (24);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (14.5);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 30);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (12.6);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (12.4);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 25);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, true);
+
+ dfr = DCPFrameRate (12);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 24);
+ BOOST_CHECK_EQUAL (dfr.repeat, true);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ /* Now add some more rates and see if it will use them
+ in preference to skip/repeat.
+ */
+
+ afr.push_back (48);
+ afr.push_back (50);
+ afr.push_back (60);
+ Config::instance()->set_allowed_dcp_frame_rates (afr);
+
+ dfr = DCPFrameRate (60);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 60);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (50);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 50);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+
+ dfr = DCPFrameRate (48);
+ BOOST_CHECK_EQUAL (dfr.frames_per_second, 48);
+ BOOST_CHECK_EQUAL (dfr.skip, false);
+ BOOST_CHECK_EQUAL (dfr.repeat, false);
+ BOOST_CHECK_EQUAL (dfr.change_speed, false);
+}
+
BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
{
shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");
CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
CreateDirectory "$SMPROGRAMS\DVD-o-matic"
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
+CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
CreateShortCut "$DESKTOP\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" ""
CreateDirectory "$SMPROGRAMS\DVD-o-matic"
-CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
+CreateShortCut "$SMPROGRAMS\DVD-o-matic\Uninstall DVD-o-matic.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic.lnk" "$INSTDIR\bin\dvdomatic.exe" "" "$INSTDIR\bin\dvdomatic.exe" 0
CreateShortCut "$SMPROGRAMS\DVD-o-matic\DVD-o-matic encode server.lnk" "$INSTDIR\bin\servomatic_gui.exe" "" "$INSTDIR\bin\servomatic_gui.exe" 0
conf.env.append_value('CXXFLAGS', '-O2')
if not conf.options.static:
- conf.check_cfg(package = 'libdcp', atleast_version = '0.36', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+ conf.check_cfg(package = 'libdcp', atleast_version = '0.39', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
conf.check_cfg(package = 'sndfile', args = '--cflags --libs', uselib_store = 'SNDFILE', mandatory = True)
conf.check_cfg(package = 'glib-2.0', args = '--cflags --libs', uselib_store = 'GLIB', mandatory = True)
- conf.check_cfg(package = 'liblzma', args = '--cflags --libs', uselib_store = 'LZMA', mandatory = True)
+ if conf.options.target_windows is False:
+ conf.check_cfg(package = 'liblzma', args = '--cflags --libs', uselib_store = 'LZMA', mandatory = True)
conf.check_cfg(package = '', path = conf.options.magickpp_config, args = '--cppflags --cxxflags --libs', uselib_store = 'MAGICK', mandatory = True)
if conf.options.static:
conf.check_cfg(package = 'libopenjpeg', args = '--cflags --libs', atleast_version = '1.5.0', uselib_store = 'OPENJPEG', mandatory = True)
conf.check_cfg(package = 'libopenjpeg', args = '--cflags --libs', max_version = '1.5.1', mandatory = True)
+ conf.check_cxx(fragment = """
+ #include <boost/version.hpp>\n
+ #if BOOST_VERSION < 104500\n
+ #error boost too old\n
+ #endif\n
+ int main(void) { return 0; }\n
+ """,
+ mandatory = True,
+ msg = 'Checking for boost library >= 1.45',
+ okmsg = 'yes',
+ errmsg = 'too old\nPlease install boost version 1.45 or higher.')
+
conf.check_cc(fragment = """
#include <libssh/libssh.h>\n
int main () {\n
#include <boost/thread.hpp>\n
int main() { boost::thread t (); }\n
""", msg = 'Checking for boost threading library',
+ libpath = '/usr/local/lib',
lib = [boost_thread, 'boost_system%s' % boost_lib_suffix],
uselib_store = 'BOOST_THREAD')