#include "options.h"
#include "decoder_factory.h"
#include "decoder.h"
-#include "imagemagick_encoder.h"
#include "transcoder.h"
#include "log.h"
#include "film.h"
void
ExamineContentJob::run ()
{
- /* Decode the content to get an accurate length */
- float progress_remaining = 1;
++ descend (1);
- /* We don't want to use any existing length here, as progress
- will be messed up.
+ /* Set the film's length to either
+ a) a length judged by running through the content or
+ b) the length from a decoder's header.
*/
- _film->unset_length ();
- _film->set_crop (Crop ());
-
+ if (!_film->trust_content_header()) {
+ /* Decode the content to get an accurate length */
+
+ /* We don't want to use any existing length here, as progress
+ will be messed up.
+ */
+ _film->unset_length ();
++ _film->set_crop (Crop ());
+
- shared_ptr<Options> o (new Options ("", "", ""));
- o->out_size = Size (512, 512);
- o->apply_crop = false;
++ shared_ptr<DecodeOptions> o (new DecodeOptions);
+ o->decode_audio = false;
+
- descend (0.5);
-
- pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoders = decoder_factory (_film, o, this);
++ Decoders decoders = decoder_factory (_film, o, this);
+
+ set_progress_unknown ();
- while (!decoders.first->pass()) {
++ while (!decoders.video->pass()) {
+ /* keep going */
+ }
+
- _film->set_length (decoders.first->video_frame());
++ _film->set_length (decoders.video->video_frame());
+
+ _film->log()->log (String::compose ("Video length examined as %1 frames", _film->length().get()));
+
- ascend ();
-
- progress_remaining -= 0.5;
-
+ } else {
+
- /* Get a quick decoder to get the content's length from its header.
- It would have been nice to just use the thumbnail transcoder's decoder,
- but that's a bit fiddly, and this isn't too expensive.
- */
++ /* Get a quick decoder to get the content's length from its header */
+
- shared_ptr<Options> o (new Options ("", "", ""));
- o->out_size = Size (1024, 1024);
- pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (_film, o, 0);
- _film->set_length (d.first->length());
++ shared_ptr<DecodeOptions> o (new DecodeOptions);
++ Decoders d = decoder_factory (_film, o, 0);
++ _film->set_length (d.video->length());
- shared_ptr<DecodeOptions> o (new DecodeOptions);
- o->decode_audio = false;
-
- descend (1);
-
- Decoders decoders = decoder_factory (_film, o, this);
-
- set_progress_unknown ();
- while (!decoders.video->pass()) {
- /* keep going */
+ _film->log()->log (String::compose ("Video length obtained from header as %1 frames", _film->length().get()));
}
- _film->set_length (decoders.video->video_frame());
- /* Now make thumbnails for it */
--
- _film->log()->log (String::compose ("Video length is %1 frames", _film->length()));
- descend (progress_remaining);
-
- try {
- shared_ptr<Options> o (new Options (_film->dir ("thumbs"), ".png", ""));
- o->out_size = _film->size ();
- o->apply_crop = false;
- o->decode_audio = false;
- o->decode_video_skip = _film->length().get() / 128;
- o->decode_subtitles = true;
- shared_ptr<ImageMagickEncoder> e (new ImageMagickEncoder (_film, o));
- Transcoder w (_film, o, this, e);
- w.go ();
-
- /* Now set the film's length from the transcoder's decoder, since we
- went to all the trouble of going through the content.
- */
-
- _film->set_length (w.video_decoder()->video_frame());
-
- } catch (std::exception& e) {
-
- ascend ();
- set_progress (1);
- set_error (e.what ());
- set_state (FINISHED_ERROR);
- return;
-
- }
-
- string const tdir = _film->dir ("thumbs");
- vector<SourceFrame> thumbs;
-
- for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (tdir); i != boost::filesystem::directory_iterator(); ++i) {
-
- /* Aah, the sweet smell of progress */
-#if BOOST_FILESYSTEM_VERSION == 3
- string const l = boost::filesystem::path(*i).leaf().generic_string();
-#else
- string const l = i->leaf ();
-#endif
-
- size_t const d = l.find (".png");
- size_t const t = l.find (".tmp");
- if (d != string::npos && t == string::npos) {
- thumbs.push_back (atoi (l.substr (0, d).c_str()));
- }
- }
-
- sort (thumbs.begin(), thumbs.end());
- _film->set_thumbs (thumbs);
--
ascend ();
set_progress (1);
set_state (FINISHED_OK);
using boost::optional;
using boost::dynamic_pointer_cast;
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j)
: Decoder (f, o, j)
, VideoDecoder (f, o, j)
, AudioDecoder (f, o, j)
setup_video ();
setup_audio ();
setup_subtitle ();
+
+ _film_connection = f->Changed.connect (bind (&FFmpegDecoder::film_changed, this, _1));
}
FFmpegDecoder::~FFmpegDecoder ()
void
FFmpegDecoder::setup_general ()
{
- int r;
-
av_register_all ();
- if ((r = avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0)) != 0) {
+ if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
throw OpenFileError (_film->content_path ());
}
throw DecodeError ("could not find video decoder");
}
- /* I think this prevents problems with green hash on decodes and
- "changing frame properties on the fly is not supported by all filters"
- messages with some content. Although I'm not sure; needs checking.
- */
- AVDictionary* opts = 0;
- av_dict_set (&opts, "threads", "1", 0);
-
- if (avcodec_open2 (_video_codec_context, _video_codec, &opts) < 0) {
+ if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
throw DecodeError ("could not open video decoder");
}
}
_film->log()->log (String::compose ("Used only %1 bytes of %2 in packet", r, _packet.size));
}
- /* Where we are in the output, in seconds */
- double const out_pts_seconds = video_frame() / frames_per_second();
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
-
- _film->log()->log (
- String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds),
- Log::VERBOSE
- );
-
- if (!_first_video) {
- _first_video = source_pts_seconds;
- }
-
- /* Difference between where we are and where we should be */
- double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
- double const one_frame = 1 / frames_per_second();
-
- /* Insert frames if required to get out_pts_seconds up to pts_seconds */
- if (delta > one_frame) {
- int const extra = rint (delta / one_frame);
- for (int i = 0; i < extra; ++i) {
- repeat_last_video ();
- _film->log()->log (
- String::compose (
- "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)",
- out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
- )
- );
- }
- }
-
- if (delta > -one_frame) {
- /* Process this frame */
- filter_and_emit_video (_frame);
+ if (_opt->video_sync) {
+ out_with_sync ();
} else {
- /* Otherwise we are omitting a frame to keep things right */
- _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
+ filter_and_emit_video (_frame);
}
}
void
FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
{
+ boost::mutex::scoped_lock lm (_filter_graphs_mutex);
+
shared_ptr<FilterGraph> graph;
list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
}
if (i == _filter_graphs.end ()) {
- graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
+ graph.reset (new FilterGraph (_film, this, Size (frame->width, frame->height), (AVPixelFormat) frame->format));
_filter_graphs.push_back (graph);
_film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format));
} else {
list<shared_ptr<Image> > images = graph->process (frame);
+ SourceFrame const sf = av_q2d (_format_context->streams[_video_stream]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame) * frames_per_second();
+
for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) {
- emit_video (*i);
+ emit_video (*i, sf);
}
}
+bool
+FFmpegDecoder::seek (SourceFrame f)
+{
+ int64_t const t = static_cast<int64_t>(f) / (av_q2d (_format_context->streams[_video_stream]->time_base) * frames_per_second());
+ int const r = av_seek_frame (_format_context, _video_stream, t, 0);
+ avcodec_flush_buffers (_video_codec_context);
+ return r < 0;
+}
+
shared_ptr<FFmpegAudioStream>
FFmpegAudioStream::create (string t, optional<int> v)
{
return String::compose ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name);
}
-
+void
+FFmpegDecoder::out_with_sync ()
+{
+ /* Where we are in the output, in seconds */
+ double const out_pts_seconds = video_frame() / frames_per_second();
+
+ /* Where we are in the source, in seconds */
+ double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame);
+
+ _film->log()->log (
+ String::compose ("Source video frame ready; source at %1, output at %2", source_pts_seconds, out_pts_seconds),
+ Log::VERBOSE
+ );
+
+ if (!_first_video) {
+ _first_video = source_pts_seconds;
+ }
+
+ /* Difference between where we are and where we should be */
+ double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
+ double const one_frame = 1 / frames_per_second();
+
+ /* Insert frames if required to get out_pts_seconds up to pts_seconds */
+ if (delta > one_frame) {
+ int const extra = rint (delta / one_frame);
+ for (int i = 0; i < extra; ++i) {
+ repeat_last_video ();
+ _film->log()->log (
+ String::compose (
+ "Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)",
+ out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
+ )
+ );
+ }
+ }
+
+ if (delta > -one_frame) {
+ /* Process this frame */
+ filter_and_emit_video (_frame);
+ } else {
+ /* Otherwise we are omitting a frame to keep things right */
+ _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds));
+ }
+}
+
+void
+FFmpegDecoder::film_changed (Film::Property p)
+{
+ switch (p) {
+ case Film::CROP:
+ {
+ boost::mutex::scoped_lock lm (_filter_graphs_mutex);
+ _filter_graphs.clear ();
+ }
+ OutputChanged ();
+ break;
+
+ default:
+ break;
+ }
+}
+
+ /** @return Length (in video frames) according to our content's header */
+ SourceFrame
+ FFmpegDecoder::length () const
+ {
+ return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
+ }
++
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include <boost/optional.hpp>
+#include <boost/thread/mutex.hpp>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libpostproc/postprocess.h>
#include "decoder.h"
#include "video_decoder.h"
#include "audio_decoder.h"
+#include "film.h"
struct AVFilterGraph;
struct AVCodecContext;
, _name (n)
, _id (i)
{}
-
+
std::string to_string () const;
std::string name () const {
class FFmpegDecoder : public VideoDecoder, public AudioDecoder
{
public:
- FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
~FFmpegDecoder ();
float frames_per_second () const;
Size native_size () const;
+ SourceFrame length () const;
int time_base_numerator () const;
int time_base_denominator () const;
int sample_aspect_ratio_numerator () const;
void set_audio_stream (boost::shared_ptr<AudioStream>);
void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+ bool seek (SourceFrame);
+
private:
bool pass ();
AVSampleFormat audio_sample_format () const;
int bytes_per_audio_sample () const;
+ void out_with_sync ();
void filter_and_emit_video (AVFrame *);
void setup_general ();
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size);
+ void film_changed (Film::Property);
+ boost::signals2::scoped_connection _film_connection;
+
std::string stream_name (AVStream* s) const;
AVFormatContext* _format_context;
boost::optional<double> _first_audio;
std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
+ boost::mutex _filter_graphs_mutex;
};
#include <boost/date_time.hpp>
#include "film.h"
#include "format.h"
-#include "imagemagick_encoder.h"
#include "job.h"
#include "filter.h"
#include "transcoder.h"
Film::Film (string d, bool must_exist)
: _use_dci_name (true)
+ , _trust_content_header (true)
, _dcp_content_type (0)
, _format (0)
, _scaler (Scaler::from_id ("bicubic"))
, _name (o._name)
, _use_dci_name (o._use_dci_name)
, _content (o._content)
+ , _trust_content_header (o._trust_content_header)
, _dcp_content_type (o._dcp_content_type)
, _format (o._format)
, _crop (o._crop)
, _studio (o._studio)
, _facility (o._facility)
, _package_type (o._package_type)
- , _thumbs (o._thumbs)
, _size (o._size)
, _length (o._length)
, _content_digest (o._content_digest)
throw MissingSettingError ("name");
}
- shared_ptr<Options> o (new Options (j2k_dir(), ".j2c", dir ("wavs")));
- o->out_size = format()->dcp_size ();
- o->padding = format()->dcp_padding (shared_from_this ());
- o->ratio = format()->ratio_as_float (shared_from_this ());
+ shared_ptr<EncodeOptions> oe (new EncodeOptions (j2k_dir(), ".j2c", dir ("wavs")));
+ oe->out_size = format()->dcp_size ();
+ oe->padding = format()->dcp_padding (shared_from_this ());
if (dcp_length ()) {
- o->video_decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
+ oe->video_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get());
if (audio_stream()) {
- o->audio_decode_range = make_pair (
- video_frames_to_audio_frames (o->video_decode_range.get().first, audio_stream()->sample_rate(), frames_per_second()),
- video_frames_to_audio_frames (o->video_decode_range.get().second, audio_stream()->sample_rate(), frames_per_second())
+ oe->audio_range = make_pair (
+ video_frames_to_audio_frames (oe->video_range.get().first, audio_stream()->sample_rate(), frames_per_second()),
+ video_frames_to_audio_frames (oe->video_range.get().second, audio_stream()->sample_rate(), frames_per_second())
);
}
}
- o->decode_subtitles = with_subtitles ();
- o->decode_video_skip = dcp_frame_rate (frames_per_second()).skip;
+
+ oe->video_skip = dcp_frame_rate (frames_per_second()).skip;
+
+ shared_ptr<DecodeOptions> od (new DecodeOptions);
+ od->decode_subtitles = with_subtitles ();
shared_ptr<Job> r;
if (transcode) {
if (dcp_ab()) {
- r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
+ r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
} else {
- r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), o, shared_ptr<Job> ())));
+ r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od, oe, shared_ptr<Job> ())));
}
}
- r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), o, r)));
- JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), o, r)));
+ r = JobManager::instance()->add (shared_ptr<Job> (new CheckHashesJob (shared_from_this(), od, oe, r)));
+ JobManager::instance()->add (shared_ptr<Job> (new MakeDCPJob (shared_from_this(), oe, r)));
}
/** Start a job to examine our content file */
return;
}
- set_thumbs (vector<SourceFrame> ());
- boost::filesystem::remove_all (dir ("thumbs"));
-
- /* This call will recreate the directory */
- dir ("thumbs");
-
_examine_content_job.reset (new ExamineContentJob (shared_from_this(), shared_ptr<Job> ()));
_examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
JobManager::instance()->add (_examine_content_job);
return N;
}
-/** Return the filename of a subtitle image if one exists for a given thumb index.
- * @param Thumbnail index.
- * @return Position of the image within the source frame, and the image filename, if one exists.
- * Otherwise the filename will be empty.
- */
-pair<Position, string>
-Film::thumb_subtitle (int n) const
-{
- string sub_file = thumb_base(n) + ".sub";
- if (!boost::filesystem::exists (sub_file)) {
- return pair<Position, string> ();
- }
-
- pair<Position, string> sub;
-
- ifstream f (sub_file.c_str ());
- multimap<string, string> kv = read_key_value (f);
- for (map<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
- if (i->first == "x") {
- sub.first.x = lexical_cast<int> (i->second);
- } else if (i->first == "y") {
- sub.first.y = lexical_cast<int> (i->second);
- sub.second = String::compose ("%1.sub.png", thumb_base(n));
- }
- }
-
- return sub;
-}
-
/** Write state to our `metadata' file */
void
Film::write_metadata () const
f << "name " << _name << "\n";
f << "use_dci_name " << _use_dci_name << "\n";
f << "content " << _content << "\n";
+ f << "trust_content_header " << (_trust_content_header ? "1" : "0") << "\n";
if (_dcp_content_type) {
f << "dcp_content_type " << _dcp_content_type->pretty_name () << "\n";
}
f << "facility " << _facility << "\n";
f << "package_type " << _package_type << "\n";
- /* Cached stuff; this is information about our content; we could
- look it up each time, but that's slow.
- */
- for (vector<SourceFrame>::const_iterator i = _thumbs.begin(); i != _thumbs.end(); ++i) {
- f << "thumb " << *i << "\n";
- }
f << "width " << _size.width << "\n";
f << "height " << _size.height << "\n";
f << "length " << _length.get_value_or(0) << "\n";
boost::mutex::scoped_lock lm (_state_mutex);
_external_audio.clear ();
- _thumbs.clear ();
_content_audio_streams.clear ();
_subtitle_streams.clear ();
_use_dci_name = (v == "1");
} else if (k == "content") {
_content = v;
+ } else if (k == "trust_content_header") {
+ _trust_content_header = (v == "1");
} else if (k == "dcp_content_type") {
_dcp_content_type = DCPContentType::from_pretty_name (v);
} else if (k == "format") {
}
/* Cached stuff */
- if (k == "thumb") {
- int const n = atoi (v.c_str ());
- /* Only add it to the list if it still exists */
- if (boost::filesystem::exists (thumb_file_for_frame (n))) {
- _thumbs.push_back (n);
- }
- } else if (k == "width") {
+ if (k == "width") {
_size.width = atoi (v.c_str ());
} else if (k == "height") {
_size.height = atoi (v.c_str ());
_dirty = false;
}
-/** @param n A thumb index.
- * @return The path to the thumb's image file.
- */
-string
-Film::thumb_file (int n) const
-{
- return thumb_file_for_frame (thumb_frame (n));
-}
-
-/** @param n A frame index within the Film's source.
- * @return The path to the thumb's image file for this frame;
- * we assume that it exists.
- */
-string
-Film::thumb_file_for_frame (SourceFrame n) const
-{
- return thumb_base_for_frame(n) + ".png";
-}
-
-/** @param n Thumb index.
- * Must not be called with the _state_mutex locked.
- */
-string
-Film::thumb_base (int n) const
-{
- return thumb_base_for_frame (thumb_frame (n));
-}
-
-string
-Film::thumb_base_for_frame (SourceFrame n) const
-{
- stringstream s;
- s.width (8);
- s << setfill('0') << n;
-
- boost::filesystem::path p;
- p /= dir ("thumbs");
- p /= s.str ();
-
- return p.string ();
-}
-
-/** @param n A thumb index.
- * @return The frame within the Film's source that it is for.
- *
- * Must not be called with the _state_mutex locked.
- */
-SourceFrame
-Film::thumb_frame (int n) const
-{
- boost::mutex::scoped_lock lm (_state_mutex);
- assert (n < int (_thumbs.size ()));
- return _thumbs[n];
-}
-
Size
Film::cropped_size (Size s) const
{
*/
try {
- shared_ptr<Options> o (new Options ("", "", ""));
- o->out_size = Size (1024, 1024);
-
- pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (shared_from_this(), o, 0);
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
+ Decoders d = decoder_factory (shared_from_this(), o, 0);
- set_size (d.first->native_size ());
- set_frames_per_second (d.first->frames_per_second ());
- set_subtitle_streams (d.first->subtitle_streams ());
- set_content_audio_streams (d.second->audio_streams ());
+ set_size (d.video->native_size ());
+ set_frames_per_second (d.video->frames_per_second ());
+ set_subtitle_streams (d.video->subtitle_streams ());
+ set_content_audio_streams (d.audio->audio_streams ());
/* Start off with the first audio and subtitle streams */
- if (!d.second->audio_streams().empty()) {
- set_content_audio_stream (d.second->audio_streams().front());
+ if (!d.audio->audio_streams().empty()) {
+ set_content_audio_stream (d.audio->audio_streams().front());
}
- if (!d.first->subtitle_streams().empty()) {
- set_subtitle_stream (d.first->subtitle_streams().front());
+ if (!d.video->subtitle_streams().empty()) {
+ set_subtitle_stream (d.video->subtitle_streams().front());
}
{
signal_changed (CONTENT);
set_content_digest (md5_digest (content_path ()));
-
+
examine_content ();
} catch (...) {
}
}
- if (!_trust_content_header) && !content().empty()) {
+
+ void
+ Film::set_trust_content_header (bool t)
+ {
+ {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ _trust_content_header = t;
+ }
+
+ signal_changed (TRUST_CONTENT_HEADER);
+
++ if (!_trust_content_header && !content().empty()) {
+ /* We just said that we don't trust the content's header */
+ examine_content ();
+ }
+ }
void
Film::set_dcp_content_type (DCPContentType const * t)
_external_audio = a;
}
- shared_ptr<Options> o (new Options ("", "", ""));
- o->decode_audio = true;
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0));
if (decoder->audio_stream()) {
_external_audio_stream = decoder->audio_stream ();
signal_changed (DCI_METADATA);
}
-void
-Film::set_thumbs (vector<SourceFrame> t)
-{
- {
- boost::mutex::scoped_lock lm (_state_mutex);
- _thumbs = t;
- }
- signal_changed (THUMBS);
-}
-
void
Film::set_size (Size s)
{
std::string j2k_dir () const;
std::vector<std::string> audio_files () const;
- std::pair<Position, std::string> thumb_subtitle (int) const;
void examine_content ();
void send_dcp_to_tms ();
std::string content_path () const;
ContentType content_type () const;
- std::string thumb_file (int) const;
- std::string thumb_base (int) const;
- SourceFrame thumb_frame (int) const;
-
int target_audio_sample_rate () const;
void write_metadata () const;
NAME,
USE_DCI_NAME,
CONTENT,
+ TRUST_CONTENT_HEADER,
DCP_CONTENT_TYPE,
FORMAT,
CROP,
SUBTITLE_OFFSET,
SUBTITLE_SCALE,
DCI_METADATA,
- THUMBS,
SIZE,
LENGTH,
CONTENT_AUDIO_STREAMS,
return _content;
}
+ bool trust_content_header () const {
+ boost::mutex::scoped_lock lm (_state_mutex);
+ return _trust_content_header;
+ }
+
DCPContentType const * dcp_content_type () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _dcp_content_type;
return _package_type;
}
- std::vector<SourceFrame> thumbs () const {
- boost::mutex::scoped_lock lm (_state_mutex);
- return _thumbs;
- }
-
Size size () const {
boost::mutex::scoped_lock lm (_state_mutex);
return _size;
void set_name (std::string);
void set_use_dci_name (bool);
void set_content (std::string);
+ void set_trust_content_header (bool);
void set_dcp_content_type (DCPContentType const *);
void set_format (Format const *);
void set_crop (Crop);
void set_studio (std::string);
void set_facility (std::string);
void set_package_type (std::string);
- void set_thumbs (std::vector<SourceFrame>);
void set_size (Size);
void set_length (SourceFrame);
void unset_length ();
/** The date that we should use in a DCI name */
boost::gregorian::date _dci_date;
- std::string thumb_file_for_frame (SourceFrame) const;
- std::string thumb_base_for_frame (SourceFrame) const;
void signal_changed (Property);
void examine_content_finished ();
* or an absolute path.
*/
std::string _content;
+ bool _trust_content_header;
/** The type of content that this Film represents (feature, trailer etc.) */
DCPContentType const * _dcp_content_type;
/** The format to present this Film in (flat, scope, etc.) */
/* Data which are cached to speed things up */
- /** Vector of frame indices for each of our `thumbnails' */
- std::vector<SourceFrame> _thumbs;
/** Size, in pixels, of the source (ignoring cropping) */
Size _size;
/** Actual length of the source (in video frames) from examining it */
class ImageMagickDecoder : public VideoDecoder
{
public:
- ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
float frames_per_second () const {
/* We don't know */
Size native_size () const;
+ SourceFrame length () const {
+ /* We don't know */
+ return 0;
+ }
+
int audio_channels () const {
return 0;
}
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
- * @param o Options.
+ * @param o Decode options.
* @param j Job that we are running under, or 0.
* @param e Encoder to use.
*/
-Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const DecodeOptions> o, Job* j, shared_ptr<Encoder> e)
: _job (j)
, _encoder (e)
, _decoders (decoder_factory (f, o, j))
}
/* Set up the decoder to use the film's set streams */
- _decoders.first->set_subtitle_stream (f->subtitle_stream ());
- if (_decoders.second) {
- _decoders.second->set_audio_stream (f->audio_stream ());
+ _decoders.video->set_subtitle_stream (f->subtitle_stream ());
- _decoders.audio->set_audio_stream (f->audio_stream ());
++ if (_decoders.audio) {
++ _decoders.audio->set_audio_stream (f->audio_stream ());
+ }
if (_matcher) {
- _decoders.first->connect_video (_matcher);
+ _decoders.video->connect_video (_matcher);
_matcher->connect_video (_encoder);
} else {
- _decoders.first->connect_video (_encoder);
+ _decoders.video->connect_video (_encoder);
}
- if (_matcher && _delay_line) {
- if (_matcher && _delay_line && _decoders.second) {
- _decoders.second->connect_audio (_delay_line);
++ if (_matcher && _delay_line && _decoders.audio) {
+ _decoders.audio->connect_audio (_delay_line);
_delay_line->connect_audio (_matcher);
_matcher->connect_audio (_gain);
_gain->connect_audio (_encoder);
while (1) {
if (!done[0]) {
- done[0] = _decoders.first->pass ();
- _decoders.first->set_progress ();
+ done[0] = _decoders.video->pass ();
+ _decoders.video->set_progress ();
}
- if (!done[1] && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
- if (!done[1] && _decoders.second && dynamic_pointer_cast<Decoder> (_decoders.second) != dynamic_pointer_cast<Decoder> (_decoders.first)) {
- done[1] = _decoders.second->pass ();
++ if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
+ done[1] = _decoders.audio->pass ();
} else {
done[1] = true;
}
* as a parameter to the constructor.
*/
+#include "decoder_factory.h"
+
class Film;
class Job;
class Encoder;
class VideoDecoder;
class AudioDecoder;
class DelayLine;
-class Options;
+class EncodeOptions;
+class DecodeOptions;
/** @class Transcoder
* @brief A class which takes a FilmState and some Options, then uses those to transcode a Film.
class Transcoder
{
public:
- Transcoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j, boost::shared_ptr<Encoder> e);
+ Transcoder (
+ boost::shared_ptr<Film> f,
+ boost::shared_ptr<const DecodeOptions> o,
+ Job* j,
+ boost::shared_ptr<Encoder> e
+ );
void go ();
- return _decoders.first;
+ boost::shared_ptr<VideoDecoder> video_decoder () const {
++ return _decoders.video;
+ }
+
protected:
/** A Job that is running this Transcoder, or 0 */
Job* _job;
/** The encoder that we will use */
boost::shared_ptr<Encoder> _encoder;
/** The decoders that we will use */
- std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _decoders;
+ Decoders _decoders;
boost::shared_ptr<Matcher> _matcher;
boost::shared_ptr<DelayLine> _delay_line;
boost::shared_ptr<Gain> _gain;
class VideoDecoder : public VideoSource, public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *);
+ VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const DecodeOptions>, Job *);
/** @return video frames per second, or 0 if unknown */
virtual float frames_per_second () const = 0;
/** @return native size in pixels */
virtual Size native_size () const = 0;
+ /** @return length (in source video frames), according to our content's header */
+ virtual SourceFrame length () const = 0;
virtual int time_base_numerator () const = 0;
virtual int time_base_denominator () const = 0;
return _subtitle_streams;
}
+ SourceFrame last_source_frame () const {
+ return _last_source_frame;
+ }
+
protected:
virtual PixelFormat pixel_format () const = 0;
- void emit_video (boost::shared_ptr<Image>);
+ void emit_video (boost::shared_ptr<Image>, SourceFrame);
void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
void repeat_last_video ();
void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>);
SourceFrame _video_frame;
-
+ SourceFrame _last_source_frame;
+
boost::shared_ptr<TimedSubtitle> _timed_subtitle;
boost::shared_ptr<Image> _last_image;
_content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
_film_sizer->Add (_content, 1, wxEXPAND);
+ _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Trust content's header"));
+ video_control (_trust_content_header);
+ _film_sizer->Add (_trust_content_header, 1);
+ _film_sizer->AddSpacer (0);
+
add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
_dcp_content_type = new wxComboBox (_film_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
_film_sizer->Add (_dcp_content_type);
_edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
_format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
_content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
+ _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this);
_left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
_right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
_top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
}
}
+ void
+ FilmEditor::trust_content_header_changed (wxCommandEvent &)
+ {
+ if (!_film) {
+ return;
+ }
+
+ _film->set_trust_content_header (_trust_content_header->GetValue ());
+ }
+
/** Called when the DCP A/B switch has been toggled */
void
FilmEditor::dcp_ab_toggled (wxCommandEvent &)
setup_subtitle_control_sensitivity ();
setup_streams ();
break;
+ case Film::TRUST_CONTENT_HEADER:
+ checked_set (_trust_content_header, _film->trust_content_header ());
+ break;
case Film::SUBTITLE_STREAMS:
setup_subtitle_control_sensitivity ();
setup_streams ();
checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
_dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
break;
- case Film::THUMBS:
- break;
case Film::DCP_AB:
checked_set (_dcp_ab, _film->dcp_ab ());
break;
film_changed (Film::NAME);
film_changed (Film::USE_DCI_NAME);
film_changed (Film::CONTENT);
+ film_changed (Film::TRUST_CONTENT_HEADER);
film_changed (Film::DCP_CONTENT_TYPE);
film_changed (Film::FORMAT);
film_changed (Film::CROP);
_edit_dci_button->Enable (s);
_format->Enable (s);
_content->Enable (s);
+ _trust_content_header->Enable (s);
_left_crop->Enable (s);
_right_crop->Enable (s);
_top_crop->Enable (s);
*/
/** @file src/film_viewer.cc
- * @brief A wx widget to view `thumbnails' of a Film.
+ * @brief A wx widget to view a preview of a Film.
*/
#include <iostream>
#include <iomanip>
+#include <wx/tglbtn.h>
#include "lib/film.h"
#include "lib/format.h"
#include "lib/util.h"
#include "lib/job_manager.h"
#include "lib/options.h"
#include "lib/subtitle.h"
+#include "lib/image.h"
+#include "lib/scaler.h"
#include "film_viewer.h"
#include "wx_util.h"
+#include "video_decoder.h"
using std::string;
using std::pair;
using std::max;
+using std::cout;
using boost::shared_ptr;
-class ThumbPanel : public wxPanel
+FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
+ : wxPanel (p)
+ , _panel (new wxPanel (this))
+ , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
+ , _play_button (new wxToggleButton (this, wxID_ANY, wxT ("Play")))
+ , _out_width (0)
+ , _out_height (0)
+ , _panel_width (0)
+ , _panel_height (0)
{
-public:
- ThumbPanel (wxPanel* parent, shared_ptr<Film> film)
- : wxPanel (parent)
- , _film (film)
- , _index (0)
- , _frame_rebuild_needed (false)
- , _composition_needed (false)
- {}
-
- /** Handle a paint event */
- void paint_event (wxPaintEvent& ev)
- {
- if (!_film || _film->thumbs().size() == 0) {
- wxPaintDC dc (this);
- return;
- }
-
- if (_frame_rebuild_needed) {
- _image.reset (new wxImage (std_to_wx (_film->thumb_file (_index))));
-
- _subtitle.reset ();
- pair<Position, string> s = _film->thumb_subtitle (_index);
- if (!s.second.empty ()) {
- _subtitle.reset (new SubtitleView (s.first, std_to_wx (s.second)));
- }
-
- _frame_rebuild_needed = false;
- compose ();
- }
+ wxBoxSizer* v_sizer = new wxBoxSizer (wxVERTICAL);
+ SetSizer (v_sizer);
- if (_composition_needed) {
- compose ();
- }
+ v_sizer->Add (_panel, 1, wxEXPAND);
- wxPaintDC dc (this);
- if (_bitmap) {
- dc.DrawBitmap (*_bitmap, 0, 0, false);
- }
+ wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
+ h_sizer->Add (_play_button, 0, wxEXPAND);
+ h_sizer->Add (_slider, 1, wxEXPAND);
- if (_film->with_subtitles() && _subtitle) {
- dc.DrawBitmap (*_subtitle->bitmap, _subtitle->transformed_area.x, _subtitle->transformed_area.y, true);
- }
- }
+ v_sizer->Add (h_sizer, 0, wxEXPAND);
- /** Handle a size event */
- void size_event (wxSizeEvent &)
- {
- if (!_image) {
- return;
- }
+ _panel->Bind (wxEVT_PAINT, &FilmViewer::paint_panel, this);
+ _panel->Bind (wxEVT_SIZE, &FilmViewer::panel_sized, this);
+ _slider->Bind (wxEVT_SCROLL_THUMBTRACK, &FilmViewer::slider_moved, this);
+ _slider->Bind (wxEVT_SCROLL_PAGEUP, &FilmViewer::slider_moved, this);
+ _slider->Bind (wxEVT_SCROLL_PAGEDOWN, &FilmViewer::slider_moved, this);
+ _play_button->Bind (wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &FilmViewer::play_clicked, this);
+ _timer.Bind (wxEVT_TIMER, &FilmViewer::timer, this);
- recompose ();
- }
+ set_film (_film);
+}
- /** @param n Thumbnail index */
- void set (int n)
+void
+FilmViewer::film_changed (Film::Property p)
+{
+ switch (p) {
+ case Film::FORMAT:
+ calculate_sizes ();
+ update_from_raw ();
+ break;
+ case Film::CONTENT:
{
- _index = n;
- _frame_rebuild_needed = true;
- Refresh ();
+ shared_ptr<DecodeOptions> o (new DecodeOptions);
+ o->decode_audio = false;
+ o->video_sync = false;
+ _decoders = decoder_factory (_film, o, 0);
+ _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2));
+ _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
+ break;
}
-
- void set_film (shared_ptr<Film> f)
- {
- _film = f;
- if (!_film) {
- clear ();
- _frame_rebuild_needed = true;
- Refresh ();
- } else {
- _frame_rebuild_needed = true;
- Refresh ();
- }
+ default:
+ break;
}
+}
- /** Clear our thumbnail image */
- void clear ()
- {
- _bitmap.reset ();
- _image.reset ();
- _subtitle.reset ();
+void
+FilmViewer::set_film (shared_ptr<Film> f)
+{
+ if (_film == f) {
+ return;
}
+
+ _film = f;
- void recompose ()
- {
- _composition_needed = true;
- Refresh ();
+ if (!_film) {
+ return;
}
- DECLARE_EVENT_TABLE ();
-
-private:
+ _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
- void compose ()
- {
- _composition_needed = false;
-
- if (!_film || !_image) {
- return;
- }
-
- /* Size of the view */
- int vw, vh;
- GetSize (&vw, &vh);
-
- Crop const fc = _film->crop ();
-
- /* Cropped rectangle */
- Rect cropped_area (
- fc.left,
- fc.top,
- _image->GetWidth() - (fc.left + fc.right),
- _image->GetHeight() - (fc.top + fc.bottom)
- );
-
- /* Target ratio */
- float const target = _film->format() ? _film->format()->ratio_as_float (_film) : 1.78;
-
- _transformed_image = _image->GetSubImage (wxRect (cropped_area.x, cropped_area.y, cropped_area.width, cropped_area.height));
-
- float x_scale = 1;
- float y_scale = 1;
-
- if ((float (vw) / vh) > target) {
- /* view is longer (horizontally) than the ratio; fit height */
- _transformed_image.Rescale (vh * target, vh, wxIMAGE_QUALITY_HIGH);
- x_scale = vh * target / cropped_area.width;
- y_scale = float (vh) / cropped_area.height;
- } else {
- /* view is shorter (horizontally) than the ratio; fit width */
- _transformed_image.Rescale (vw, vw / target, wxIMAGE_QUALITY_HIGH);
- x_scale = float (vw) / cropped_area.width;
- y_scale = (vw / target) / cropped_area.height;
- }
+ film_changed (Film::CONTENT);
+ film_changed (Film::CROP);
+ film_changed (Film::FORMAT);
+}
- _bitmap.reset (new wxBitmap (_transformed_image));
+void
+FilmViewer::decoder_changed ()
+{
+ seek_and_update (_decoders.video->last_source_frame ());
+}
- if (_subtitle) {
+void
+FilmViewer::timer (wxTimerEvent& ev)
+{
+ _panel->Refresh ();
+ _panel->Update ();
- _subtitle->transformed_area = subtitle_transformed_area (
- x_scale, y_scale, _subtitle->base_area, _film->subtitle_offset(), _film->subtitle_scale()
- );
+ shared_ptr<Image> last = _display;
+ while (last == _display) {
+ _decoders.video->pass ();
+ }
- _subtitle->transformed_image = _subtitle->base_image;
- _subtitle->transformed_image.Rescale (_subtitle->transformed_area.width, _subtitle->transformed_area.height, wxIMAGE_QUALITY_HIGH);
- _subtitle->transformed_area.x -= rint (_film->crop().left * x_scale);
- _subtitle->transformed_area.y -= rint (_film->crop().top * y_scale);
- _subtitle->bitmap.reset (new wxBitmap (_subtitle->transformed_image));
+ if (_film->length()) {
+ int const new_slider_position = 4096 * _decoders.video->last_source_frame() / _film->length().get();
+ if (new_slider_position != _slider->GetValue()) {
+ _slider->SetValue (new_slider_position);
}
}
+}
- shared_ptr<Film> _film;
- shared_ptr<wxImage> _image;
- wxImage _transformed_image;
- /** currently-displayed thumbnail index */
- int _index;
- shared_ptr<wxBitmap> _bitmap;
- bool _frame_rebuild_needed;
- bool _composition_needed;
- struct SubtitleView
- {
- SubtitleView (Position p, wxString const & i)
- : base_image (i)
- {
- base_area.x = p.x;
- base_area.y = p.y;
- base_area.width = base_image.GetWidth ();
- base_area.height = base_image.GetHeight ();
- }
+void
+FilmViewer::paint_panel (wxPaintEvent& ev)
+{
+ wxPaintDC dc (_panel);
+ if (!_display) {
+ return;
+ }
- Rect base_area;
- Rect transformed_area;
- wxImage base_image;
- wxImage transformed_image;
- shared_ptr<wxBitmap> bitmap;
- };
+ wxImage i (_out_width, _out_height, _display->data()[0], true);
+ wxBitmap b (i);
+ dc.DrawBitmap (b, 0, 0);
+}
- shared_ptr<SubtitleView> _subtitle;
-};
-BEGIN_EVENT_TABLE (ThumbPanel, wxPanel)
-EVT_PAINT (ThumbPanel::paint_event)
-EVT_SIZE (ThumbPanel::size_event)
-END_EVENT_TABLE ()
+void
+FilmViewer::slider_moved (wxCommandEvent& ev)
+{
+ if (_film->length()) {
+ seek_and_update (_slider->GetValue() * _film->length().get() / 4096);
+ }
+}
-FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
- : wxPanel (p)
+void
+FilmViewer::seek_and_update (SourceFrame f)
{
- _decoders.video->seek (f);
- _sizer = new wxBoxSizer (wxVERTICAL);
- SetSizer (_sizer);
++ if (_decoders.video->seek (f)) {
++ return;
++ }
- _thumb_panel = new ThumbPanel (this, f);
- _sizer->Add (_thumb_panel, 1, wxEXPAND);
-
- int const m = max ((size_t) 1, f ? f->thumbs().size() - 1 : 0);
- _slider = new wxSlider (this, wxID_ANY, 0, 0, m);
- _sizer->Add (_slider, 0, wxEXPAND | wxLEFT | wxRIGHT);
- set_thumbnail (0);
-
- _slider->Connect (wxID_ANY, wxEVT_COMMAND_SLIDER_UPDATED, wxCommandEventHandler (FilmViewer::slider_changed), 0, this);
+ shared_ptr<Image> last = _display;
+ while (last == _display) {
+ _decoders.video->pass ();
+ }
+ _panel->Refresh ();
+ _panel->Update ();
+}
- set_film (_film);
+void
+FilmViewer::panel_sized (wxSizeEvent& ev)
+{
+ _panel_width = ev.GetSize().GetWidth();
+ _panel_height = ev.GetSize().GetHeight();
+ calculate_sizes ();
+ update_from_raw ();
}
void
-FilmViewer::set_thumbnail (int n)
+FilmViewer::update_from_raw ()
{
- if (_film == 0 || int (_film->thumbs().size()) <= n) {
+ if (!_raw) {
return;
}
- _thumb_panel->set (n);
+ if (_out_width && _out_height) {
+ _display = _raw->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, Scaler::from_id ("bicubic"));
+ }
+
+ _panel->Refresh ();
+ _panel->Update ();
}
void
-FilmViewer::slider_changed (wxCommandEvent &)
+FilmViewer::calculate_sizes ()
{
- set_thumbnail (_slider->GetValue ());
+ float const panel_ratio = static_cast<float> (_panel_width) / _panel_height;
+ float const film_ratio = _film->format() ? _film->format()->ratio_as_float(_film) : 1.78;
+ if (panel_ratio < film_ratio) {
+ /* panel is less widscreen than the film; clamp width */
+ _out_width = _panel_width;
+ _out_height = _out_width / film_ratio;
+ } else {
+ /* panel is more widescreen than the film; clamp heignt */
+ _out_height = _panel_height;
+ _out_width = _out_height * film_ratio;
+ }
}
void
-FilmViewer::film_changed (Film::Property p)
+FilmViewer::play_clicked (wxCommandEvent &)
{
- ensure_ui_thread ();
-
- switch (p) {
- case Film::THUMBS:
- if (_film && _film->thumbs().size() > 1) {
- _slider->SetRange (0, _film->thumbs().size() - 1);
- } else {
- _thumb_panel->clear ();
- _slider->SetRange (0, 1);
- }
-
- _slider->SetValue (0);
- set_thumbnail (0);
- break;
- case Film::CONTENT:
- setup_visibility ();
- break;
- case Film::CROP:
- case Film::FORMAT:
- case Film::WITH_SUBTITLES:
- case Film::SUBTITLE_OFFSET:
- case Film::SUBTITLE_SCALE:
- _thumb_panel->recompose ();
- break;
- default:
- break;
- }
+ check_play_state ();
}
void
-FilmViewer::set_film (shared_ptr<Film> f)
+FilmViewer::check_play_state ()
{
- if (_film == f) {
- return;
+ if (_play_button->GetValue()) {
+ _timer.Start (1000 / _film->frames_per_second());
+ } else {
+ _timer.Stop ();
}
-
- _film = f;
- _thumb_panel->set_film (_film);
-
- if (!_film) {
- return;
- }
-
- _film->Changed.connect (bind (&FilmViewer::film_changed, this, _1));
- film_changed (Film::CROP);
- film_changed (Film::THUMBS);
- setup_visibility ();
}
void
-FilmViewer::setup_visibility ()
+FilmViewer::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub)
{
- if (!_film) {
- return;
+ _raw = image;
+ if (_out_width && _out_height) {
+ _display = _raw->scale_and_convert_to_rgb (Size (_out_width, _out_height), 0, Scaler::from_id ("bicubic"));
}
-
- ContentType const c = _film->content_type ();
- _slider->Show (c == VIDEO);
}