#include "ffmpeg.h"
#include "ffmpeg_content.h"
#include "exceptions.h"
+ #include "util.h"
#include "i18n.h"
av_register_all ();
_file_group.set_paths (_ffmpeg_content->paths ());
- _avio_buffer = static_cast<uint8_t*> (av_malloc (_avio_buffer_size));
+ _avio_buffer = static_cast<uint8_t*> (wrapped_av_malloc (_avio_buffer_size));
_avio_context = avio_alloc_context (_avio_buffer, _avio_buffer_size, 0, this, avio_read_wrapper, 0, avio_seek_wrapper);
_format_context = avformat_alloc_context ();
_format_context->pb = _avio_context;
FFmpeg::setup_video ()
{
boost::mutex::scoped_lock lm (_mutex);
-
+
+ assert (_video_stream >= 0);
AVCodecContext* context = _format_context->streams[_video_stream]->codec;
AVCodec* codec = avcodec_find_decoder (context->codec_id);
AVCodecContext *
FFmpeg::audio_codec_context () const
{
+ if (!_ffmpeg_content->audio_stream ()) {
+ return 0;
+ }
+
return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
}
/* Constructor for tests */
FFmpegAudioStream ()
: FFmpegStream ("", 0)
+ , frame_rate (0)
+ , channels (0)
, mapping (1)
{}
};
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
/* AudioContent */
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ AudioFrame audio_length () const;
int content_audio_frame_rate () const;
int output_audio_frame_rate () const;
AudioMapping audio_mapping () const;
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
+#include "timer.h"
using std::string;
using std::min;
libdcp::Size cropped_size = crop.apply (size ());
struct SwsContext* scale_context = sws_getContext (
- cropped_size.width, cropped_size.height, pixel_format(),
- inter_size.width, inter_size.height, out_format,
- scaler->ffmpeg_id (), 0, 0, 0
+ cropped_size.width, cropped_size.height, pixel_format(),
+ inter_size.width, inter_size.height, out_format,
+ scaler->ffmpeg_id (), 0, 0, 0
);
uint8_t* scale_in_data[components()];
void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* Only implemented for RGBA onto RGB24 so far */
- assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+ int this_bpp = 0;
+ int other_bpp = 0;
+
+ if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 4;
+ other_bpp = 4;
+ } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 3;
+ other_bpp = 4;
+ } else {
+ assert (false);
+ }
int start_tx = position.x;
int start_ox = 0;
}
for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
- uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+ uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
- tp += 3;
- op += 4;
+ tp += this_bpp;
+ op += other_bpp;
}
}
}
void
Image::allocate ()
{
- _data = (uint8_t **) av_malloc (4 * sizeof (uint8_t *));
+ _data = (uint8_t **) wrapped_av_malloc (4 * sizeof (uint8_t *));
_data[0] = _data[1] = _data[2] = _data[3] = 0;
- _line_size = (int *) av_malloc (4 * sizeof (int));
+ _line_size = (int *) wrapped_av_malloc (4 * sizeof (int));
_line_size[0] = _line_size[1] = _line_size[2] = _line_size[3] = 0;
- _stride = (int *) av_malloc (4 * sizeof (int));
+ _stride = (int *) wrapped_av_malloc (4 * sizeof (int));
_stride[0] = _stride[1] = _stride[2] = _stride[3] = 0;
for (int i = 0; i < components(); ++i) {
seem to mind. The nasty + 1 in this malloc makes sure there is always a byte
for that instruction to read safely.
*/
- _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i) + 1);
+ _data[i] = (uint8_t *) wrapped_av_malloc (_stride[i] * lines (i) + 1);
}
}
} catch (libdcp::FileError& e) {
- set_progress (1);
- set_state (FINISHED_ERROR);
-
string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
try {
}
set_error (e.what(), m);
-
- } catch (OpenFileError& e) {
-
set_progress (1);
set_state (FINISHED_ERROR);
+
+ } catch (OpenFileError& e) {
set_error (
String::compose (_("Could not open %1"), e.file().string()),
String::compose (_("DCP-o-matic could not open the file %1. Perhaps it does not exist or is in an unexpected format."), e.file().string())
);
+ set_progress (1);
+ set_state (FINISHED_ERROR);
+
} catch (boost::thread_interrupted &) {
set_state (FINISHED_CANCELLED);
-
- } catch (std::exception& e) {
+ } catch (std::bad_alloc& e) {
+
+ set_error (_("Out of memory"), _("There was not enough memory to do this."));
set_progress (1);
set_state (FINISHED_ERROR);
+
+ } catch (std::exception& e) {
+
set_error (
e.what (),
_("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)")
);
- } catch (...) {
-
set_progress (1);
set_state (FINISHED_ERROR);
+
+ } catch (...) {
+
set_error (
_("Unknown error"),
_("It is not known what caused this error. The best idea is to report the problem to the DCP-o-matic mailing list (carl@dcpomatic.com)")
);
+ set_progress (1);
+ set_state (FINISHED_ERROR);
}
}
}
}
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
int
Job::elapsed_time () const
{
*/
#include <stdint.h>
+#include <algorithm>
#include "player.h"
#include "film.h"
#include "ffmpeg_decoder.h"
#include "sndfile_decoder.h"
#include "sndfile_content.h"
#include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
#include "playlist.h"
#include "job.h"
#include "image.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
+#include "render_subtitles.h"
using std::list;
using std::cout;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
class Piece
{
public:
- Piece (shared_ptr<Content> c)
- : content (c)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
- {}
-
- Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+ Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
: content (c)
, decoder (d)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
+ , frc (f)
{}
- /** Set this piece to repeat a video frame a given number of times */
- void set_repeat (IncomingVideo video, int num)
- {
- repeat_video = video;
- repeat_to_do = num;
- repeat_done = 0;
- }
-
- void reset_repeat ()
- {
- repeat_video.image.reset ();
- repeat_to_do = 0;
- repeat_done = 0;
- }
-
- bool repeating () const
- {
- return repeat_done != repeat_to_do;
- }
-
- void repeat (Player* player)
- {
- player->process_video (
- repeat_video.weak_piece,
- repeat_video.image,
- repeat_video.eyes,
- repeat_done > 0,
- repeat_video.frame,
- (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
- );
-
- ++repeat_done;
- }
-
shared_ptr<Content> content;
shared_ptr<Decoder> decoder;
- /** Time of the last video we emitted relative to the start of the DCP */
- Time video_position;
- /** Time of the last audio we emitted relative to the start of the DCP */
- Time audio_position;
-
- IncomingVideo repeat_video;
- int repeat_to_do;
- int repeat_done;
+ FrameRateChange frc;
};
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
, _audio_position (0)
, _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
, _last_emit_was_black (false)
+ , _just_did_inaccurate_seek (false)
+ , _approximate_size (false)
{
_playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
setup_pieces ();
}
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
+ /* Interrogate all our pieces to find the one with the earliest decoded data */
+
+ shared_ptr<Piece> earliest_piece;
+ shared_ptr<Decoded> earliest_decoded;
+ DCPTime earliest_time = TIME_MAX;
+ DCPTime earliest_audio = TIME_MAX;
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
+
+ bool done = false;
+ shared_ptr<Decoded> dec;
+ while (!done) {
+ dec = (*i)->decoder->peek ();
+ if (!dec) {
+ /* Decoder has nothing else to give us */
+ break;
+ }
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
+ dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
+ DCPTime const t = dec->dcp_time - offset;
+ if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
+ /* In the end-trimmed part; decoder has nothing else to give us */
+ dec.reset ();
+ done = true;
+ } else if (t >= (*i)->content->trim_start ()) {
+ /* Within the un-trimmed part; everything's ok */
+ done = true;
+ } else {
+ /* Within the start-trimmed part; get something else */
+ (*i)->decoder->consume ();
}
}
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
- }
+ if (!dec) {
+ continue;
}
- }
- if (!earliest) {
+ if (dec->dcp_time < earliest_time) {
+ earliest_piece = *i;
+ earliest_decoded = dec;
+ earliest_time = dec->dcp_time;
+ }
+
+ if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
+ earliest_audio = dec->dcp_time;
+ }
+ }
+
+ if (!earliest_piece) {
flush ();
return true;
}
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
- } else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
+ if (earliest_audio != TIME_MAX) {
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
+ Audio (tb.audio, tb.time);
+ /* This assumes that the audio_frames_to_time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ }
+
+ /* Emit the earliest thing */
+
+ shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+ shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+ shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
+ shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
+
+ /* Will be set to false if we shouldn't consume the peeked DecodedThing */
+ bool consume = true;
+
+ if (dv && _video) {
+
+ if (_just_did_inaccurate_seek) {
+
+ /* Just emit; no subtlety */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+
+ } else if (dv->dcp_time > _video_position) {
+
+ /* Too far ahead */
+
+ list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+ while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+ ++i;
+ }
+
+ if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+ /* We're outside all video content */
+ emit_black ();
+ _statistics.video.black++;
} else {
- earliest->decoder->pass ();
+ /* We're inside some video; repeat the frame */
+ _last_incoming_video.video->dcp_time = _video_position;
+ emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
+ step_video_position (_last_incoming_video.video);
+ _statistics.video.repeat++;
}
- }
- break;
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+ consume = false;
+
+ } else if (dv->dcp_time == _video_position) {
+ /* We're ok */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+ _statistics.video.good++;
} else {
- earliest->decoder->pass ();
-
- if (earliest->decoder->done()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
- assert (ac);
- shared_ptr<Resampler> re = resampler (ac, false);
- if (re) {
- shared_ptr<const AudioBuffers> b = re->flush ();
- if (b->frames ()) {
- process_audio (earliest, b, ac->audio_length ());
- }
- }
- }
+ /* Too far behind: skip */
+ _statistics.video.skip++;
}
- break;
- }
- if (_audio) {
- boost::optional<Time> audio_done_up_to;
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
+ _just_did_inaccurate_seek = false;
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
- if (ad && ad->has_audio ()) {
- audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
- }
- }
+ } else if (da && _audio) {
- if (audio_done_up_to) {
- TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ if (da->dcp_time > _audio_position) {
+ /* Too far ahead */
+ emit_silence (da->dcp_time - _audio_position);
+ consume = false;
+ _statistics.audio.silence += (da->dcp_time - _audio_position);
+ } else if (da->dcp_time == _audio_position) {
+ /* We're ok */
+ emit_audio (earliest_piece, da);
+ _statistics.audio.good += da->data->frames();
+ } else {
+ /* Too far behind: skip */
+ _statistics.audio.skip += da->data->frames();
}
- }
+ } else if (dis && _video) {
+ _image_subtitle.piece = earliest_piece;
+ _image_subtitle.subtitle = dis;
+ update_subtitle_from_image ();
+ } else if (dts && _video) {
+ _text_subtitle.piece = earliest_piece;
+ _text_subtitle.subtitle = dts;
+ update_subtitle_from_text ();
+ }
+
+ if (consume) {
+ earliest_piece->decoder->consume ();
+ }
+
return false;
}
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
{
/* Keep a note of what came in so that we can repeat it if required */
_last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.image = image;
- _last_incoming_video.eyes = eyes;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
+ _last_incoming_video.video = video;
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
assert (content);
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
- if (frc.skip && (frame % 2) == 1) {
- return;
- }
+ FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
- return;
- }
-
- Time const time = content->position() + relative_time + extra - content->trim_start ();
float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
- libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
+ libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
+ }
shared_ptr<PlayerImage> pi (
new PlayerImage (
- image,
+ video->image,
content->crop(),
image_size,
_video_container_size,
)
);
- if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+ if (
+ _film->with_subtitles () &&
+ _out_subtitle.image &&
+ video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
+ ) {
Position<int> const container_offset (
(_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
+ (_video_container_size.height - image_size.height) / 2
);
pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
_last_video = piece->content;
#endif
- Video (pi, eyes, content->colour_conversion(), same, time);
-
+ Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
+
_last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+}
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+void
+Player::step_video_position (shared_ptr<DecodedVideo> video)
+{
+ /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
+ if (video->eyes != EYES_LEFT) {
+ /* This assumes that the video_frames_to_time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _video_position += _film->video_frames_to_time (1);
}
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
{
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
/* Gain */
if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
gain->apply_gain (content->audio_gain ());
- audio = gain;
- }
-
- /* Resample */
- if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
- }
-
- Time const relative_time = _film->audio_frames_to_time (frame);
-
- if (content->trimmed (relative_time)) {
- return;
+ audio->data = gain;
}
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-
/* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
dcp_mapped->make_silent ();
-
AudioMapping map = content->audio_mapping ();
for (int i = 0; i < map.content_channels(); ++i) {
for (int j = 0; j < _film->audio_channels(); ++j) {
if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
dcp_mapped->accumulate_channel (
- audio.get(),
+ audio->data.get(),
i,
static_cast<libdcp::Channel> (j),
map.get (i, static_cast<libdcp::Channel> (j))
}
}
- audio = dcp_mapped;
+ audio->data = dcp_mapped;
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
+ /* Delay */
+ audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+ if (audio->dcp_time < 0) {
+ int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+ if (frames >= audio->data->frames ()) {
return;
}
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+ trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
- audio = trimmed;
- time = 0;
+ audio->data = trimmed;
+ audio->dcp_time = 0;
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ _audio_merger.push (audio->data, audio->dcp_time);
}
void
Player::flush ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
if (_audio && tb.audio) {
Audio (tb.audio, tb.time);
_audio_position += _film->audio_frames_to_time (tb.audio->frames ());
}
while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ emit_silence (_video_position - _audio_position);
}
}
* @return true on error
*/
void
-Player::seek (Time t, bool accurate)
+Player::seek (DCPTime t, bool accurate)
{
if (!_have_valid_pieces) {
setup_pieces ();
}
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
- if (!vc) {
- continue;
- }
-
/* s is the offset of t from the start position of this content */
- Time s = t - vc->position ();
- s = max (static_cast<Time> (0), s);
- s = min (vc->length_after_trim(), s);
+ DCPTime s = t - (*i)->content->position ();
+ s = max (static_cast<DCPTime> (0), s);
+ s = min ((*i)->content->length_after_trim(), s);
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
+ /* Convert this to the content time */
+ ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
/* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
-
- (*i)->reset_repeat ();
+ (*i)->decoder->seek (ct, accurate);
}
- _video_position = _audio_position = t;
+ _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
+ _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
+
+ _audio_merger.clear (_audio_position);
- /* XXX: don't seek audio because we don't need to... */
+ if (!accurate) {
+ /* We just did an inaccurate seek, so it's likely that the next thing seen
+ out of pass() will be a fair distance from _{video,audio}_position. Setting
+ this flag stops pass() from trying to fix that: we assume that if it
+ was an inaccurate seek then the caller does not care too much about
+ inserting black/silence to keep the time tidy.
+ */
+ _just_did_inaccurate_seek = true;
+ }
}
void
Player::setup_pieces ()
{
list<shared_ptr<Piece> > old_pieces = _pieces;
-
_pieces.clear ();
ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+ if (!(*i)->paths_valid ()) {
+ continue;
+ }
++
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
+
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ DCPTime best_overlap_t = 0;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+ } else {
+ /* No video overlap; e.g. if the DCP is just audio */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
+ /* FFmpeg */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
- fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
- piece->decoder = fd;
+ decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
-
+
+ /* ImageContent */
shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
if (ic) {
- bool reusing = false;
-
/* See if we can re-use an old ImageDecoder */
for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
if (imd && imd->content() == ic) {
- piece = *j;
- reusing = true;
+ decoder = imd;
}
}
- if (!reusing) {
- shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- piece->decoder = id;
+ if (!decoder) {
+ decoder.reset (new ImageDecoder (_film, ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
+ /* SndfileContent */
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
- shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+ decoder.reset (new SndfileDecoder (_film, sc));
+ frc = best_overlap_frc;
+ }
- piece->decoder = sd;
+ /* SubRipContent */
+ shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+ if (rc) {
+ decoder.reset (new SubRipDecoder (_film, rc));
+ frc = best_overlap_frc;
}
- _pieces.push_back (piece);
+ ContentTime st = (*i)->trim_start() * frc->speed_up;
+ decoder->seek (st, true);
+
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
_have_valid_pieces = true;
+
+ /* The Piece for the _last_incoming_video will no longer be valid */
+ _last_incoming_video.video.reset ();
+
+ _video_position = _audio_position = 0;
}
void
property == SubtitleContentProperty::SUBTITLE_SCALE
) {
- update_subtitle ();
+ update_subtitle_from_image ();
+ update_subtitle_from_text ();
Changed (frequent);
} else if (
} else if (property == ContentProperty::PATH) {
+ _have_valid_pieces = false;
Changed (frequent);
}
}
);
}
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
-
- if (!create) {
- return shared_ptr<Resampler> ();
- }
-
- _film->log()->log (
- String::compose (
- "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
- )
- );
-
- shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
- _resamplers[c] = r;
- return r;
-}
-
void
Player::emit_black ()
{
}
void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
{
if (most == 0) {
return;
}
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+ DCPTime t = min (most, TIME_HZ / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
silence->make_silent ();
Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
+
+ _audio_position += t;
}
void
}
void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
-{
- _in_subtitle.piece = weak_piece;
- _in_subtitle.image = image;
- _in_subtitle.rect = rect;
- _in_subtitle.from = from;
- _in_subtitle.to = to;
-
- update_subtitle ();
-}
-
-void
-Player::update_subtitle ()
+Player::update_subtitle_from_image ()
{
- shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
+ shared_ptr<Piece> piece = _image_subtitle.piece.lock ();
if (!piece) {
return;
}
- if (!_in_subtitle.image) {
+ if (!_image_subtitle.subtitle->image) {
_out_subtitle.image.reset ();
return;
}
shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
assert (sc);
- dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
+ dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect;
libdcp::Size scaled_size;
in_rect.x += sc->subtitle_x_offset ();
_out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
_out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
- _out_subtitle.image = _in_subtitle.image->scale (
+ _out_subtitle.image = _image_subtitle.subtitle->image->scale (
scaled_size,
Scaler::from_id ("bicubic"),
- _in_subtitle.image->pixel_format (),
+ _image_subtitle.subtitle->image->pixel_format (),
true
);
-
- /* XXX: hack */
- Time from = _in_subtitle.from;
- Time to = _in_subtitle.to;
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
- if (vc) {
- from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
- to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
- }
- _out_subtitle.from = from + piece->content->position ();
- _out_subtitle.to = to + piece->content->position ();
+ _out_subtitle.from = _image_subtitle.subtitle->dcp_time + piece->content->position ();
+ _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to + piece->content->position ();
}
/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
bool
Player::repeat_last_video ()
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
+ if (!_last_incoming_video.video || !_have_valid_pieces) {
return false;
}
- process_video (
+ emit_video (
_last_incoming_video.weak_piece,
- _last_incoming_video.image,
- _last_incoming_video.eyes,
- _last_incoming_video.same,
- _last_incoming_video.frame,
- _last_incoming_video.extra
+ _last_incoming_video.video
);
return true;
}
+void
+Player::update_subtitle_from_text ()
+{
+ if (_text_subtitle.subtitle->subs.empty ()) {
+ _out_subtitle.image.reset ();
+ return;
+ }
+
+ render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position);
+}
+
+void
+Player::set_approximate_size ()
+{
+ _approximate_size = true;
+}
+
PlayerImage::PlayerImage (
shared_ptr<const Image> in,
Crop crop,
}
shared_ptr<Image>
-PlayerImage::image ()
+PlayerImage::image (AVPixelFormat format, bool aligned)
{
- shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
-
+ shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
if (_subtitle_image) {
return out;
}
+
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
+}
+
+PlayerStatistics const &
+Player::statistics () const
+{
+ return _statistics;
+}
int
TranscodeJob::remaining_time () const
{
- if (!_transcoder) {
+ /* _transcoder might be destroyed by the job-runner thread */
+ shared_ptr<Transcoder> t = _transcoder;
+
+ if (!t) {
return 0;
}
- float fps = _transcoder->current_encoding_rate ();
+ float fps = t->current_encoding_rate ();
if (fps == 0) {
return 0;
}
/* Compute approximate proposed length here, as it's only here that we need it */
- VideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
- OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
++ VideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
return left / fps;
}
#include <iostream>
#include <fstream>
#include <climits>
+ #include <stdexcept>
#ifdef DCPOMATIC_POSIX
#include <execinfo.h>
#include <cxxabi.h>
#include <openssl/md5.h>
#include <magick/MagickCore.h>
#include <magick/version.h>
+#include <pangomm/init.h>
#include <libdcp/version.h>
#include <libdcp/util.h>
#include <libdcp/signer_chain.h>
using std::numeric_limits;
using std::pair;
using std::cout;
+ using std::bad_alloc;
using std::streampos;
+ using std::set_terminate;
using boost::shared_ptr;
using boost::thread;
using boost::lexical_cast;
}
#endif
+ /* From http://stackoverflow.com/questions/2443135/how-do-i-find-where-an-exception-was-thrown-in-c */
+ void
+ terminate ()
+ {
+ static bool tried_throw = false;
+
+ try {
+ // try once to re-throw currently active exception
+ if (!tried_throw++) {
+ throw;
+ }
+ }
+ catch (const std::exception &e) {
+ std::cerr << __FUNCTION__ << " caught unhandled exception. what(): "
+ << e.what() << std::endl;
+ }
+ catch (...) {
+ std::cerr << __FUNCTION__ << " caught unknown/unhandled exception."
+ << std::endl;
+ }
+
+ #ifdef DCPOMATIC_POSIX
+ stacktrace (cout, 50);
+ #endif
+ abort();
+ }
+
/** Call the required functions to set up DCP-o-matic's static arrays, etc.
* Must be called from the UI thread, if there is one.
*/
boost::filesystem::path lib = app_contents ();
lib /= "lib";
setenv ("LTDL_LIBRARY_PATH", lib.c_str (), 1);
- #endif
+ #endif
+
+ set_terminate (terminate);
+ Pango::init ();
libdcp::init ();
Ratio::setup_ratios ();
* @return Equivalent number of audio frames for `v'.
*/
int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second)
{
return ((int64_t) v * audio_sample_rate / frames_per_second);
}
return channels[c];
}
-FrameRateConversion::FrameRateConversion (float source, int dcp)
+FrameRateChange::FrameRateChange (float source, int dcp)
: skip (false)
, repeat (1)
, change_speed (false)
repeat = round (dcp / source);
}
- change_speed = !about_equal (source * factor(), dcp);
+ speed_up = dcp / (source * factor());
+ change_speed = !about_equal (speed_up, 1.0);
if (!skip && repeat == 1 && !change_speed) {
description = _("Content and DCP have the same rate.\n");
return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
}
-
+DCPTime
+time_round_up (DCPTime t, DCPTime nearest)
+{
+ DCPTime const a = t + nearest - 1;
+ return a - (a % nearest);
+}
++
+ void *
+ wrapped_av_malloc (size_t s)
+ {
+ void* p = av_malloc (s);
+ if (!p) {
+ throw bad_alloc ();
+ }
+ return p;
+ }
extern boost::shared_ptr<const libdcp::Signer> make_signer ();
extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
-struct FrameRateConversion
+struct FrameRateChange
{
- FrameRateConversion (float, int);
+ FrameRateChange (float, int);
/** @return factor by which to multiply a source frame rate
to get the effective rate after any skip or repeat has happened.
*/
bool change_speed;
+ /** Amount by which the video is being sped-up in the DCP; e.g. for a
+ * 24fps source in a 25fps DCP this would be 25/24.
+ */
+ float speed_up;
+
std::string description;
};
extern int dcp_audio_frame_rate (int);
extern int stride_round_up (int, int const *, int);
+extern DCPTime time_round_up (DCPTime, DCPTime);
extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
extern std::string get_required_string (std::multimap<std::string, std::string> const & kv, std::string k);
extern int get_optional_int (std::multimap<std::string, std::string> const & kv, std::string k);
extern std::string get_optional_string (std::multimap<std::string, std::string> const & kv, std::string k);
+ extern void* wrapped_av_malloc (size_t);
/** @class Socket
* @brief A class to wrap a boost::asio::ip::tcp::socket with some things
int _timeout;
};
-extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+extern int64_t video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second);
class LocaleGuard
{
player.cc
playlist.cc
ratio.cc
+ render_subtitles.cc
resampler.cc
scp_dcp_job.cc
scaler.cc
sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
+ subrip.cc
+ subrip_content.cc
+ subrip_decoder.cc
subtitle_content.cc
subtitle_decoder.cc
timer.cc
"""
def build(bld):
- if bld.env.STATIC:
+ if bld.env.BUILD_STATIC:
obj = bld(features = 'cxx cxxstlib')
else:
obj = bld(features = 'cxx cxxshlib')
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
- CURL ZIP QUICKMAIL
+ CURL ZIP QUICKMAIL PANGOMM CAIROMM
"""
obj.source = sources + ' version.cc'
if bld.env.TARGET_WINDOWS:
obj.uselib += ' WINSOCK2 BFD DBGHELP IBERTY SHLWAPI MSWSOCK BOOST_LOCALE'
obj.source += ' stack.cpp'
- if bld.env.STATIC:
+ if bld.env.BUILD_STATIC:
obj.uselib += ' XML++'
obj.target = 'dcpomatic'
static int frame = 0;
void
-process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, Time)
+process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, DCPTime)
{
- shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
- shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
+ shared_ptr<DCPVideoFrame> local (
+ new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)
+ );
+
+ shared_ptr<DCPVideoFrame> remote (
+ new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)
+ );
cout << "Frame " << frame << ": ";
cout.flush ();
dcpomatic_setup ();
- server = new ServerDescription (server_host, 1);
- film.reset (new Film (film_dir));
- film->read_metadata ();
-
- shared_ptr<Player> player = film->make_player ();
- player->disable_audio ();
-
try {
+ server = new ServerDescription (server_host, 1);
+ film.reset (new Film (film_dir));
+ film->read_metadata ();
+
+ shared_ptr<Player> player = film->make_player ();
+ player->disable_audio ();
+
player->Video.connect (boost::bind (process_video, _1, _2, _3, _5));
bool done = false;
while (!done) {
gc->SetPen (*wxLIGHT_GREY_PEN);
gc->StrokePath (grid);
- gc->DrawText (_("Time"), data_width, _height - _y_origin + db_label_height / 2);
+ gc->DrawText (_("DCPTime"), data_width, _height - _y_origin + db_label_height / 2);
if (_type_visible[AudioPoint::PEAK]) {
for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
p += pow (*j, 2);
}
- p = sqrt (p / smoothing.size ());
+ if (smoothing.size() > 0) {
+ p = sqrt (p / smoothing.size ());
+ }
path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (p));
}
#include "lib/player.h"
#include "lib/video_content.h"
#include "lib/video_decoder.h"
+#include "lib/timer.h"
#include "film_viewer.h"
#include "wx_util.h"
using std::max;
using std::cout;
using std::list;
+ using std::bad_alloc;
using std::make_pair;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
return;
}
- _player = f->make_player ();
+ try {
+ _player = f->make_player ();
+ } catch (bad_alloc) {
+ error_dialog (this, _("There is not enough free memory to do that."));
+ _film.reset ();
+ return;
+ }
+
_player->disable_audio ();
+ _player->set_approximate_size ();
_player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5));
_player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
fetch_next_frame ();
- Time const len = _film->length ();
+ DCPTime const len = _film->length ();
if (len) {
int const new_slider_position = 4096 * _player->video_position() / len;
FilmViewer::slider_moved ()
{
if (_film && _player) {
- _player->seek (_slider->GetValue() * _film->length() / 4096, false);
- fetch_next_frame ();
+ try {
+ _player->seek (_slider->GetValue() * _film->length() / 4096, false);
+ fetch_next_frame ();
+ } catch (OpenFileError& e) {
+ /* There was a problem opening a content file; we'll let this slide as it
+ probably means a missing content file, which we're already taking care of.
+ */
+ }
}
}
_out_size.width = max (64, _out_size.width);
_out_size.height = max (64, _out_size.height);
+ /* The player will round its image down to the nearest 4 pixels
+ to speed up its scale, so do similar here to avoid black borders
+ around things. This is a bit of a hack.
+ */
+ _out_size.width &= ~3;
+ _out_size.height &= ~3;
+
_player->set_video_container_size (_out_size);
}
}
void
-FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t)
+FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, DCPTime t)
{
if (eyes == EYES_RIGHT) {
return;
}
-
- _frame = image->image ();
+
+ /* Going via BGRA here makes the scaler faster then using RGB24 directly (about
+ twice on x86 Linux).
+ */
+ shared_ptr<Image> im = image->image (PIX_FMT_BGRA, true);
+ _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
_got_frame = true;
set_position_text (t);
}
void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text (DCPTime t)
{
if (!_film) {
_frame_number->SetLabel ("0");
We want to see the one before it, so we need to go back 2.
*/
- Time p = _player->video_position() - _film->video_frames_to_time (2);
+ DCPTime p = _player->video_position() - _film->video_frames_to_time (2);
if (p < 0) {
p = 0;
}
- _player->seek (p, true);
- fetch_next_frame ();
+ try {
+ _player->seek (p, true);
+ fetch_next_frame ();
+ } catch (OpenFileError& e) {
+ /* There was a problem opening a content file; we'll let this slide as it
+ probably means a missing content file, which we're already taking care of.
+ */
+ }
}
void
class View : public boost::noncopyable
{
public:
- View (Timeline& t)
+ View (DCPTimeline& t)
: _timeline (t)
{
protected:
virtual void do_paint (wxGraphicsContext *) = 0;
- int time_x (Time t) const
+ int time_x (DCPTime t) const
{
return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
}
- Timeline& _timeline;
+ DCPTimeline& _timeline;
private:
dcpomatic::Rect<int> _last_paint_bbox;
class ContentView : public View
{
public:
- ContentView (Timeline& tl, shared_ptr<Content> c)
+ ContentView (DCPTimeline& tl, shared_ptr<Content> c)
: View (tl)
, _content (c)
, _track (0)
return;
}
- Time const position = cont->position ();
- Time const len = cont->length_after_trim ();
+ DCPTime const position = cont->position ();
+ DCPTime const len = cont->length_after_trim ();
wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
class AudioContentView : public ContentView
{
public:
- AudioContentView (Timeline& tl, shared_ptr<Content> c)
+ AudioContentView (DCPTimeline& tl, shared_ptr<Content> c)
: ContentView (tl, c)
{}
class VideoContentView : public ContentView
{
public:
- VideoContentView (Timeline& tl, shared_ptr<Content> c)
+ VideoContentView (DCPTimeline& tl, shared_ptr<Content> c)
: ContentView (tl, c)
{}
}
};
-class TimeAxisView : public View
+class DCPTimeAxisView : public View
{
public:
- TimeAxisView (Timeline& tl, int y)
+ DCPTimeAxisView (DCPTimeline& tl, int y)
: View (tl)
, _y (y)
{}
path.AddLineToPoint (_timeline.width(), _y);
gc->StrokePath (path);
- Time t = 0;
+ DCPTime t = 0;
while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
wxGraphicsPath path = gc->CreatePath ();
path.MoveToPoint (time_x (t), _y - 4);
};
-Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
+DCPTimeline::DCPTimeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
: wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
, _film_editor (ed)
, _film (film)
- , _time_axis_view (new TimeAxisView (*this, 32))
+ , _time_axis_view (new DCPTimeAxisView (*this, 32))
, _tracks (0)
, _pixels_per_time_unit (0)
, _left_down (false)
SetDoubleBuffered (true);
#endif
- Bind (wxEVT_PAINT, boost::bind (&Timeline::paint, this));
- Bind (wxEVT_LEFT_DOWN, boost::bind (&Timeline::left_down, this, _1));
- Bind (wxEVT_LEFT_UP, boost::bind (&Timeline::left_up, this, _1));
- Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down, this, _1));
- Bind (wxEVT_MOTION, boost::bind (&Timeline::mouse_moved, this, _1));
- Bind (wxEVT_SIZE, boost::bind (&Timeline::resized, this));
+ Bind (wxEVT_PAINT, boost::bind (&DCPTimeline::paint, this));
+ Bind (wxEVT_LEFT_DOWN, boost::bind (&DCPTimeline::left_down, this, _1));
+ Bind (wxEVT_LEFT_UP, boost::bind (&DCPTimeline::left_up, this, _1));
+ Bind (wxEVT_RIGHT_DOWN, boost::bind (&DCPTimeline::right_down, this, _1));
+ Bind (wxEVT_MOTION, boost::bind (&DCPTimeline::mouse_moved, this, _1));
+ Bind (wxEVT_SIZE, boost::bind (&DCPTimeline::resized, this));
playlist_changed ();
SetMinSize (wxSize (640, tracks() * track_height() + 96));
- _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+ _playlist_connection = film->playlist()->Changed.connect (bind (&DCPTimeline::playlist_changed, this));
}
void
-Timeline::paint ()
+DCPTimeline::paint ()
{
wxPaintDC dc (this);
}
void
-Timeline::playlist_changed ()
+DCPTimeline::playlist_changed ()
{
ensure_ui_thread ();
}
void
-Timeline::assign_tracks ()
+DCPTimeline::assign_tracks ()
{
for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
}
int
-Timeline::tracks () const
+DCPTimeline::tracks () const
{
return _tracks;
}
void
-Timeline::setup_pixels_per_time_unit ()
+DCPTimeline::setup_pixels_per_time_unit ()
{
shared_ptr<const Film> film = _film.lock ();
- if (!film) {
+ if (!film || film->length() == 0) {
return;
}
}
shared_ptr<View>
-Timeline::event_to_view (wxMouseEvent& ev)
+DCPTimeline::event_to_view (wxMouseEvent& ev)
{
ViewList::iterator i = _views.begin();
Position<int> const p (ev.GetX(), ev.GetY());
}
void
-Timeline::left_down (wxMouseEvent& ev)
+DCPTimeline::left_down (wxMouseEvent& ev)
{
shared_ptr<View> view = event_to_view (ev);
shared_ptr<ContentView> content_view = dynamic_pointer_cast<ContentView> (view);
}
void
-Timeline::left_up (wxMouseEvent& ev)
+DCPTimeline::left_up (wxMouseEvent& ev)
{
_left_down = false;
}
void
-Timeline::mouse_moved (wxMouseEvent& ev)
+DCPTimeline::mouse_moved (wxMouseEvent& ev)
{
if (!_left_down) {
return;
}
void
-Timeline::right_down (wxMouseEvent& ev)
+DCPTimeline::right_down (wxMouseEvent& ev)
{
shared_ptr<View> view = event_to_view (ev);
shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (view);
}
void
-Timeline::set_position_from_event (wxMouseEvent& ev)
+DCPTimeline::set_position_from_event (wxMouseEvent& ev)
{
wxPoint const p = ev.GetPosition();
return;
}
- Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
+ DCPTime new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
if (_snap) {
bool first = true;
- Time nearest_distance = TIME_MAX;
- Time nearest_new_position = TIME_MAX;
+ DCPTime nearest_distance = TIME_MAX;
+ DCPTime nearest_new_position = TIME_MAX;
/* Find the nearest content edge; this is inefficient */
for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
{
/* Snap starts to ends */
- Time const d = abs (cv->content()->end() - new_position);
+ DCPTime const d = abs (cv->content()->end() - new_position);
if (first || d < nearest_distance) {
nearest_distance = d;
nearest_new_position = cv->content()->end();
{
/* Snap ends to starts */
- Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
+ DCPTime const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
if (d < nearest_distance) {
nearest_distance = d;
nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
}
void
-Timeline::force_redraw (dcpomatic::Rect<int> const & r)
+DCPTimeline::force_redraw (dcpomatic::Rect<int> const & r)
{
RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
}
shared_ptr<const Film>
-Timeline::film () const
+DCPTimeline::film () const
{
return _film.lock ();
}
void
-Timeline::resized ()
+DCPTimeline::resized ()
{
setup_pixels_per_time_unit ();
}
void
-Timeline::clear_selection ()
+DCPTimeline::clear_selection ()
{
for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
}
}
-Timeline::ContentViewList
-Timeline::selected_views () const
+DCPTimeline::ContentViewList
+DCPTimeline::selected_views () const
{
ContentViewList sel;
}
ContentList
-Timeline::selected_content () const
+DCPTimeline::selected_content () const
{
ContentList sel;
ContentViewList views = selected_views ();
int total_out = 0;
/* 3 hours */
- int64_t const N = from * 60 * 60 * 3;
-
+ int64_t const N = int64_t (from) * 60 * 60 * 3;
-
++
+ /* XXX: no longer checks anything */
-
for (int64_t i = 0; i < N; i += 1000) {
shared_ptr<AudioBuffers> a (new AudioBuffers (1, 1000));
a->make_silent ();
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> r = resamp.run (a, i);
- BOOST_CHECK_EQUAL (r.second, total_out);
- total_out += r.first->frames ();
+ shared_ptr<const AudioBuffers> r = resamp.run (a);
+ total_out += r->frames ();
}
}
""", msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
def build(bld):
- obj = bld(features = 'cxx cxxprogram')
+ obj = bld(features='cxx cxxprogram')
obj.name = 'unit-tests'
- obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+ obj.uselib = 'BOOST_TEST BOOST_THREAD DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
obj.use = 'libdcpomatic'
obj.source = """
4k_test.cc
ffmpeg_dcp_test.cc
ffmpeg_examiner_test.cc
ffmpeg_pts_offset.cc
+ ffmpeg_seek_test.cc
file_group_test.cc
film_metadata_test.cc
frame_rate_test.cc
pixel_formats_test.cc
play_test.cc
ratio_test.cc
+ repeat_frame_test.cc
recover_test.cc
resampler_test.cc
scaling_test.cc
+ seek_zero_test.cc
silence_padding_test.cc
+ skip_frame_test.cc
stream_test.cc
+ subrip_test.cc
test.cc
threed_test.cc
util_test.cc
obj.target = 'unit-tests'
obj.install_path = ''
+
+ obj = bld(features='cxx cxxprogram')
+ obj.name = 'long-unit-tests'
+ obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+ obj.use = 'libdcpomatic'
+ obj.source = """
+ test.cc
+ long_ffmpeg_seek_test.cc
+ """
+
+ obj.target = 'long-unit-tests'
+ obj.install_path = ''
import sys
APPNAME = 'dcpomatic'
- VERSION = '1.64.0devel'
+ VERSION = '1.64.11devel'
def options(opt):
opt.load('compiler_cxx')
opt.load('winres')
- opt.add_option('--enable-debug', action='store_true', default=False, help='build with debugging information and without optimisation')
- opt.add_option('--disable-gui', action='store_true', default=False, help='disable building of GUI tools')
- opt.add_option('--target-windows', action='store_true', default=False, help='set up to do a cross-compile to Windows')
- opt.add_option('--static', action='store_true', default=False, help='build statically, and link statically to libdcp and FFmpeg')
- opt.add_option('--magickpp-config', action='store', default='Magick++-config', help='path to Magick++-config')
- opt.add_option('--wx-config', action='store', default='wx-config', help='path to wx-config')
+ opt.add_option('--enable-debug', action='store_true', default=False, help='build with debugging information and without optimisation')
+ opt.add_option('--disable-gui', action='store_true', default=False, help='disable building of GUI tools')
+ opt.add_option('--target-windows', action='store_true', default=False, help='set up to do a cross-compile to make a Windows package')
+ opt.add_option('--target-debian', action='store_true', default=False, help='set up to compile for a Debian/Ubuntu package')
+ opt.add_option('--target-centos', action='store_true', default=False, help='set up to compile for a Centos package')
+ opt.add_option('--magickpp-config', action='store', default='Magick++-config', help='path to Magick++-config')
+ opt.add_option('--wx-config', action='store', default='wx-config', help='path to wx-config')
opt.add_option('--address-sanitizer', action='store_true', default=False, help='build with address sanitizer')
+ opt.add_option('--install-prefix', default=None, help='prefix of where DCP-o-matic will be installed')
- def pkg_config_args(conf):
- if conf.env.STATIC:
- return '--cflags'
+ def static_ffmpeg(conf):
+ conf.check_cfg(package='libavformat', args='--cflags', uselib_store='AVFORMAT', mandatory=True)
+ conf.env.STLIB_AVFORMAT = ['avformat']
+ conf.check_cfg(package='libavfilter', args='--cflags', uselib_store='AVFILTER', mandatory=True)
+ conf.env.STLIB_AVFILTER = ['avfilter', 'swresample']
+ conf.check_cfg(package='libavcodec', args='--cflags', uselib_store='AVCODEC', mandatory=True)
+ conf.env.STLIB_AVCODEC = ['avcodec']
+ conf.env.LIB_AVCODEC = ['z']
+ conf.check_cfg(package='libavutil', args='--cflags', uselib_store='AVUTIL', mandatory=True)
+ conf.env.STLIB_AVUTIL = ['avutil']
+ conf.check_cfg(package='libswscale', args='--cflags', uselib_store='SWSCALE', mandatory=True)
+ conf.env.STLIB_SWSCALE = ['swscale']
+ conf.check_cfg(package='libswresample', args='--cflags', uselib_store='SWRESAMPLE', mandatory=True)
+ conf.env.STLIB_SWRESAMPLE = ['swresample']
+ conf.check_cfg(package='libpostproc', args='--cflags', uselib_store='POSTPROC', mandatory=True)
+ conf.env.STLIB_POSTPROC = ['postproc']
+
+ def dynamic_ffmpeg(conf):
+ 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='libavutil', args='--cflags --libs', uselib_store='AVUTIL', mandatory=True)
+ conf.check_cfg(package='libswscale', args='--cflags --libs', uselib_store='SWSCALE', mandatory=True)
+ conf.check_cfg(package='libswresample', args='--cflags --libs', uselib_store='SWRESAMPLE', mandatory=True)
+ conf.check_cfg(package='libpostproc', args='--cflags --libs', uselib_store='POSTPROC', mandatory=True)
+
+ def static_openjpeg(conf):
+ conf.check_cfg(package='libopenjpeg', args='--cflags', atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
+ conf.check_cfg(package='libopenjpeg', args='--cflags', max_version='1.5.1', mandatory=True)
+ conf.env.STLIB_OPENJPEG = ['openjpeg']
+
+ def dynamic_openjpeg(conf):
+ 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)
+
+ def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
+ conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
+ conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
+ conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+ conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
+
+ if static_boost:
+ conf.env.STLIB_DCP.append('boost_system')
+
+ if static_xmlpp:
+ conf.env.STLIB_DCP.append('xml++-2.6')
+ else:
+ conf.env.LIB_DCP.append('xml++-2.6')
+
+ if static_xmlsec:
+ conf.env.STLIB_DCP.append('xmlsec1-openssl')
+ conf.env.STLIB_DCP.append('xmlsec1')
else:
- return '--cflags --libs'
+ conf.env.LIB_DCP.append('xmlsec1-openssl')
+ conf.env.LIB_DCP.append('xmlsec1')
+
+ if static_ssh:
+ conf.env.STLIB_DCP.append('ssh')
+ else:
+ conf.env.LIB_DCP.append('ssh')
+
+ def dynamic_dcp(conf):
+ conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+ conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
+
+ def dynamic_ssh(conf):
+ conf.check_cc(fragment="""
+ #include <libssh/libssh.h>\n
+ int main () {\n
+ ssh_session s = ssh_new ();\n
+ return 0;\n
+ }
+ """, msg='Checking for library libssh', mandatory=True, lib='ssh', uselib_store='SSH')
+
+ def dynamic_boost(conf, lib_suffix, thread):
+ 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_cxx(fragment="""
+ #include <boost/thread.hpp>\n
+ int main() { boost::thread t (); }\n
+ """, msg='Checking for boost threading library',
+ libpath='/usr/local/lib',
+ lib=[thread, 'boost_system%s' % lib_suffix],
+ uselib_store='BOOST_THREAD')
+
+ conf.check_cxx(fragment="""
+ #include <boost/filesystem.hpp>\n
+ int main() { boost::filesystem::copy_file ("a", "b"); }\n
+ """, msg='Checking for boost filesystem library',
+ libpath='/usr/local/lib',
+ lib=['boost_filesystem%s' % lib_suffix, 'boost_system%s' % lib_suffix],
+ uselib_store='BOOST_FILESYSTEM')
+
+ conf.check_cxx(fragment="""
+ #include <boost/date_time.hpp>\n
+ int main() { boost::gregorian::day_clock::local_day(); }\n
+ """, msg='Checking for boost datetime library',
+ libpath='/usr/local/lib',
+ lib=['boost_date_time%s' % lib_suffix, 'boost_system%s' % lib_suffix],
+ uselib_store='BOOST_DATETIME')
+
+ conf.check_cxx(fragment="""
+ #include <boost/signals2.hpp>\n
+ int main() { boost::signals2::signal<void (int)> x; }\n
+ """,
+ msg='Checking for boost signals2 library',
+ uselib_store='BOOST_SIGNALS2')
+
+ def static_boost(conf, lib_suffix):
+ conf.env.STLIB_BOOST_THREAD = ['boost_thread']
+ conf.env.STLIB_BOOST_FILESYSTEM = ['boost_filesystem%s' % lib_suffix]
+ conf.env.STLIB_BOOST_DATETIME = ['boost_date_time%s' % lib_suffix, 'boost_system%s' % lib_suffix]
+ conf.env.STLIB_BOOST_SIGNALS2 = ['boost_signals2']
+
+ def dynamic_quickmail(conf):
+ conf.check_cxx(fragment="""
+ #include <quickmail.h>
+ int main(void) { quickmail_initialize (); }
+ """,
+ mandatory=True,
+ msg='Checking for libquickmail',
+ libpath='/usr/local/lib',
+ lib=['quickmail', 'curl'],
+ uselib_store='QUICKMAIL')
def configure(conf):
conf.load('compiler_cxx')
if conf.options.target_windows:
conf.load('winres')
+ # conf.options -> conf.env
conf.env.TARGET_WINDOWS = conf.options.target_windows
conf.env.DISABLE_GUI = conf.options.disable_gui
- conf.env.STATIC = conf.options.static
+ conf.env.TARGET_DEBIAN = conf.options.target_debian
+ conf.env.TARGET_CENTOS = conf.options.target_centos
conf.env.VERSION = VERSION
conf.env.TARGET_OSX = sys.platform == 'darwin'
conf.env.TARGET_LINUX = not conf.env.TARGET_WINDOWS and not conf.env.TARGET_OSX
+ # true if we should build dcpomatic/libdcpomatic/libdcpomatic-wx statically
+ conf.env.BUILD_STATIC = conf.options.target_debian or conf.options.target_centos
+ if conf.options.install_prefix is None:
+ conf.env.INSTALL_PREFIX = conf.env.PREFIX
+ else:
+ conf.env.INSTALL_PREFIX = conf.options.install_prefix
# Common CXXFLAGS
conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS', '-D__STDC_LIMIT_MACROS', '-msse', '-mfpmath=sse', '-ffast-math', '-fno-strict-aliasing',
conf.env.append_value('CXXFLAGS', ['-fsanitize=address', '-fno-omit-frame-pointer'])
conf.env.append_value('LINKFLAGS', ['-fsanitize=address'])
- # Windows-specific
+ #
+ # Platform-specific CFLAGS hacks and other tinkering
+ #
+
+ # Windows
if conf.env.TARGET_WINDOWS:
conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_WINDOWS', '-DWIN32_LEAN_AND_MEAN', '-DBOOST_USE_WINDOWS_H', '-DUNICODE', '-DBOOST_THREAD_PROVIDES_GENERIC_SHARED_MUTEX_ON_WIN'])
wxrc = os.popen('wx-config --rescomp').read().split()[1:]
boost_lib_suffix = '-mt'
boost_thread = 'boost_thread_win32-mt'
- # POSIX-specific
+ # POSIX
if conf.env.TARGET_LINUX or conf.env.TARGET_OSX:
conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_POSIX')
conf.env.append_value('CXXFLAGS', '-DPOSIX_LOCALE_PREFIX="%s/share/locale"' % conf.env['PREFIX'])
boost_thread = 'boost_thread'
conf.env.append_value('LINKFLAGS', '-pthread')
- # Linux-specific
+ # Linux
if conf.env.TARGET_LINUX:
conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
+
+ if conf.env.TARGET_DEBIAN:
# libxml2 seems to be linked against this on Ubuntu but it doesn't mention it in its .pc file
conf.check_cfg(package='liblzma', args='--cflags --libs', uselib_store='LZMA', mandatory=True)
- if not conf.env.DISABLE_GUI:
- if conf.env.STATIC:
- conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
- else:
- # On Linux we need to be able to include <gtk/gtk.h> to check GTK's version
- conf.check_cfg(package='gtk+-2.0', args='--cflags', uselib_store='GTK', mandatory=True)
-
- # OSX-specific
+
+ if not conf.env.DISABLE_GUI and conf.env.TARGET_LINUX:
+ conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
+
+ # OSX
if conf.env.TARGET_OSX:
conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_OSX')
conf.env.append_value('LINKFLAGS', '-headerpad_max_install_names')
- # Dependencies which are dynamically linked everywhere except --static
- # Get libs only when we are dynamically linking
- conf.check_cfg(package='libdcp', atleast_version='0.92', args=pkg_config_args(conf), uselib_store='DCP', mandatory=True)
- # Remove erroneous escaping of quotes from xmlsec1 defines
- conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
- conf.check_cfg(package='libcxml', atleast_version='0.08', args=pkg_config_args(conf), uselib_store='CXML', mandatory=True)
- conf.check_cfg(package='libavformat', args=pkg_config_args(conf), uselib_store='AVFORMAT', mandatory=True)
- conf.check_cfg(package='libavfilter', args=pkg_config_args(conf), uselib_store='AVFILTER', mandatory=True)
- conf.check_cfg(package='libavcodec', args=pkg_config_args(conf), uselib_store='AVCODEC', mandatory=True)
- conf.check_cfg(package='libavutil', args=pkg_config_args(conf), uselib_store='AVUTIL', mandatory=True)
- conf.check_cfg(package='libswscale', args=pkg_config_args(conf), uselib_store='SWSCALE', mandatory=True)
- conf.check_cfg(package='libswresample', args=pkg_config_args(conf), uselib_store='SWRESAMPLE', mandatory=True)
- conf.check_cfg(package='libpostproc', args=pkg_config_args(conf), uselib_store='POSTPROC', mandatory=True)
- conf.check_cfg(package='libopenjpeg', args=pkg_config_args(conf), atleast_version='1.5.0', uselib_store='OPENJPEG', mandatory=True)
- conf.check_cfg(package='libopenjpeg', args=pkg_config_args(conf), max_version='1.5.1', mandatory=True)
-
- if conf.env.STATIC:
- # This is hackio grotesquio for static builds (ie for .deb packages). We need to link some things
- # statically and some dynamically, or things get horribly confused and the dynamic linker (I think)
- # crashes. These calls do what the check_cfg calls would have done, but specify the
- # different bits as static or dynamic as required. It'll break if you look at it funny, but
- # I think anyone else who builds would do so dynamically.
- conf.env.STLIB_CXML = ['cxml']
- conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
- conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2', 'xmlsec1', 'xmlsec1-openssl', 'xslt']
- conf.env.STLIB_CXML = ['cxml']
- conf.env.STLIB_AVFORMAT = ['avformat']
- conf.env.STLIB_AVFILTER = ['avfilter', 'swresample']
- conf.env.STLIB_AVCODEC = ['avcodec']
- conf.env.LIB_AVCODEC = ['z']
- conf.env.STLIB_AVUTIL = ['avutil']
- conf.env.STLIB_SWSCALE = ['swscale']
- conf.env.STLIB_POSTPROC = ['postproc']
- conf.env.STLIB_SWRESAMPLE = ['swresample']
- conf.env.STLIB_OPENJPEG = ['openjpeg']
- conf.env.STLIB_QUICKMAIL = ['quickmail']
- else:
- conf.check_cxx(fragment="""
- #include <quickmail.h>
- int main(void) { quickmail_initialize (); }
- """,
- mandatory=True,
- msg='Checking for libquickmail',
- libpath='/usr/local/lib',
- lib=['quickmail', 'curl'],
- uselib_store='QUICKMAIL')
+ #
+ # Dependencies.
+ # There's probably a neater way of expressing these, but I've gone for brute force for now.
+ #
- # Dependencies which are always dynamically linked
- 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= '', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
- conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
- conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
- conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
- conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
- conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
+ if conf.env.TARGET_DEBIAN:
+ conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags', uselib_store='CXML', mandatory=True)
+ conf.env.STLIB_CXML = ['cxml']
+ conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
+ conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
+ conf.env.STLIB_QUICKMAIL = ['quickmail']
+ static_ffmpeg(conf)
+ static_openjpeg(conf)
+ static_dcp(conf, False, False, False, False)
+ dynamic_boost(conf, boost_lib_suffix, boost_thread)
- 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.')
+ if conf.env.TARGET_CENTOS:
+ conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags --libs-only-L', uselib_store='CXML', mandatory=True)
+ conf.env.STLIB_CXML = ['cxml', 'boost_filesystem']
+ conf.check_cfg(package='libcurl', args='--cflags --libs-only-L', uselib_store='CURL', mandatory=True)
+ conf.env.STLIB_CURL = ['curl']
+ conf.env.LIB_CURL = ['ssh2', 'idn']
+ conf.env.STLIB_QUICKMAIL = ['quickmail', 'curl']
+ conf.env.LIB_QUICKMAIL = ['ssh2', 'idn']
+ static_ffmpeg(conf)
+ static_openjpeg(conf)
+ static_dcp(conf, True, True, True, True)
+ static_boost(conf, boost_lib_suffix)
- conf.check_cc(fragment="""
- #include <libssh/libssh.h>\n
- int main () {\n
- ssh_session s = ssh_new ();\n
- return 0;\n
- }
- """, msg='Checking for library libssh', mandatory=True, lib='ssh', uselib_store='SSH')
-
- conf.check_cxx(fragment="""
- #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')
-
- conf.check_cxx(fragment="""
- #include <boost/filesystem.hpp>\n
- int main() { boost::filesystem::copy_file ("a", "b"); }\n
- """, msg='Checking for boost filesystem library',
- libpath='/usr/local/lib',
- lib=['boost_filesystem%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
- uselib_store='BOOST_FILESYSTEM')
-
- conf.check_cxx(fragment="""
- #include <boost/date_time.hpp>\n
- int main() { boost::gregorian::day_clock::local_day(); }\n
- """, msg='Checking for boost datetime library',
- libpath='/usr/local/lib',
- lib=['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
- uselib_store='BOOST_DATETIME')
-
- # Only Windows versions require boost::locale, which is handy, as it was only introduced in
- # boost 1.48 and so isn't (easily) available on old Ubuntus.
if conf.env.TARGET_WINDOWS:
+ conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
+ conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
conf.check_cxx(fragment="""
#include <boost/locale.hpp>\n
int main() { std::locale::global (boost::locale::generator().generate ("")); }\n
libpath='/usr/local/lib',
lib=['boost_locale%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
uselib_store='BOOST_LOCALE')
+ dynamic_quickmail(conf)
+ dynamic_boost(conf, boost_lib_suffix, boost_thread)
+ dynamic_ffmpeg(conf)
+ dynamic_openjpeg(conf)
+ dynamic_dcp(conf)
+ dynamic_ssh(conf)
- conf.check_cxx(fragment="""
- #include <boost/signals2.hpp>\n
- int main() { boost::signals2::signal<void (int)> x; }\n
- """,
- msg='Checking for boost signals2 library',
- uselib_store='BOOST_SIGNALS2')
+ # Not packaging; just a straight build
+ if not conf.env.TARGET_WINDOWS and not conf.env.TARGET_DEBIAN and not conf.env.TARGET_CENTOS:
+ conf.check_cfg(package='libcxml', atleast_version='0.08', args='--cflags --libs', uselib_store='CXML', mandatory=True)
+ conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XML++', mandatory=True)
+ conf.check_cfg(package='libcurl', args='--cflags --libs', uselib_store='CURL', mandatory=True)
+ dynamic_quickmail(conf)
+ dynamic_boost(conf, boost_lib_suffix, boost_thread)
+ dynamic_ffmpeg(conf)
+ dynamic_dcp(conf)
+ dynamic_openjpeg(conf)
+ dynamic_ssh(conf)
+
+ # Dependencies which are always dynamically linked
+ 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= '', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
+ conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
++ conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
++ conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
conf.check_cc(fragment="""
#include <glib.h>