+2014-08-06 Carl Hetherington <cth@carlh.net>
+
+ * Version 2.0.1 released.
+
+2014-07-15 Carl Hetherington <cth@carlh.net>
+
+ * A variety of changes were made on the 2.0 branch
+ but not documented in the ChangeLog. Most sigificantly:
+
+ - DCP import
+ - Creation of DCPs with proper XML subtitles
+ - Import of .srt and .xml subtitles
+ - Audio processing framework (with some basic processors).
+
+2014-03-07 Carl Hetherington <cth@carlh.net>
+
+ * Add subtitle view.
+
+ 2014-08-09 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.72.10 released.
+
+ 2014-08-09 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.72.8 released.
+
+ 2014-08-08 Carl Hetherington <cth@carlh.net>
+
+ * Approximate support for changing timing details of multiple
+ bits of content at the same time.
+
+ * Allow removal of multiple bits of content at the same time.
+
+ * Version 1.72.7 released.
+
2014-08-04 Carl Hetherington <cth@carlh.net>
* Add BCC option for KDM emails.
2014-07-10 Carl Hetherington <cth@carlh.net>
* Version 1.72.2 released.
+>>>>>>> origin/master
2014-07-10 Carl Hetherington <cth@carlh.net>
-dcpomatic (1.72.10-1) UNRELEASED; urgency=low
+dcpomatic (2.0.1-1) UNRELEASED; urgency=low
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
++<<<<<<< HEAD
+
+ -- Carl Hetherington <carl@dalglish> Wed, 06 Aug 2014 18:59:35 +0100
++=======
+ * New upstream release.
+ * New upstream release.
+
+ -- Carl Hetherington <carl@d1stkfactory> Sat, 09 Aug 2014 12:38:18 +0100
++>>>>>>> origin/master
dcpomatic (0.87-1) UNRELEASED; urgency=low
ENV=/Users/carl/Environments/osx/10.6
ROOT=$1
-appdir="DCP-o-matic.app"
+appdir="DCP-o-matic 2.app"
approot="$appdir/Contents"
libs="$approot/lib"
macos="$approot/MacOS"
relink="$relink|$2"
}
++<<<<<<< HEAD
+universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2 "$WORK/$macos"
+universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_cli "$WORK/$macos"
+universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_server_cli "$WORK/$macos"
+universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_batch "$WORK/$macos"
+universal_copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic2.dylib "$WORK/$libs"
+universal_copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic2-wx.dylib "$WORK/$libs"
+universal_copy_lib $ROOT libcxml "$WORK/$libs"
+universal_copy_lib $ROOT libdcp-1.0 "$WORK/$libs"
+universal_copy_lib $ROOT libasdcp-libdcp-1.0 "$WORK/$libs"
+universal_copy_lib $ROOT libkumu-libdcp-1.0 "$WORK/$libs"
++=======
+ universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic "$WORK/$macos"
+ universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic_cli "$WORK/$macos"
+ universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic_server_cli "$WORK/$macos"
+ universal_copy $ROOT src/dcpomatic/build/src/tools/dcpomatic_batch "$WORK/$macos"
+ universal_copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic.dylib "$WORK/$libs"
+ universal_copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic-wx.dylib "$WORK/$libs"
+ universal_copy_lib $ROOT libcxml "$WORK/$libs"
+ universal_copy_lib $ROOT libdcp "$WORK/$libs"
+ universal_copy_lib $ROOT libasdcp-libdcp "$WORK/$libs"
+ universal_copy_lib $ROOT libkumu-libdcp "$WORK/$libs"
++>>>>>>> origin/master
universal_copy_lib $ROOT libopenjpeg "$WORK/$libs"
universal_copy_lib $ROOT libavdevice "$WORK/$libs"
universal_copy_lib $ROOT libavformat "$WORK/$libs"
relink=`echo $relink | sed -e "s/\+//g"`
-for obj in "$WORK/$macos/dcpomatic" "$WORK/$macos/dcpomatic_batch" "$WORK/$macos/dcpomatic_cli" "$WORK/$macos/dcpomatic_server_cli" "$WORK/$macos/ffprobe" "$WORK/$libs/"*.dylib; do
+for obj in "$WORK/$macos/dcpomatic2" "$WORK/$macos/dcpomatic2_batch" "$WORK/$macos/dcpomatic2_cli" "$WORK/$macos/dcpomatic2_server_cli" "$WORK/$macos/ffprobe" "$WORK/$libs/"*.dylib; do
deps=`otool -L "$obj" | awk '{print $1}' | egrep "($relink)" | egrep "($ENV|$ROOT|boost)"`
changes=""
for dep in $deps; do
+ echo "Relinking $dep into $obj"
base=`basename $dep`
# $dep will be a path within 64/; make a 32/ path too
dep32=`echo $dep | sed -e "s/\/64\//\/32\//g"`
cp icons/kdm_email.png "$WORK/$resources"
cp icons/servers.png "$WORK/$resources"
cp icons/tms.png "$WORK/$resources"
+cp icons/keys.png "$WORK/$resources"
# i18n: DCP-o-matic .mo files
for lang in de_DE es_ES fr_FR it_IT sv_SE nl_NL; do
set theViewOptions to the icon view options of container window
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 64
- set position of item "DCP-o-matic.app" of container window to {90, 80}
+ set position of item "DCP-o-matic 2.app" of container window to {90, 80}
set position of item "Applications" of container window to {310, 80}
close
open
*/
#include "audio_analysis.h"
+#include "audio_buffers.h"
#include "analyse_audio_job.h"
#include "compose.hpp"
#include "film.h"
return _("Analyse audio");
}
- string
- AnalyseAudioJob::json_name () const
- {
- return N_("analyse_audio");
- }
-
void
AnalyseAudioJob::run ()
{
shared_ptr<Playlist> playlist (new Playlist);
playlist->add (content);
shared_ptr<Player> player (new Player (_film, playlist));
- player->disable_video ();
- player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1, _2));
-
- _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points);
+ int64_t const len = _film->length().frames (_film->audio_frame_rate());
+ _samples_per_point = max (int64_t (1), len / _num_points);
_current.resize (_film->audio_channels ());
_analysis.reset (new AudioAnalysis (_film->audio_channels ()));
_done = 0;
- OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
- while (!player->pass ()) {
- set_progress (double (_done) / len);
+ DCPTime const block = DCPTime::from_seconds (1.0 / 8);
+ for (DCPTime t; t < _film->length(); t += block) {
+ analyse (player->get_audio (t, block, false));
+ set_progress (t.seconds() / _film->length().seconds());
}
_analysis->write (content->audio_analysis_path ());
}
void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::analyse (shared_ptr<const AudioBuffers> b)
{
for (int i = 0; i < b->frames(); ++i) {
for (int j = 0; j < b->channels(); ++j) {
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
*/
+/** @file src/lib/analyse_audio_job.h
+ * @brief AnalyseAudioJob class.
+ */
+
#include "job.h"
#include "audio_analysis.h"
#include "types.h"
+#include "dcpomatic_time.h"
class AudioBuffers;
class AudioContent;
+/** @class AnalyseAudioJob
+ * @brief A job to analyse the audio of a piece of AudioContent and make a note of its
+ * broad peak and RMS levels.
+ *
+ * After computing the peak and RMS levels over the length of the content, the job
+ * will write a file to Content::audio_analysis_path.
+ */
class AnalyseAudioJob : public Job
{
public:
AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
std::string name () const;
- std::string json_name () const;
void run ();
private:
- void audio (boost::shared_ptr<const AudioBuffers>, Time);
+ void analyse (boost::shared_ptr<const AudioBuffers>);
boost::weak_ptr<AudioContent> _content;
- OutputAudioFrame _done;
+ int64_t _done;
int64_t _samples_per_point;
std::vector<AudioPoint> _current;
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
#include "audio_decoder.h"
#include "audio_buffers.h"
-#include "exceptions.h"
-#include "log.h"
+#include "audio_processor.h"
#include "resampler.h"
+#include "util.h"
#include "i18n.h"
- using std::stringstream;
using std::list;
using std::pair;
using std::cout;
+using std::min;
+using std::max;
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
- : Decoder (film)
- , _audio_content (content)
- , _audio_position (0)
+AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content)
+ : _audio_content (content)
{
+ if (content->resampled_audio_frame_rate() != content->audio_frame_rate() && content->audio_channels ()) {
+ _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ()));
+ }
+ if (content->audio_processor ()) {
+ _processor = content->audio_processor()->clone (content->resampled_audio_frame_rate ());
+ }
+
+ reset_decoded_audio ();
}
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::reset_decoded_audio ()
{
- Audio (data, frame);
- _audio_position = frame + data->frames ();
+ _decoded_audio = ContentAudio (shared_ptr<AudioBuffers> (new AudioBuffers (_audio_content->processed_audio_channels(), 0)), 0);
}
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- * The player needs to know that there is no audio otherwise it will keep trying to
- * pass() the decoder to get it to emit audio.
+shared_ptr<ContentAudio>
+AudioDecoder::get_audio (AudioFrame frame, AudioFrame length, bool accurate)
+{
+ shared_ptr<ContentAudio> dec;
+
+ AudioFrame const end = frame + length - 1;
+
+ if (frame < _decoded_audio.frame || end > (_decoded_audio.frame + length * 4)) {
+ /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+ seek (ContentTime::from_frames (frame, _audio_content->audio_frame_rate()), accurate);
+ }
+
+ /* Offset of the data that we want from the start of _decoded_audio.audio
+ (to be set up shortly)
+ */
+ AudioFrame decoded_offset = 0;
+
+ /* Now enough pass() calls will either:
+ * (a) give us what we want, or
+ * (b) hit the end of the decoder.
+ *
+ * If we are being accurate, we want the right frames,
+ * otherwise any frames will do.
+ */
+ if (accurate) {
+ /* Keep stuffing data into _decoded_audio until we have enough data, or the subclass does not want to give us any more */
+ while (!pass() && (_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end)) {}
+ decoded_offset = frame - _decoded_audio.frame;
+ } else {
+ while (!pass() && _decoded_audio.audio->frames() < length) {}
+ /* Use decoded_offset of 0, as we don't really care what frames we return */
+ }
+
+ /* The amount of data available in _decoded_audio.audio starting from `frame'. This could be -ve
+ if pass() returned true before we got enough data.
+ */
+ AudioFrame const available = _decoded_audio.audio->frames() - decoded_offset;
+
+ /* We will return either that, or the requested amount, whichever is smaller */
+ AudioFrame const to_return = max ((AudioFrame) 0, min (available, length));
+
+ /* Copy our data to the output */
+ shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), to_return));
+ out->copy_from (_decoded_audio.audio.get(), to_return, decoded_offset, 0);
+
+ AudioFrame const remaining = max ((AudioFrame) 0, available - to_return);
+
+ /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */
+ _decoded_audio.audio->move (decoded_offset + to_return, 0, remaining);
+ /* And set up the number of frames we have left */
+ _decoded_audio.audio->set_frames (remaining);
+ /* Also bump where those frames are in terms of the content */
+ _decoded_audio.frame += decoded_offset + to_return;
+
+ return shared_ptr<ContentAudio> (new ContentAudio (out, frame));
+}
+
+/** Called by subclasses when audio data is ready.
+ *
+ * Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling.
+ * We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ * data out. Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ * The time is passed in here so that after a seek we can set up our _audio_position. The
+ * time is ignored once this has been done.
*/
-bool
-AudioDecoder::has_audio () const
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+ if (_resampler) {
+ data = _resampler->run (data);
+ }
+
+ if (_processor) {
+ data = _processor->run (data);
+ }
+
+ AudioFrame const frame_rate = _audio_content->resampled_audio_frame_rate ();
+
+ if (_seek_reference) {
+ /* We've had an accurate seek and now we're seeing some data */
+ ContentTime const delta = time - _seek_reference.get ();
+ AudioFrame const delta_frames = delta.frames (frame_rate);
+ if (delta_frames > 0) {
+ /* This data comes after the seek time. Pad the data with some silence. */
+ shared_ptr<AudioBuffers> padded (new AudioBuffers (data->channels(), data->frames() + delta_frames));
+ padded->make_silent ();
+ padded->copy_from (data.get(), data->frames(), 0, delta_frames);
+ data = padded;
+ time -= delta;
+ } else if (delta_frames < 0) {
+ /* This data comes before the seek time. Throw some data away */
+ AudioFrame const to_discard = min (-delta_frames, static_cast<AudioFrame> (data->frames()));
+ AudioFrame const to_keep = data->frames() - to_discard;
+ if (to_keep == 0) {
+ /* We have to throw all this data away, so keep _seek_reference and
+ try again next time some data arrives.
+ */
+ return;
+ }
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (data->channels(), to_keep));
+ trimmed->copy_from (data.get(), to_keep, to_discard, 0);
+ data = trimmed;
+ time += ContentTime::from_frames (to_discard, frame_rate);
+ }
+ _seek_reference = optional<ContentTime> ();
+ }
+
+ if (!_audio_position) {
+ _audio_position = time.frames (frame_rate);
+ }
+
+ assert (_audio_position.get() >= (_decoded_audio.frame + _decoded_audio.audio->frames()));
+
+ add (data);
+}
+
+void
+AudioDecoder::add (shared_ptr<const AudioBuffers> data)
+{
+ /* Resize _decoded_audio to fit the new data */
+ int new_size = 0;
+ if (_decoded_audio.audio->frames() == 0) {
+ /* There's nothing in there, so just store the new data */
+ new_size = data->frames ();
+ _decoded_audio.frame = _audio_position.get ();
+ } else {
+ /* Otherwise we need to extend _decoded_audio to include the new stuff */
+ new_size = _audio_position.get() + data->frames() - _decoded_audio.frame;
+ }
+
+ _decoded_audio.audio->ensure_size (new_size);
+ _decoded_audio.audio->set_frames (new_size);
+
+ /* Copy new data in */
+ _decoded_audio.audio->copy_from (data.get(), data->frames(), 0, _audio_position.get() - _decoded_audio.frame);
+ _audio_position = _audio_position.get() + data->frames ();
+
+ /* Limit the amount of data we keep in case nobody is asking for it */
+ int const max_frames = _audio_content->resampled_audio_frame_rate () * 10;
+ if (_decoded_audio.audio->frames() > max_frames) {
+ int const to_remove = _decoded_audio.audio->frames() - max_frames;
+ _decoded_audio.frame += to_remove;
+ _decoded_audio.audio->move (to_remove, 0, max_frames);
+ _decoded_audio.audio->set_frames (max_frames);
+ }
+}
+
+void
+AudioDecoder::flush ()
+{
+ if (!_resampler) {
+ return;
+ }
+
+ shared_ptr<const AudioBuffers> b = _resampler->flush ();
+ if (b) {
+ add (b);
+ }
+}
+
+void
+AudioDecoder::seek (ContentTime t, bool accurate)
{
- return _audio_content->audio_channels () > 0;
+ _audio_position.reset ();
+ reset_decoded_audio ();
+ if (accurate) {
+ _seek_reference = t;
+ }
+ if (_processor) {
+ _processor->flush ();
+ }
}
*/
#include <libxml++/libxml++.h>
-#include <libdcp/colour_matrix.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include "config.h"
#include "colour_conversion.h"
using std::list;
using std::string;
- using std::stringstream;
using std::cout;
using std::vector;
using boost::shared_ptr;
using boost::optional;
-using libdcp::raw_convert;
+using dcp::raw_convert;
ColourConversion::ColourConversion ()
: input_gamma (2.4)
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
- matrix (i, j) = libdcp::colour_matrix::srgb_to_xyz[i][j];
+ matrix (i, j) = dcp::colour_matrix::srgb_to_xyz[i][j];
}
}
}
#include "film.h"
#include "log.h"
#include "config.h"
-#include "dcp_video_frame.h"
+#include "dcp_video.h"
#include "server.h"
#include "cross.h"
#include "writer.h"
#include "server_finder.h"
#include "player.h"
-#include "player_video_frame.h"
+#include "player_video.h"
#include "i18n.h"
using std::pair;
using std::string;
- using std::stringstream;
using std::vector;
using std::list;
using std::cout;
int const Encoder::_history_size = 25;
/** @param f Film that we are encoding */
-Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j)
+Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j, shared_ptr<Writer> writer)
: _film (f)
, _job (j)
, _video_frames_out (0)
, _terminate (false)
+ , _writer (writer)
{
- _have_a_real_frame[EYES_BOTH] = false;
- _have_a_real_frame[EYES_LEFT] = false;
- _have_a_real_frame[EYES_RIGHT] = false;
+
}
Encoder::~Encoder ()
}
void
-Encoder::process_begin ()
+Encoder::begin ()
{
for (int i = 0; i < Config::instance()->num_local_encoding_threads (); ++i) {
_threads.push_back (new boost::thread (boost::bind (&Encoder::encoder_thread, this, optional<ServerDescription> ())));
}
- _writer.reset (new Writer (_film, _job));
ServerFinder::instance()->connect (boost::bind (&Encoder::server_found, this, _1));
}
void
-Encoder::process_end ()
+Encoder::end ()
{
boost::mutex::scoped_lock lock (_mutex);
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<DCPVideo> >::iterator i = _queue.begin(); i != _queue.end(); ++i) {
LOG_GENERAL (N_("Encode left-over frame %1"), (*i)->index ());
try {
_writer->write ((*i)->encode_locally(), (*i)->index (), (*i)->eyes ());
LOG_ERROR (N_("Local encode failed (%1)"), e.what ());
}
}
-
- _writer->finish ();
- _writer.reset ();
}
/** @return an estimate of the current number of frames we are encoding per second,
}
void
-Encoder::process_video (shared_ptr<PlayerVideoFrame> pvf, bool same)
+Encoder::enqueue (shared_ptr<PlayerVideo> pvf)
{
_waker.nudge ();
if (_writer->can_fake_write (_video_frames_out)) {
_writer->fake_write (_video_frames_out, pvf->eyes ());
- _have_a_real_frame[pvf->eyes()] = false;
- frame_done ();
- } else if (same && _have_a_real_frame[pvf->eyes()]) {
- /* Use the last frame that we encoded. */
- _writer->repeat (_video_frames_out, pvf->eyes());
frame_done ();
+ } else if (pvf->has_j2k ()) {
+ _writer->write (pvf->j2k(), _video_frames_out, pvf->eyes ());
} else {
/* Queue this new frame for encoding */
LOG_TIMING ("adding to queue of %1", _queue.size ());
- _queue.push_back (shared_ptr<DCPVideoFrame> (
- new DCPVideoFrame (
- pvf, _video_frames_out, _film->video_frame_rate(),
- _film->j2k_bandwidth(), _film->resolution(), _film->log()
+ _queue.push_back (shared_ptr<DCPVideo> (
+ new DCPVideo (
+ pvf,
+ _video_frames_out,
+ _film->video_frame_rate(),
+ _film->j2k_bandwidth(),
+ _film->resolution(),
+ _film->burn_subtitles(),
+ _film->log()
)
));
waiting on that.
*/
_empty_condition.notify_all ();
- _have_a_real_frame[pvf->eyes()] = true;
}
if (pvf->eyes() != EYES_LEFT) {
}
}
-void
-Encoder::process_audio (shared_ptr<const AudioBuffers> data)
-{
- _writer->write (data);
-}
-
void
Encoder::terminate_threads ()
{
}
LOG_TIMING ("[%1] encoder thread wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
- shared_ptr<DCPVideoFrame> vf = _queue.front ();
+ shared_ptr<DCPVideo> vf = _queue.front ();
LOG_TIMING ("[%1] encoder thread pops frame %2 (%3) from queue", boost::this_thread::get_id(), vf->index(), vf->eyes ());
_queue.pop_front ();
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 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
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "ffmpeg.h"
#include "ffmpeg_content.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
#include "exceptions.h"
#include "util.h"
using std::string;
using std::cout;
- using std::stringstream;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
boost::mutex FFmpeg::_mutex;
, _video_stream (-1)
{
setup_general ();
- setup_video ();
- setup_audio ();
+ setup_decoders ();
}
FFmpeg::~FFmpeg ()
boost::mutex::scoped_lock lm (_mutex);
for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
- AVCodecContext* context = _format_context->streams[i]->codec;
- if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) {
- avcodec_close (context);
- }
+ avcodec_close (_format_context->streams[i]->codec);
}
av_frame_free (&_frame);
-
avformat_close_input (&_format_context);
}
}
void
-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);
-
- if (codec == 0) {
- throw DecodeError (_("could not find video decoder"));
- }
-
- if (avcodec_open2 (context, codec, 0) < 0) {
- throw DecodeError (N_("could not open video decoder"));
- }
-}
-
-void
-FFmpeg::setup_audio ()
+FFmpeg::setup_decoders ()
{
boost::mutex::scoped_lock lm (_mutex);
for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
AVCodecContext* context = _format_context->streams[i]->codec;
- if (context->codec_type != AVMEDIA_TYPE_AUDIO) {
- continue;
- }
AVCodec* codec = avcodec_find_decoder (context->codec_id);
- if (codec == 0) {
- throw DecodeError (_("could not find audio decoder"));
- }
-
- if (avcodec_open2 (context, codec, 0) < 0) {
- throw DecodeError (N_("could not open audio decoder"));
+ if (codec) {
+ if (avcodec_open2 (context, codec, 0) < 0) {
+ throw DecodeError (N_("could not open decoder"));
+ }
}
+
+ /* We are silently ignoring any failures to find suitable decoders here */
}
}
-
AVCodecContext *
FFmpeg::video_codec_context () const
{
AVCodecContext *
FFmpeg::audio_codec_context () const
{
+ if (!_ffmpeg_content->audio_stream ()) {
+ return 0;
+ }
+
return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
}
+AVCodecContext *
+FFmpeg::subtitle_codec_context () const
+{
+ if (!_ffmpeg_content->subtitle_stream ()) {
+ return 0;
+ }
+
+ return _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
+}
+
int
FFmpeg::avio_read (uint8_t* buffer, int const amount)
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
-#include "film.h"
#include "filter.h"
#include "exceptions.h"
#include "image.h"
#include "util.h"
#include "log.h"
#include "ffmpeg_decoder.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
#include "filter_graph.h"
#include "audio_buffers.h"
#include "ffmpeg_content.h"
-#include "image_proxy.h"
+#include "raw_image_proxy.h"
+#include "film.h"
+#include "timer.h"
#include "i18n.h"
-#define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
-#define LOG_ERROR(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
-#define LOG_WARNING(...) film->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+#define LOG_GENERAL(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+#define LOG_ERROR(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
+#define LOG_WARNING(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
using std::cout;
using std::string;
using std::vector;
- using std::stringstream;
using std::list;
using std::min;
using std::pair;
+using std::make_pair;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
-using libdcp::Size;
+using dcp::Size;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
- : Decoder (f)
- , VideoDecoder (f, c)
- , AudioDecoder (f, c)
- , SubtitleDecoder (f)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log)
+ : VideoDecoder (c)
+ , AudioDecoder (c)
+ , SubtitleDecoder (c)
, FFmpeg (c)
- , _subtitle_codec_context (0)
- , _subtitle_codec (0)
- , _decode_video (video)
- , _decode_audio (audio)
- , _pts_offset (0)
- , _just_sought (false)
+ , _log (log)
{
- setup_subtitle ();
-
/* Audio and video frame PTS values may not start with 0. We want
to fiddle them so that:
Then we remove big initial gaps in PTS and we allow our
insertion of black frames to work.
- We will do:
- audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
- video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
+ We will do pts_to_use = pts_from_ffmpeg + pts_offset;
*/
- bool const have_video = video && c->first_video();
- bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+ bool const have_video = c->first_video();
+ bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
/* First, make one of them start at 0 */
/* Now adjust both so that the video pts starts on a frame */
if (have_video && have_audio) {
- double first_video = c->first_video().get() + _pts_offset;
- double const old_first_video = first_video;
-
- /* Round the first video up to a frame boundary */
- if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
- first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
- }
-
- _pts_offset += first_video - old_first_video;
- }
-}
-
-FFmpegDecoder::~FFmpegDecoder ()
-{
- boost::mutex::scoped_lock lm (_mutex);
-
- if (_subtitle_codec_context) {
- avcodec_close (_subtitle_codec_context);
+ ContentTime first_video = c->first_video().get() + _pts_offset;
+ ContentTime const old_first_video = first_video;
+ _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
}
}
/* XXX: should we reset _packet.data and size after each *_decode_* call? */
- if (_decode_video) {
- while (decode_video_packet ()) {}
- }
+ while (decode_video_packet ()) {}
- if (_ffmpeg_content->audio_stream() && _decode_audio) {
+ if (_ffmpeg_content->audio_stream()) {
decode_audio_packet ();
+ AudioDecoder::flush ();
}
-
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length_after_3d_combine ();
- _audio_position = _ffmpeg_content->audio_length ();
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
/* Maybe we should fail here, but for now we'll just finish off instead */
char buf[256];
av_strerror (r, buf, sizeof(buf));
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
LOG_ERROR (N_("error on av_read_frame (%1) (%2)"), buf, r);
}
flush ();
- return;
+ return true;
}
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
int const si = _packet.stream_index;
-
- if (si == _video_stream && _decode_video) {
+
+ if (si == _video_stream) {
decode_video_packet ();
- } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
decode_audio_packet ();
- } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
+ } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
decode_subtitle_packet ();
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
}
void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+FFmpegDecoder::seek (ContentTime time, bool accurate)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
-
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
+ VideoDecoder::seek (time, accurate);
+ AudioDecoder::seek (time, accurate);
+
+ /* If we are doing an `accurate' seek, we need to use pre-roll, as
+ we don't really know what the seek will give us.
*/
- int initial = frame;
- if (accurate) {
- initial -= 5;
- }
+ ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
+ time -= pre_roll;
- if (initial < 0) {
- initial = 0;
- }
-
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->original_video_frame_rate()) - _pts_offset) / time_base;
-
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
-
- avcodec_flush_buffers (video_codec_context());
- if (_subtitle_codec_context) {
- avcodec_flush_buffers (_subtitle_codec_context);
- }
-
- /* This !accurate is piling hack upon hack; setting _just_sought to true
- even with accurate == true defeats our attempt to align the start
- of the video and audio. Here we disable that defeat when accurate == true
- i.e. when we are making a DCP rather than just previewing one.
- Ewww. This should be gone in 2.0.
+ /* XXX: it seems debatable whether PTS should be used here...
+ http://www.mjbshaw.com/2012/04/seeking-in-ffmpeg-know-your-timestamp.html
*/
- if (!accurate) {
- _just_sought = true;
- }
-
- _video_position = frame;
- if (frame == 0 || !accurate) {
- /* We're already there, or we're as close as we need to be */
- return;
- }
+ ContentTime const u = time - _pts_offset;
+ int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
- while (true) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
+ if (_ffmpeg_content->audio_stream ()) {
+ s = min (
+ s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+ );
+ }
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
- _video_position = rint (
- (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->original_video_frame_rate()
- );
+ av_seek_frame (_format_context, _video_stream, s, 0);
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
+ if (subtitle_codec_context ()) {
+ avcodec_flush_buffers (subtitle_codec_context ());
}
-
- /* _video_position should be the next thing to be emitted, which will the one after the thing
- we just saw.
- */
- _video_position++;
}
void
int frame_finished;
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet);
+
if (decode_result < 0) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
LOG_ERROR ("avcodec_decode_audio4 failed (%1)", decode_result);
return;
}
if (frame_finished) {
-
- if (_audio_position == 0) {
- /* Where we are in the source, in seconds */
- double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
- if (pts > 0) {
- /* Emit some silence */
- int64_t frames = pts * _ffmpeg_content->content_audio_frame_rate ();
- while (frames > 0) {
- int64_t const this_time = min (frames, (int64_t) _ffmpeg_content->content_audio_frame_rate() / 2);
-
- shared_ptr<AudioBuffers> silence (
- new AudioBuffers (_ffmpeg_content->audio_channels(), this_time)
- );
-
- silence->make_silent ();
- audio (silence, _audio_position);
- frames -= this_time;
- }
- }
- }
+ ContentTime const ct = ContentTime::from_seconds (
+ av_frame_get_best_effort_timestamp (_frame) *
+ av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+ + _pts_offset;
int const data_size = av_samples_get_buffer_size (
0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
-
- audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+
+ audio (deinterleave_audio (_frame->data, data_size), ct);
}
copy_packet.data += decode_result;
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+ while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
++i;
}
if (i == _filter_graphs.end ()) {
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+ graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
_filter_graphs.push_back (graph);
-
LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format);
} else {
graph = *i;
list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame);
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) {
shared_ptr<Image> image = i->first;
if (i->second != AV_NOPTS_VALUE) {
-
- double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
- if (_just_sought) {
- /* We just did a seek, so disable any attempts to correct for where we
- are / should be.
- */
- _video_position = rint (pts * _ffmpeg_content->original_video_frame_rate ());
- _just_sought = false;
- }
-
- double const next = _video_position / _ffmpeg_content->original_video_frame_rate();
- double const one_frame = 1 / _ffmpeg_content->original_video_frame_rate ();
- double delta = pts - next;
-
- while (delta > one_frame) {
- /* This PTS is more than one frame forward in time of where we think we should be; emit
- a black frame.
- */
-
- /* XXX: I think this should be a copy of the last frame... */
- boost::shared_ptr<Image> black (
- new Image (
- static_cast<AVPixelFormat> (_frame->format),
- libdcp::Size (video_codec_context()->width, video_codec_context()->height),
- true
- )
- );
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- black->make_black ();
- video (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), false, _video_position);
- delta -= one_frame;
- }
-
- if (delta > -one_frame) {
- /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (shared_ptr<ImageProxy> (new RawImageProxy (image, film->log())), false, _video_position);
- }
-
+ double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
+ video (
+ shared_ptr<ImageProxy> (new RawImageProxy (image, _video_content->film()->log())),
+ rint (pts * _ffmpeg_content->video_frame_rate ())
+ );
} else {
LOG_WARNING ("Dropping frame without PTS");
}
return true;
}
-
-
-void
-FFmpegDecoder::setup_subtitle ()
-{
- boost::mutex::scoped_lock lm (_mutex);
-
- if (!_ffmpeg_content->subtitle_stream()) {
- return;
- }
-
- _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
- if (_subtitle_codec_context == 0) {
- throw DecodeError (N_("could not find subtitle stream"));
- }
-
- _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
-
- if (_subtitle_codec == 0) {
- throw DecodeError (N_("could not find subtitle decoder"));
- }
-
- if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
- throw DecodeError (N_("could not open subtitle decoder"));
- }
-}
-
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
void
FFmpegDecoder::decode_subtitle_packet ()
{
int got_subtitle;
AVSubtitle sub;
- if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
+ if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
return;
}
indicate that the previous subtitle should stop.
*/
if (sub.num_rects <= 0) {
- subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+ image_subtitle (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
return;
} else if (sub.num_rects > 1) {
throw DecodeError (_("multi-part subtitles not yet supported"));
}
- /* Subtitle PTS in seconds (within the source, not taking into account any of the
+ /* Subtitle PTS (within the source, not taking into account any of the
source that we may have chopped off for the DCP)
*/
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _pts_offset;
-
- /* hence start time for this sub */
- Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
- Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+ ContentTimePeriod period = subtitle_period (sub) + _pts_offset;
AVSubtitleRect const * rect = sub.rects[0];
if (rect->type != SUBTITLE_BITMAP) {
- throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ /* XXX */
+ // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+ return;
}
/* Note RGBA is expressed little-endian, so the first byte in the word is R, second
G, third B, fourth A.
*/
- shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+ shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
/* Start of the first line in the subtitle */
uint8_t* sub_p = rect->pict.data[0];
out_p += image->stride()[0] / sizeof (uint32_t);
}
- libdcp::Size const vs = _ffmpeg_content->video_size ();
+ dcp::Size const vs = _ffmpeg_content->video_size ();
- subtitle (
+ image_subtitle (
+ period,
image,
dcpomatic::Rect<double> (
static_cast<double> (rect->x) / vs.width,
static_cast<double> (rect->y) / vs.height,
static_cast<double> (rect->w) / vs.width,
static_cast<double> (rect->h) / vs.height
- ),
- from,
- to
+ )
);
-
avsubtitle_free (&sub);
}
+
+list<ContentTimePeriod>
+FFmpegDecoder::subtitles_during (ContentTimePeriod p, bool starting) const
+{
+ return _ffmpeg_content->subtitles_during (p, starting);
+}
*/
-#include <Magick++.h>
-#include <libdcp/util.h>
-#include <libdcp/raw_convert.h>
+#include <dcp/util.h>
+#include <dcp/raw_convert.h>
#include "image_proxy.h"
+#include "raw_image_proxy.h"
+#include "magick_image_proxy.h"
+#include "j2k_image_proxy.h"
#include "image.h"
#include "exceptions.h"
#include "cross.h"
using std::cout;
using std::string;
- using std::stringstream;
using boost::shared_ptr;
ImageProxy::ImageProxy (shared_ptr<Log> log)
}
-RawImageProxy::RawImageProxy (shared_ptr<Image> image, shared_ptr<Log> log)
- : ImageProxy (log)
- , _image (image)
-{
-
-}
-
-RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log)
- : ImageProxy (log)
-{
- libdcp::Size size (
- xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
- );
-
- _image.reset (new Image (static_cast<AVPixelFormat> (xml->number_child<int> ("PixelFormat")), size, true));
- _image->read_from_socket (socket);
-}
-
-shared_ptr<Image>
-RawImageProxy::image () const
-{
- return _image;
-}
-
-void
-RawImageProxy::add_metadata (xmlpp::Node* node) const
-{
- node->add_child("Type")->add_child_text (N_("Raw"));
- node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width));
- node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height));
- node->add_child("PixelFormat")->add_child_text (libdcp::raw_convert<string> (_image->pixel_format ()));
-}
-
-void
-RawImageProxy::send_binary (shared_ptr<Socket> socket) const
-{
- _image->write_to_socket (socket);
-}
-
-MagickImageProxy::MagickImageProxy (boost::filesystem::path path, shared_ptr<Log> log)
- : ImageProxy (log)
-{
- /* Read the file into a Blob */
-
- boost::uintmax_t const size = boost::filesystem::file_size (path);
- FILE* f = fopen_boost (path, "rb");
- if (!f) {
- throw OpenFileError (path);
- }
-
- uint8_t* data = new uint8_t[size];
- if (fread (data, 1, size, f) != size) {
- delete[] data;
- throw ReadFileError (path);
- }
-
- fclose (f);
- _blob.update (data, size);
- delete[] data;
-}
-
-MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket, shared_ptr<Log> log)
- : ImageProxy (log)
-{
- uint32_t const size = socket->read_uint32 ();
- uint8_t* data = new uint8_t[size];
- socket->read (data, size);
- _blob.update (data, size);
- delete[] data;
-}
-
-shared_ptr<Image>
-MagickImageProxy::image () const
-{
- if (_image) {
- return _image;
- }
-
- LOG_TIMING ("[%1] MagickImageProxy begins decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length());
-
- Magick::Image* magick_image = 0;
- try {
- magick_image = new Magick::Image (_blob);
- } catch (...) {
- throw DecodeError (_("Could not decode image file"));
- }
-
- LOG_TIMING ("[%1] MagickImageProxy decode finished", boost::this_thread::get_id ());
-
- libdcp::Size size (magick_image->columns(), magick_image->rows());
-
- _image.reset (new Image (PIX_FMT_RGB24, size, true));
-
- /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */
- uint8_t* p = _image->data()[0];
- for (int i = 0; i < size.height; ++i) {
- using namespace MagickCore;
- magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p);
- p += _image->stride()[0];
- }
-
- delete magick_image;
-
- LOG_TIMING ("[%1] MagickImageProxy completes decode and convert of %2 bytes", boost::this_thread::get_id(), _blob.length());
-
- return _image;
-}
-
-void
-MagickImageProxy::add_metadata (xmlpp::Node* node) const
-{
- node->add_child("Type")->add_child_text (N_("Magick"));
-}
-
-void
-MagickImageProxy::send_binary (shared_ptr<Socket> socket) const
-{
- socket->write (_blob.length ());
- socket->write ((uint8_t *) _blob.data (), _blob.length ());
-}
-
shared_ptr<ImageProxy>
image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket, shared_ptr<Log> log)
{
return shared_ptr<ImageProxy> (new RawImageProxy (xml, socket, log));
} else if (xml->string_child("Type") == N_("Magick")) {
return shared_ptr<MagickImageProxy> (new MagickImageProxy (xml, socket, log));
+ } else if (xml->string_child("Type") == N_("J2K")) {
+ return shared_ptr<J2KImageProxy> (new J2KImageProxy (xml, socket, log));
}
throw NetworkError (_("Unexpected image type received by server"));
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
#include "job.h"
#include "util.h"
#include "cross.h"
#include "ui_signaller.h"
#include "exceptions.h"
+#include "film.h"
+#include "log.h"
#include "i18n.h"
run ();
- } catch (libdcp::FileError& e) {
-
+ } catch (dcp::FileError& e) {
+
string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf());
try {
}
}
-/** @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
{
void
Job::set_error (string s, string d)
{
+ _film->log()->log (String::compose ("Error in job: %1 (%2)", s, d), Log::TYPE_ERROR);
boost::mutex::scoped_lock lm (_state_mutex);
_error_summary = s;
_error_details = d;
return s.str ();
}
- string
- Job::json_status () const
- {
- boost::mutex::scoped_lock lm (_state_mutex);
-
- switch (_state) {
- case NEW:
- return N_("new");
- case RUNNING:
- return N_("running");
- case PAUSED:
- return N_("paused");
- case FINISHED_OK:
- return N_("finished_ok");
- case FINISHED_ERROR:
- return N_("finished_error");
- case FINISHED_CANCELLED:
- return N_("finished_cancelled");
- }
-
- return "";
- }
-
/** @return An estimate of the remaining time for this sub-job, in seconds */
int
Job::remaining_time () const
using std::min;
using std::max;
using std::string;
- using std::stringstream;
using std::pair;
using boost::optional;
using boost::shared_ptr;
_sequencing_video = true;
ContentList cl = _content;
- Time next_left = 0;
- Time next_right = 0;
+ DCPTime next_left;
+ DCPTime next_right;
for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
if (!vc) {
continue;
}
-
+
if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
vc->set_position (next_right);
- next_right = vc->end() + 1;
+ next_right = vc->end() + DCPTime::delta ();
} else {
vc->set_position (next_left);
- next_left = vc->end() + 1;
+ next_left = vc->end() + DCPTime::delta ();
}
}
/** @param node <Playlist> node */
void
-Playlist::set_from_xml (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version, list<string>& notes)
+Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
{
list<cxml::NodePtr> c = node->node_children ("Content");
for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
Changed ();
}
-bool
-Playlist::has_subtitles () const
-{
- for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
- shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
- if (fc && !fc->subtitle_streams().empty()) {
- return true;
- }
- }
-
- return false;
-}
-
class FrameRateCandidate
{
public:
return best->dcp;
}
-Time
+DCPTime
Playlist::length () const
{
- Time len = 0;
+ DCPTime len;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
- len = max (len, (*i)->end() + 1);
+ len = max (len, (*i)->end() + DCPTime::delta ());
}
return len;
}
}
-Time
+DCPTime
Playlist::video_end () const
{
- Time end = 0;
+ DCPTime end;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
if (dynamic_pointer_cast<const VideoContent> (*i)) {
end = max (end, (*i)->end ());
return end;
}
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (!vc) {
+ continue;
+ }
+
+ if (vc->position() >= t && t < vc->end()) {
+ return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+ }
+ }
+
+ return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
void
Playlist::set_sequence_video (bool s)
{
void
Playlist::repeat (ContentList c, int n)
{
- pair<Time, Time> range (TIME_MAX, 0);
+ pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
range.first = min (range.first, (*i)->position ());
range.second = max (range.second, (*i)->position ());
range.second = max (range.second, (*i)->end ());
}
- Time pos = range.second;
+ DCPTime pos = range.second;
for (int i = 0; i < n; ++i) {
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
shared_ptr<Content> copy = (*i)->clone ();
}
- Time const p = (*previous)->position ();
+ DCPTime const p = (*previous)->position ();
(*previous)->set_position (p + c->length_after_trim ());
c->set_position (p);
sort (_content.begin(), _content.end(), ContentSorter ());
c->set_position (c->position() + c->length_after_trim ());
sort (_content.begin(), _content.end(), ContentSorter ());
}
-
-FrameRateChange
-Playlist::active_frame_rate_change (Time t, int dcp_video_frame_rate) const
-{
- for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
- shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
- if (!vc) {
- continue;
- }
-
- if (vc->position() >= t && t < vc->end()) {
- return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
- }
- }
-
- return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
-}
*/
-#include <libdcp/types.h>
+#include <dcp/types.h>
#include "ratio.h"
#include "util.h"
#include "i18n.h"
using std::string;
- using std::stringstream;
using std::vector;
vector<Ratio const *> Ratio::_ratios;
boost::filesystem::path dcp,
boost::posix_time::ptime from,
boost::posix_time::ptime to,
- libdcp::KDM::Formulation formulation
+ dcp::Formulation formulation
)
: Job (f)
, _screens (screens)
return String::compose (_("Email KDMs for %1"), _film->name());
}
- string
- SendKDMEmailJob::json_name () const
- {
- return N_("send_kdm_email");
- }
-
void
SendKDMEmailJob::run ()
{
*/
#include <boost/filesystem.hpp>
-#include <libdcp/kdm.h>
+#include <dcp/types.h>
#include "job.h"
class Screen;
boost::filesystem::path,
boost::posix_time::ptime,
boost::posix_time::ptime,
- libdcp::KDM::Formulation
+ dcp::Formulation
);
std::string name () const;
- std::string json_name () const;
void run ();
private:
boost::filesystem::path _dcp;
boost::posix_time::ptime _from;
boost::posix_time::ptime _to;
- libdcp::KDM::Formulation _formulation;
+ dcp::Formulation _formulation;
};
using std::stringstream;
using std::fixed;
using std::setprecision;
+using std::cout;
using boost::shared_ptr;
/** @param s Film to use.
return String::compose (_("Transcode %1"), _film->name());
}
- string
- TranscodeJob::json_name () const
- {
- return N_("transcode");
- }
-
void
TranscodeJob::run ()
{
return s.str ();
}
+/** @return Approximate remaining time in seconds */
int
TranscodeJob::remaining_time () const
{
}
/* Compute approximate proposed length here, as it's only here that we need it */
- OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - t->video_frames_out();
- return left / fps;
+ return (_film->length().frames (_film->video_frame_rate ()) - t->video_frames_out()) / fps;
}
#include <fstream>
#include <cerrno>
-#include <libdcp/mono_picture_asset.h>
-#include <libdcp/stereo_picture_asset.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/reel.h>
-#include <libdcp/dcp.h>
-#include <libdcp/cpl.h>
+#include <dcp/mono_picture_mxf.h>
+#include <dcp/stereo_picture_mxf.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_mxf_writer.h>
+#include <dcp/reel.h>
+#include <dcp/reel_mono_picture_asset.h>
+#include <dcp/reel_stereo_picture_asset.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/reel_subtitle_asset.h>
+#include <dcp/dcp.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
#include "writer.h"
#include "compose.hpp"
#include "film.h"
#include "ratio.h"
#include "log.h"
-#include "dcp_video_frame.h"
+#include "dcp_video.h"
#include "dcp_content_type.h"
-#include "player.h"
#include "audio_mapping.h"
#include "config.h"
#include "job.h"
#include "cross.h"
+#include "audio_buffers.h"
#include "md5_digester.h"
+#include "encoded_data.h"
#include "i18n.h"
using std::string;
using std::list;
using std::cout;
- using std::stringstream;
using boost::shared_ptr;
using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
int const Writer::_maximum_frames_in_memory = Config::instance()->num_local_encoding_threads() + 4;
, _last_written_eyes (EYES_RIGHT)
, _full_written (0)
, _fake_written (0)
- , _repeat_written (0)
, _pushed_to_disk (0)
{
/* Remove any old DCP */
*/
if (_film->three_d ()) {
- _picture_asset.reset (new libdcp::StereoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::StereoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
} else {
- _picture_asset.reset (new libdcp::MonoPictureAsset (_film->internal_video_mxf_dir (), _film->internal_video_mxf_filename ()));
+ _picture_mxf.reset (new dcp::MonoPictureMXF (dcp::Fraction (_film->video_frame_rate (), 1)));
}
- _picture_asset->set_edit_rate (_film->video_frame_rate ());
- _picture_asset->set_size (_film->frame_size ());
- _picture_asset->set_interop (_film->interop ());
+ _picture_mxf->set_size (_film->frame_size ());
if (_film->encrypted ()) {
- _picture_asset->set_key (_film->key ());
+ _picture_mxf->set_key (_film->key ());
}
- _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
+ _picture_mxf_writer = _picture_mxf->start_write (
+ _film->internal_video_mxf_dir() / _film->internal_video_mxf_filename(),
+ _film->interop() ? dcp::INTEROP : dcp::SMPTE,
+ _first_nonexistant_frame > 0
+ );
if (_film->audio_channels ()) {
- _sound_asset.reset (new libdcp::SoundAsset (_film->directory (), _film->audio_mxf_filename ()));
- _sound_asset->set_edit_rate (_film->video_frame_rate ());
- _sound_asset->set_channels (_film->audio_channels ());
- _sound_asset->set_sampling_rate (_film->audio_frame_rate ());
- _sound_asset->set_interop (_film->interop ());
+ _sound_mxf.reset (new dcp::SoundMXF (dcp::Fraction (_film->video_frame_rate(), 1), _film->audio_frame_rate (), _film->audio_channels ()));
if (_film->encrypted ()) {
- _sound_asset->set_key (_film->key ());
+ _sound_mxf->set_key (_film->key ());
}
-
- /* Write the sound asset into the film directory so that we leave the creation
+
+ /* Write the sound MXF into the film directory so that we leave the creation
of the DCP directory until the last minute.
*/
- _sound_asset_writer = _sound_asset->start_write ();
+ _sound_mxf_writer = _sound_mxf->start_write (_film->directory() / _film->audio_mxf_filename(), _film->interop() ? dcp::INTEROP : dcp::SMPTE);
+ }
+
+ /* Check that the signer is OK if we need one */
+ if (_film->is_signed() && !Config::instance()->signer()->valid ()) {
+ throw InvalidSignerError ();
}
_thread = new boost::thread (boost::bind (&Writer::thread, this));
}
FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r");
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
QueueItem qi;
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- if (_sound_asset) {
- _sound_asset_writer->write (audio->data(), audio->frames());
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->write (audio->data(), audio->frames());
}
}
qi.encoded.reset (new EncodedData (_film->j2c_path (qi.frame, qi.eyes, false)));
}
- libdcp::FrameInfo fin = _picture_asset_writer->write (qi.encoded->data(), qi.encoded->size());
+ dcp::FrameInfo fin = _picture_mxf_writer->write (qi.encoded->data(), qi.encoded->size());
qi.encoded->write_info (_film, qi.frame, qi.eyes, fin);
_last_written[qi.eyes] = qi.encoded;
++_full_written;
}
case QueueItem::FAKE:
LOG_GENERAL (N_("Writer FAKE-writes %1 to MXF"), qi.frame);
- _picture_asset_writer->fake_write (qi.size);
+ _picture_mxf_writer->fake_write (qi.size);
_last_written[qi.eyes].reset ();
++_fake_written;
break;
- case QueueItem::REPEAT:
- {
- LOG_GENERAL (N_("Writer REPEAT-writes %1 to MXF"), qi.frame);
- libdcp::FrameInfo fin = _picture_asset_writer->write (
- _last_written[qi.eyes]->data(),
- _last_written[qi.eyes]->size()
- );
-
- _last_written[qi.eyes]->write_info (_film, qi.frame, qi.eyes, fin);
- ++_repeat_written;
- break;
- }
}
lock.lock ();
_last_written_frame = qi.frame;
_last_written_eyes = qi.eyes;
- if (_film->length()) {
- shared_ptr<Job> job = _job.lock ();
- assert (job);
- int total = _film->time_to_video_frames (_film->length ());
- if (_film->three_d ()) {
- /* _full_written and so on are incremented for each eye, so we need to double the total
- frames to get the correct progress.
- */
- total *= 2;
- }
- job->set_progress (float (_full_written + _fake_written + _repeat_written) / total);
+ shared_ptr<Job> job = _job.lock ();
+ assert (job);
+ int64_t total = _film->length().frames (_film->video_frame_rate ());
+ if (_film->three_d ()) {
+ /* _full_written and so on are incremented for each eye, so we need to double the total
+ frames to get the correct progress.
+ */
+ total *= 2;
+ }
+ if (total) {
+ job->set_progress (float (_full_written + _fake_written) / total);
}
}
terminate_thread (true);
- _picture_asset_writer->finalize ();
- if (_sound_asset_writer) {
- _sound_asset_writer->finalize ();
+ _picture_mxf_writer->finalize ();
+ if (_sound_mxf_writer) {
+ _sound_mxf_writer->finalize ();
}
- int const frames = _last_written_frame + 1;
-
- _picture_asset->set_duration (frames);
-
/* Hard-link the video MXF into the DCP */
boost::filesystem::path video_from;
video_from /= _film->internal_video_mxf_dir();
LOG_WARNING_NC ("Hard-link failed; fell back to copying");
}
- /* And update the asset */
-
- _picture_asset->set_directory (_film->dir (_film->dcp_name ()));
- _picture_asset->set_file_name (_film->video_mxf_filename ());
+ _picture_mxf->set_file (video_to);
/* Move the audio MXF into the DCP */
- if (_sound_asset) {
+ if (_sound_mxf) {
boost::filesystem::path audio_to;
audio_to /= _film->dir (_film->dcp_name ());
audio_to /= _film->audio_mxf_filename ();
String::compose (_("could not move audio MXF into the DCP (%1)"), ec.value ()), _film->file (_film->audio_mxf_filename ())
);
}
-
- _sound_asset->set_directory (_film->dir (_film->dcp_name ()));
- _sound_asset->set_duration (frames);
+
+ _sound_mxf->set_file (audio_to);
}
-
- libdcp::DCP dcp (_film->dir (_film->dcp_name()));
- shared_ptr<libdcp::CPL> cpl (
- new libdcp::CPL (
- _film->dir (_film->dcp_name()),
+ dcp::DCP dcp (_film->dir (_film->dcp_name()));
+
+ shared_ptr<dcp::CPL> cpl (
+ new dcp::CPL (
_film->dcp_name(),
- _film->dcp_content_type()->libdcp_kind (),
- frames,
- _film->video_frame_rate ()
+ _film->dcp_content_type()->libdcp_kind ()
)
);
- dcp.add_cpl (cpl);
+ dcp.add (cpl);
+
+ shared_ptr<dcp::Reel> reel (new dcp::Reel ());
+
+ shared_ptr<dcp::MonoPictureMXF> mono = dynamic_pointer_cast<dcp::MonoPictureMXF> (_picture_mxf);
+ if (mono) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelMonoPictureAsset (mono, 0)));
+ dcp.add (mono);
+ }
+
+ shared_ptr<dcp::StereoPictureMXF> stereo = dynamic_pointer_cast<dcp::StereoPictureMXF> (_picture_mxf);
+ if (stereo) {
+ reel->add (shared_ptr<dcp::ReelPictureAsset> (new dcp::ReelStereoPictureAsset (stereo, 0)));
+ dcp.add (stereo);
+ }
+
+ if (_sound_mxf) {
+ reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+ dcp.add (_sound_mxf);
+ }
- cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
- _picture_asset,
- _sound_asset,
- shared_ptr<libdcp::SubtitleAsset> ()
- )
- ));
+ if (_subtitle_content) {
+ _subtitle_content->write_xml (_film->dir (_film->dcp_name ()) / _film->subtitle_xml_filename ());
+ reel->add (shared_ptr<dcp::ReelSubtitleAsset> (
+ new dcp::ReelSubtitleAsset (
+ _subtitle_content,
+ dcp::Fraction (_film->video_frame_rate(), 1),
+ _picture_mxf->intrinsic_duration (),
+ 0
+ )
+ ));
+
+ dcp.add (_subtitle_content);
+ }
+
+ cpl->add (reel);
shared_ptr<Job> job = _job.lock ();
assert (job);
job->sub (_("Computing image digest"));
- _picture_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _picture_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
- if (_sound_asset) {
+ if (_sound_mxf) {
job->sub (_("Computing audio digest"));
- _sound_asset->compute_digest (boost::bind (&Job::set_progress, job.get(), _1, false));
+ _sound_mxf->hash (boost::bind (&Job::set_progress, job.get(), _1, false));
}
- libdcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
+ dcp::XMLMetadata meta = Config::instance()->dcp_metadata ();
meta.set_issue_date_now ();
- dcp.write_xml (_film->interop (), meta, _film->is_signed() ? make_signer () : shared_ptr<const libdcp::Signer> ());
-
- LOG_GENERAL (
- N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk
- );
-}
-/** Tell the writer that frame `f' should be a repeat of the frame before it */
-void
-Writer::repeat (int f, Eyes e)
-{
- boost::mutex::scoped_lock lock (_mutex);
-
- while (_queued_full_in_memory > _maximum_frames_in_memory) {
- /* The queue is too big; wait until that is sorted out */
- _full_condition.wait (lock);
- }
-
- QueueItem qi;
- qi.type = QueueItem::REPEAT;
- qi.frame = f;
- if (_film->three_d() && e == EYES_BOTH) {
- qi.eyes = EYES_LEFT;
- _queue.push_back (qi);
- qi.eyes = EYES_RIGHT;
- _queue.push_back (qi);
- } else {
- qi.eyes = e;
- _queue.push_back (qi);
+ shared_ptr<const dcp::Signer> signer;
+ if (_film->is_signed ()) {
+ signer = Config::instance()->signer ();
+ /* We did check earlier, but check again here to be on the safe side */
+ if (!signer->valid ()) {
+ throw InvalidSignerError ();
+ }
}
- /* Now there's something to do: wake anything wait()ing on _empty_condition */
- _empty_condition.notify_all ();
+ dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, signer);
+
+ LOG_GENERAL (
+ N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_written, _pushed_to_disk
+ );
}
bool
return false;
}
- libdcp::FrameInfo info (ifi);
+ dcp::FrameInfo info (ifi);
fclose (ifi);
if (info.size == 0) {
LOG_GENERAL ("Existing frame %1 has no info file", f);
return (frame != 0 && frame < _first_nonexistant_frame);
}
+void
+Writer::write (PlayerSubtitles subs)
+{
+ if (subs.text.empty ()) {
+ return;
+ }
+
+ if (!_subtitle_content) {
+ _subtitle_content.reset (
+ new dcp::SubtitleContent (_film->name(), _film->isdcf_metadata().subtitle_language)
+ );
+ }
+
+ for (list<dcp::SubtitleString>::const_iterator i = subs.text.begin(); i != subs.text.end(); ++i) {
+ _subtitle_content->add (*i);
+ }
+}
+
bool
operator< (QueueItem const & a, QueueItem const & b)
{
audio_buffers.cc
audio_content.cc
audio_decoder.cc
+ audio_filter.cc
audio_mapping.cc
+ audio_processor.cc
cinema.cc
+ cinema_sound_processor.cc
colour_conversion.cc
config.cc
content.cc
content_factory.cc
+ content_subtitle.cc
cross.cc
+ dcp_content.cc
dcp_content_type.cc
- dcp_video_frame.cc
- decoder.cc
+ dcp_decoder.cc
+ dcp_examiner.cc
+ dcp_subtitle_content.cc
+ dcp_subtitle_decoder.cc
+ dcp_video.cc
+ dcpomatic_time.cc
dolby_cp750.cc
encoder.cc
+ encoded_data.cc
examine_content_job.cc
exceptions.cc
file_group.cc
filter_graph.cc
ffmpeg.cc
+ ffmpeg_audio_stream.cc
ffmpeg_content.cc
ffmpeg_decoder.cc
ffmpeg_examiner.cc
+ ffmpeg_stream.cc
+ ffmpeg_subtitle_stream.cc
film.cc
filter.cc
frame_rate_change.cc
image_examiner.cc
image_proxy.cc
isdcf_metadata.cc
+ j2k_image_proxy.cc
job.cc
job_manager.cc
kdm.cc
- json_server.cc
log.cc
+ magick_image_proxy.cc
md5_digester.cc
- piece.cc
+ mid_side_decoder.cc
player.cc
- player_video_frame.cc
+ player_video.cc
playlist.cc
ratio.cc
+ raw_image_proxy.cc
+ render_subtitles.cc
resampler.cc
scp_dcp_job.cc
scaler.cc
send_kdm_email_job.cc
server.cc
server_finder.cc
+ single_stream_audio_content.cc
sndfile_content.cc
sndfile_decoder.cc
- sound_processor.cc
- subtitle.cc
+ subrip.cc
+ subrip_content.cc
+ subrip_decoder.cc
subtitle_content.cc
subtitle_decoder.cc
timer.cc
types.cc
ui_signaller.cc
update.cc
+ upmixer_a.cc
util.cc
video_content.cc
video_decoder.cc
else:
obj = bld(features = 'cxx cxxshlib')
- obj.name = 'libdcpomatic'
+ obj.name = 'libdcpomatic2'
obj.export_includes = ['..']
obj.uselib = """
AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
- SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XMLPP
- CURL ZIP QUICKMAIL XMLSEC
+ SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
+ CURL ZIP QUICKMAIL PANGOMM CAIROMM XMLSEC
"""
if bld.env.TARGET_OSX:
if bld.env.BUILD_STATIC:
obj.uselib += ' XMLPP'
- obj.target = 'dcpomatic'
+ obj.target = 'dcpomatic2'
- i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic', bld)
+ i18n.po_to_mo(os.path.join('src', 'lib'), 'libdcpomatic2', bld)
def pot(bld):
i18n.pot(os.path.join('src', 'lib'), sources, 'libdcpomatic')
#include <wx/stdpaths.h>
#include <wx/cmdline.h>
#include <wx/preferences.h>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
#include "wx/film_viewer.h"
#include "wx/film_editor.h"
#include "wx/job_manager_view.h"
#include "wx/servers_list_dialog.h"
#include "wx/hints_dialog.h"
#include "wx/update_dialog.h"
+#include "wx/content_panel.h"
#include "lib/film.h"
#include "lib/config.h"
#include "lib/util.h"
void file_changed (boost::filesystem::path f)
{
- stringstream s;
- s << wx_to_std (_("DCP-o-matic"));
+ string s = wx_to_std (_("DCP-o-matic"));
if (!f.empty ()) {
- s << " - " << f.string ();
+ s += " - " + f.string ();
}
- SetTitle (std_to_wx (s.str()));
+ SetTitle (std_to_wx (s));
}
void file_new ()
shared_ptr<Job> (new SendKDMEmailJob (film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ()))
);
}
- } catch (libdcp::NotEncryptedError& e) {
+ } catch (dcp::NotEncryptedError& e) {
error_dialog (this, _("CPL's content is not encrypted."));
} catch (exception& e) {
error_dialog (this, e.what ());
void content_scale_to_fit_width ()
{
- VideoContentList vc = _film_editor->selected_video_content ();
+ VideoContentList vc = _film_editor->content_panel()->selected_video ();
for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
(*i)->scale_and_crop_to_fit_width ();
}
void content_scale_to_fit_height ()
{
- VideoContentList vc = _film_editor->selected_video_content ();
+ VideoContentList vc = _film_editor->content_panel()->selected_video ();
for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
(*i)->scale_and_crop_to_fit_height ();
}
}
bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
bool const have_cpl = film && !film->cpls().empty ();
- bool const have_selected_video_content = !_film_editor->selected_video_content().empty();
+ bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty();
for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
};
+/** @class App
+ * @brief The magic App class for wxWidgets.
+ */
class App : public wxApp
{
bool OnInit ()
#include <iostream>
#include <iomanip>
#include <getopt.h>
-#include <libdcp/version.h>
+#include <dcp/version.h>
#include "lib/film.h"
#include "lib/filter.h"
#include "lib/transcode_job.h"
#include "lib/log.h"
#include "lib/ui_signaller.h"
#include "lib/server_finder.h"
- #include "lib/json_server.h"
using std::string;
using std::cerr;
<< " -f, --flags show flags passed to C++ compiler on build\n"
<< " -n, --no-progress do not print progress to stdout\n"
<< " -r, --no-remote do not use any remote servers\n"
- << " -j, --json <port> run a JSON server on the specified port\n"
<< " -k, --keep-going keep running even when the job is complete\n"
<< "\n"
<< "<FILM> is the film directory.\n";
string film_dir;
bool progress = true;
bool no_remote = false;
- int json_port = 0;
bool keep_going = false;
int option_index = 0;
{ "flags", no_argument, 0, 'f'},
{ "no-progress", no_argument, 0, 'n'},
{ "no-remote", no_argument, 0, 'r'},
- { "json", required_argument, 0, 'j' },
{ "keep-going", no_argument, 0, 'k' },
{ 0, 0, 0, 0 }
};
- int c = getopt_long (argc, argv, "vhdfnrj:k", long_options, &option_index);
+ int c = getopt_long (argc, argv, "vhdfnrk", long_options, &option_index);
if (c == -1) {
break;
case 'r':
no_remote = true;
break;
- case 'j':
- json_port = atoi (optarg);
- break;
case 'k':
keep_going = true;
break;
ServerFinder::instance()->disable ();
}
- if (json_port) {
- new JSONServer (json_port);
- }
-
cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
char buf[256];
if (gethostname (buf, 256) == 0) {
}
cout << "\nMaking DCP for " << film->name() << "\n";
- // cout << "Content: " << film->content() << "\n";
- // pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
- // cout << "Filters: " << f.first << " " << f.second << "\n";
film->make_dcp ();
}
void
-Timecode::set (Time t, int fps)
+Timecode::set (DCPTime t, int fps)
{
- /* Do this calculation with frames so that we can round
- to a frame boundary at the start rather than the end.
- */
- int64_t f = divide_with_round (t * fps, TIME_HZ);
-
- int const h = f / (3600 * fps);
- f -= h * 3600 * fps;
- int const m = f / (60 * fps);
- f -= m * 60 * fps;
- int const s = f / fps;
- f -= s * fps;
+ int h;
+ int m;
+ int s;
+ int f;
+ t.split (fps, h, m, s, f);
checked_set (_hours, lexical_cast<string> (h));
checked_set (_minutes, lexical_cast<string> (m));
checked_set (_seconds, lexical_cast<string> (s));
checked_set (_frames, lexical_cast<string> (f));
- _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02" wxLongLongFmtSpec "d", h, m, s, f));
+ _fixed->SetLabel (std_to_wx (t.timecode (fps)));
}
-Time
+DCPTime
Timecode::get (int fps) const
{
- Time t = 0;
+ DCPTime t;
string const h = wx_to_std (_hours->GetValue ());
- t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
+ t += DCPTime::from_seconds (lexical_cast<int> (h.empty() ? "0" : h) * 3600);
string const m = wx_to_std (_minutes->GetValue());
- t += lexical_cast<int> (m.empty() ? "0" : m) * 60 * TIME_HZ;
+ t += DCPTime::from_seconds (lexical_cast<int> (m.empty() ? "0" : m) * 60);
string const s = wx_to_std (_seconds->GetValue());
- t += lexical_cast<int> (s.empty() ? "0" : s) * TIME_HZ;
+ t += DCPTime::from_seconds (lexical_cast<int> (s.empty() ? "0" : s));
string const f = wx_to_std (_frames->GetValue());
- t += lexical_cast<int> (f.empty() ? "0" : f) * TIME_HZ / fps;
+ t += DCPTime::from_seconds (lexical_cast<double> (f.empty() ? "0" : f) / fps);
return t;
}
+ void
+ Timecode::clear ()
+ {
+ checked_set (_hours, "");
+ checked_set (_minutes, "");
+ checked_set (_seconds, "");
+ checked_set (_frames, "");
+ _fixed->SetLabel ("");
+ }
+
void
Timecode::changed ()
{
public:
Timecode (wxWindow *);
- void set (Time, int);
- Time get (int) const;
+ void set (DCPTime, int);
+ DCPTime get (int) const;
+ void clear ();
void set_editable (bool);
*/
-#include <libdcp/raw_convert.h>
+#include <dcp/raw_convert.h>
#include "lib/content.h"
#include "lib/image_content.h"
#include "timing_panel.h"
#include "wx_util.h"
#include "timecode.h"
-#include "film_editor.h"
+#include "content_panel.h"
using std::cout;
using std::string;
+ using std::set;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
-using libdcp::raw_convert;
+using dcp::raw_convert;
-TimingPanel::TimingPanel (FilmEditor* e)
+TimingPanel::TimingPanel (ContentPanel* p)
/* horrid hack for apparent lack of context support with wxWidgets i18n code */
- : FilmEditorPanel (e, S_("Timing|Timing"))
+ : ContentSubPanel (p, S_("Timing|Timing"))
{
wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
_sizer->Add (grid, 0, wxALL, 8);
void
TimingPanel::film_content_changed (int property)
{
- ContentList cl = _editor->selected_content ();
- int const film_video_frame_rate = _editor->film()->video_frame_rate ();
+ ContentList cl = _parent->selected ();
- shared_ptr<Content> content;
- if (cl.size() == 1) {
- content = cl.front ();
- }
-
+ int const film_video_frame_rate = _parent->film()->video_frame_rate ();
+
+ /* Here we check to see if we have exactly one different value of various
+ properties, and fill the controls with that value if so.
+ */
if (property == ContentProperty::POSITION) {
- if (content) {
- _position->set (content->position (), film_video_frame_rate);
+
- set<Time> check;
++ set<DCPTime> check;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ check.insert ((*i)->position ());
+ }
+
+ if (check.size() == 1) {
+ _position->set (cl.front()->position(), film_video_frame_rate);
} else {
- _position->set (DCPTime () , 24);
+ _position->clear ();
}
+
} else if (
property == ContentProperty::LENGTH ||
property == VideoContentProperty::VIDEO_FRAME_RATE ||
property == VideoContentProperty::VIDEO_FRAME_TYPE
) {
- if (content) {
- _full_length->set (content->full_length (), film_video_frame_rate);
- _play_length->set (content->length_after_trim (), film_video_frame_rate);
+
- set<Time> check;
++ set<DCPTime> check;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ check.insert ((*i)->full_length ());
+ }
+
+ if (check.size() == 1) {
+ _full_length->set (cl.front()->full_length (), film_video_frame_rate);
} else {
- _full_length->set (DCPTime (), 24);
- _play_length->set (DCPTime (), 24);
+ _full_length->clear ();
}
+
} else if (property == ContentProperty::TRIM_START) {
- if (content) {
- _trim_start->set (content->trim_start (), film_video_frame_rate);
- _play_length->set (content->length_after_trim (), film_video_frame_rate);
+
- set<Time> check;
++ set<DCPTime> check;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ check.insert ((*i)->trim_start ());
+ }
+
+ if (check.size() == 1) {
+ _trim_start->set (cl.front()->trim_start (), film_video_frame_rate);
} else {
- _trim_start->set (DCPTime (), 24);
- _play_length->set (DCPTime (), 24);
+ _trim_start->clear ();
}
+
} else if (property == ContentProperty::TRIM_END) {
- if (content) {
- _trim_end->set (content->trim_end (), film_video_frame_rate);
- _play_length->set (content->length_after_trim (), film_video_frame_rate);
+
- set<Time> check;
++ set<DCPTime> check;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ check.insert ((*i)->trim_end ());
+ }
+
+ if (check.size() == 1) {
+ _trim_end->set (cl.front()->trim_end (), film_video_frame_rate);
} else {
- _trim_end->set (DCPTime (), 24);
- _play_length->set (DCPTime (), 24);
- _trim_end->set (0, 24);
++ _trim_end->clear ();
+ }
+ }
+
+ if (
+ property == ContentProperty::LENGTH ||
+ property == ContentProperty::TRIM_START ||
+ property == ContentProperty::TRIM_END ||
+ property == VideoContentProperty::VIDEO_FRAME_RATE ||
+ property == VideoContentProperty::VIDEO_FRAME_TYPE
+ ) {
+
- set<Time> check;
++ set<DCPTime> check;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ check.insert ((*i)->length_after_trim ());
+ }
+
+ if (check.size() == 1) {
+ _play_length->set (cl.front()->length_after_trim (), film_video_frame_rate);
+ } else {
+ _play_length->clear ();
}
}
if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
- if (content) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (content);
+ set<float> check;
+ shared_ptr<VideoContent> vc;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ vc = dynamic_pointer_cast<VideoContent> (*i);
if (vc) {
- _video_frame_rate->SetValue (std_to_wx (raw_convert<string> (vc->video_frame_rate (), 5)));
- } else {
- _video_frame_rate->SetValue ("24");
+ check.insert (vc->video_frame_rate ());
}
+ }
+ if (check.size() == 1) {
+ _video_frame_rate->SetValue (std_to_wx (raw_convert<string> (vc->video_frame_rate (), 5)));
+ _video_frame_rate->Enable (true);
} else {
- _video_frame_rate->SetValue ("24");
+ _video_frame_rate->SetValue ("");
+ _video_frame_rate->Enable (false);
}
}
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (content);
- shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (content);
- _full_length->set_editable (ic && ic->still ());
- _play_length->set_editable (!ic || !ic->still ());
- _video_frame_rate->Enable (vc);
+ bool have_still = false;
+ for (ContentList::const_iterator i = cl.begin (); i != cl.end(); ++i) {
+ shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i);
+ if (ic && ic->still ()) {
+ have_still = true;
+ }
+ }
+
+ _full_length->set_editable (have_still);
+ _play_length->set_editable (!have_still);
_set_video_frame_rate->Enable (false);
}
void
TimingPanel::position_changed ()
{
- ContentList c = _editor->selected_content ();
+ ContentList c = _parent->selected ();
- if (c.size() == 1) {
- c.front()->set_position (_position->get (_parent->film()->video_frame_rate ()));
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- (*i)->set_position (_position->get (_editor->film()->video_frame_rate ()));
++ (*i)->set_position (_position->get (_parent->film()->video_frame_rate ()));
}
}
void
TimingPanel::full_length_changed ()
{
- ContentList c = _editor->selected_content ();
+ ContentList c = _parent->selected ();
- if (c.size() == 1) {
- shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+ shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i);
if (ic && ic->still ()) {
- ic->set_video_length (rint (_full_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ));
+ /* XXX: No effective FRC here... is this right? */
+ ic->set_video_length (ContentTime (_full_length->get (_parent->film()->video_frame_rate()), FrameRateChange (1, 1)));
}
}
}
void
TimingPanel::trim_start_changed ()
{
- ContentList c = _editor->selected_content ();
+ ContentList c = _parent->selected ();
- if (c.size() == 1) {
- c.front()->set_trim_start (_trim_start->get (_parent->film()->video_frame_rate ()));
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- (*i)->set_trim_start (_trim_start->get (_editor->film()->video_frame_rate ()));
++ (*i)->set_trim_start (_trim_start->get (_parent->film()->video_frame_rate ()));
}
}
void
TimingPanel::trim_end_changed ()
{
- ContentList c = _editor->selected_content ();
+ ContentList c = _parent->selected ();
- if (c.size() == 1) {
- c.front()->set_trim_end (_trim_end->get (_parent->film()->video_frame_rate ()));
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- (*i)->set_trim_end (_trim_end->get (_editor->film()->video_frame_rate ()));
++ (*i)->set_trim_end (_trim_end->get (_parent->film()->video_frame_rate ()));
}
}
void
TimingPanel::play_length_changed ()
{
- ContentList c = _editor->selected_content ();
+ ContentList c = _parent->selected ();
- if (c.size() == 1) {
- c.front()->set_trim_end (c.front()->full_length() - _play_length->get (_parent->film()->video_frame_rate()) - c.front()->trim_start());
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- (*i)->set_trim_end ((*i)->full_length() - _play_length->get (_editor->film()->video_frame_rate()) - (*i)->trim_start());
++ (*i)->set_trim_end ((*i)->full_length() - _play_length->get (_parent->film()->video_frame_rate()) - (*i)->trim_start());
}
}
void
TimingPanel::set_video_frame_rate ()
{
- ContentList c = _editor->selected_content ();
+ ContentList c = _parent->selected ();
- if (c.size() == 1) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c.front ());
+ for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
if (vc) {
vc->set_video_frame_rate (raw_convert<float> (wx_to_std (_video_frame_rate->GetValue ())));
}
void
TimingPanel::content_selection_changed ()
{
- ContentList sel = _parent->selected ();
- bool const single = sel.size() == 1;
-
- /* Things that are only allowed with single selections */
- _position->Enable (single);
- _full_length->Enable (single);
- _trim_start->Enable (single);
- _trim_end->Enable (single);
- _play_length->Enable (single);
- _video_frame_rate->Enable (single);
- bool const e = !_editor->selected_content().empty ();
++ bool const e = !_parent->selected().empty ();
+
+ _position->Enable (e);
+ _full_length->Enable (e);
+ _trim_start->Enable (e);
+ _trim_end->Enable (e);
+ _play_length->Enable (e);
+ _video_frame_rate->Enable (e);
film_content_changed (ContentProperty::POSITION);
film_content_changed (ContentProperty::LENGTH);
#include "filter_dialog.h"
#include "video_panel.h"
#include "wx_util.h"
-#include "film_editor.h"
#include "content_colour_conversion_dialog.h"
#include "content_widget.h"
+#include "content_panel.h"
using std::vector;
using std::string;
assert (false);
}
-VideoPanel::VideoPanel (FilmEditor* e)
- : FilmEditorPanel (e, _("Video"))
+VideoPanel::VideoPanel (ContentPanel* p)
+ : ContentSubPanel (p, _("Video"))
{
wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
_sizer->Add (grid, 0, wxALL, 8);
void
VideoPanel::film_content_changed (int property)
{
- VideoContentList vc = _editor->selected_video_content ();
+ VideoContentList vc = _parent->selected_video ();
shared_ptr<VideoContent> vcs;
shared_ptr<FFmpegContent> fcs;
if (!vc.empty ()) {
void
VideoPanel::edit_filters_clicked ()
{
- FFmpegContentList c = _editor->selected_ffmpeg_content ();
+ FFmpegContentList c = _parent->selected_ffmpeg ();
if (c.size() != 1) {
return;
}
void
VideoPanel::setup_description ()
{
- VideoContentList vc = _editor->selected_video_content ();
+ VideoContentList vc = _parent->selected_video ();
if (vc.empty ()) {
_description->SetLabel ("");
return;
}
Crop const crop = vcs->crop ();
- if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != libdcp::Size (0, 0)) {
- libdcp::Size cropped = vcs->video_size_after_crop ();
+ if ((crop.left || crop.right || crop.top || crop.bottom) && vcs->video_size() != dcp::Size (0, 0)) {
+ dcp::Size cropped = vcs->video_size_after_crop ();
d << wxString::Format (
_("Cropped to %dx%d (%.2f:1)\n"),
cropped.width, cropped.height,
++lines;
}
- libdcp::Size const container_size = _editor->film()->frame_size ();
- libdcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
+ dcp::Size const container_size = _parent->film()->frame_size ();
+ dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size, 1);
if (scaled != vcs->video_size_after_crop ()) {
d << wxString::Format (
d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
++lines;
- FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+ FrameRateChange frc (vcs->video_frame_rate(), _parent->film()->video_frame_rate ());
- d << std_to_wx (frc.description) << "\n";
+ d << std_to_wx (frc.description ()) << "\n";
++lines;
for (int i = lines; i < 6; ++i) {
void
VideoPanel::edit_colour_conversion_clicked ()
{
- VideoContentList vc = _editor->selected_video_content ();
+ VideoContentList vc = _parent->selected_video ();
if (vc.size() != 1) {
return;
}
void
VideoPanel::content_selection_changed ()
{
- VideoContentList sel = _editor->selected_video_content ();
+ VideoContentList sel = _parent->selected_video ();
bool const single = sel.size() == 1;
_left_crop->set_content (sel);
/*
- Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2014 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
*/
+/** @file test/film_metadata_test.cc
+ * @brief Test some basic reading/writing of film metadata.
+ */
+
#include <sstream>
#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
#include "test.h"
using std::string;
- using std::stringstream;
using std::list;
using boost::shared_ptr;
BOOST_AUTO_TEST_CASE (film_metadata_test)
{
- string const test_film = "build/test/film_metadata_test";
-
- if (boost::filesystem::exists (test_film)) {
- boost::filesystem::remove_all (test_film);
- }
+ shared_ptr<Film> f = new_test_film ("film_metadata_test");
+ boost::filesystem::path dir = test_film_dir ("film_metadata_test");
- shared_ptr<Film> f (new Film (test_film));
f->_isdcf_date = boost::gregorian::from_undelimited_string ("20130211");
BOOST_CHECK (f->container() == 0);
BOOST_CHECK (f->dcp_content_type() == 0);
list<string> ignore;
ignore.push_back ("Key");
- check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
+ check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
- shared_ptr<Film> g (new Film (test_film));
+ shared_ptr<Film> g (new Film (dir));
g->read_metadata ();
BOOST_CHECK_EQUAL (g->name(), "fred");
BOOST_CHECK_EQUAL (g->container(), Ratio::from_id ("185"));
g->write_metadata ();
- check_xml ("test/data/metadata.xml.ref", test_film + "/metadata.xml", ignore);
+ check_xml ("test/data/metadata.xml.ref", dir.string() + "/metadata.xml", ignore);
}
/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2014 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
*/
+/** @file test/job_test.cc
+ * @brief Basic tests of Job and JobManager.
+ */
+
#include <boost/test/unit_test.hpp>
#include "lib/job.h"
#include "lib/job_manager.h"
string name () const {
return "";
}
-
- string json_name () const {
- return "";
- }
};
BOOST_AUTO_TEST_CASE (job_manager_test)