Merge master.
authorCarl Hetherington <cth@carlh.net>
Tue, 1 Apr 2014 21:51:54 +0000 (22:51 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 1 Apr 2014 21:51:54 +0000 (22:51 +0100)
156 files changed:
ChangeLog
cscript
doc/design/resampling.tex
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_mapping.cc
src/lib/audio_mapping.h
src/lib/audio_merger.h [deleted file]
src/lib/cinema.cc
src/lib/cinema.h
src/lib/colour_conversion.cc
src/lib/config.cc
src/lib/config.h
src/lib/content.cc
src/lib/content.h
src/lib/content_audio.h [new file with mode: 0644]
src/lib/content_factory.cc
src/lib/content_subtitle.cc [new file with mode: 0644]
src/lib/content_subtitle.h [new file with mode: 0644]
src/lib/content_video.h [new file with mode: 0644]
src/lib/dcp_content_type.cc
src/lib/dcp_content_type.h
src/lib/dcp_video.cc [new file with mode: 0644]
src/lib/dcp_video.h [new file with mode: 0644]
src/lib/dcp_video_frame.cc
src/lib/dcp_video_frame.h
src/lib/dcpomatic_time.cc [new file with mode: 0644]
src/lib/dcpomatic_time.h [new file with mode: 0644]
src/lib/decoder.cc [deleted file]
src/lib/decoder.h
src/lib/encoder.cc
src/lib/encoder.h
src/lib/exceptions.cc
src/lib/exceptions.h
src/lib/ffmpeg.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/film.cc
src/lib/film.h
src/lib/filter_graph.cc
src/lib/filter_graph.h
src/lib/frame_rate_change.cc [new file with mode: 0644]
src/lib/frame_rate_change.h [new file with mode: 0644]
src/lib/image.cc
src/lib/image.h
src/lib/image_content.cc
src/lib/image_content.h
src/lib/image_decoder.cc
src/lib/image_decoder.h
src/lib/image_examiner.cc
src/lib/image_examiner.h
src/lib/job.cc
src/lib/kdm.cc
src/lib/kdm.h
src/lib/player.cc
src/lib/player.h
src/lib/playlist.cc
src/lib/playlist.h
src/lib/position_image.h [new file with mode: 0644]
src/lib/ratio.cc
src/lib/ratio.h
src/lib/rect.h
src/lib/render_subtitles.cc [new file with mode: 0644]
src/lib/render_subtitles.h [new file with mode: 0644]
src/lib/resampler.cc
src/lib/resampler.h
src/lib/server.cc
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/subrip.cc [new file with mode: 0644]
src/lib/subrip.h [new file with mode: 0644]
src/lib/subrip_content.cc [new file with mode: 0644]
src/lib/subrip_content.h [new file with mode: 0644]
src/lib/subrip_decoder.cc [new file with mode: 0644]
src/lib/subrip_decoder.h [new file with mode: 0644]
src/lib/subrip_subtitle.h [new file with mode: 0644]
src/lib/subtitle_content.cc
src/lib/subtitle_decoder.cc
src/lib/subtitle_decoder.h
src/lib/transcode_job.cc
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/types.h
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_examiner.h
src/lib/writer.cc
src/lib/writer.h
src/lib/wscript
src/tools/dcpomatic_cli.cc
src/tools/dcpomatic_create.cc
src/tools/dcpomatic_kdm.cc
src/tools/server_test.cc
src/wx/audio_mapping_view.cc
src/wx/config_dialog.cc
src/wx/film_editor.cc
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/properties_dialog.cc
src/wx/screen_dialog.cc
src/wx/screen_dialog.h
src/wx/subtitle_panel.cc
src/wx/subtitle_panel.h
src/wx/subtitle_view.cc [new file with mode: 0644]
src/wx/subtitle_view.h [new file with mode: 0644]
src/wx/timecode.cc
src/wx/timecode.h
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timing_panel.cc
src/wx/video_panel.cc
src/wx/wscript
src/wx/wx_util.cc
test/audio_analysis_test.cc
test/audio_delay_test.cc
test/audio_mapping_test.cc
test/audio_merger_test.cc [deleted file]
test/black_fill_test.cc
test/client_server_test.cc
test/colour_conversion_test.cc
test/ffmpeg_audio_test.cc
test/ffmpeg_examiner_test.cc
test/ffmpeg_pts_offset.cc
test/frame_rate_test.cc
test/image_test.cc
test/make_black_test.cc
test/play_test.cc [deleted file]
test/ratio_test.cc
test/recover_test.cc
test/repeat_frame_test.cc [new file with mode: 0644]
test/resampler_test.cc
test/scaling_test.cc
test/seek_zero_test.cc [new file with mode: 0644]
test/silence_padding_test.cc
test/skip_frame_test.cc [new file with mode: 0644]
test/stream_test.cc
test/subrip_test.cc [new file with mode: 0644]
test/test.cc
test/test.h
test/util_test.cc
test/wscript
wscript

index 22fddbeb1fbca95c3271bc0f2e08ab4065c856a5..1d3ee28fa067027dbc1a5e236160be18151cd498 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2014-03-07  Carl Hetherington  <cth@carlh.net>
+
+       * Add subtitle view.
+
 2014-04-01  Carl Hetherington  <cth@carlh.net>
 
        * Basic support for separate left/right-eye files or directories
@@ -50,6 +54,7 @@
 
        * Another attempt to fix colour conversion dialog strange behaviour
        on OS X.
+>>>>>>> master
 
 2014-03-18  Carl Hetherington  <cth@carlh.net>
 
diff --git a/cscript b/cscript
index 7e6b769f075492d66e5ebe2e679f3feb6c2d2640..c07c430310a41ad3f430e1bc60ba8d0123fefe50 100644 (file)
--- a/cscript
+++ b/cscript
@@ -130,7 +130,7 @@ def make_control(debian_version, bits, filename, debug):
 
 def dependencies(target):
     return (('ffmpeg-cdist', 'a0db025'),
-            ('libdcp', '8af7b48'))
+            ('libdcp', '1.0'))
 
 def build(target, options):
     cmd = './waf configure --prefix=%s' % target.work_dir_cscript()
index 44aeee9b1b4c8abcdea917a7aeeebe66620c7815..cf9cfb1edc220bcdfa6f242b4aeee94427c2a0d5 100644 (file)
@@ -1,4 +1,5 @@
 \documentclass{article}
+\usepackage{amsmath}
 \begin{document}
 
 Here is what resampling we need to do.  Content video is at $C_V$ fps, audio at $C_A$.  
@@ -18,6 +19,7 @@ $C_V$ is a DCI rate, $C_A$ is not.  e.g.\ if $C_V = 24$, $C_A = 44.1\times{}10^3
 \textbf{Resample $C_A$ to the DCI rate.}
 
 \section{Hard case 1}
+\label{sec:hard1}
 
 $C_V$ is not a DCI rate, $C_A$ is, e.g.\ if $C_V = 25$, $C_A =
 48\times{}10^3$.  We will run the video at a nearby DCI rate $F_V$,
@@ -31,5 +33,24 @@ resample audio to $25 * 48\times{}10^3 / 24 = 50\times{}10^3$.
 \medskip
 \textbf{Resample $C_A$ to $C_V C_A / F_V$}
 
+\section{Hard case 2}
+
+Neither $C_V$ nor $C_A$ is not a DCI rate, e.g.\ if $C_V = 25$, $C_A =
+44.1\times{}10^3$.  We will run the video at a nearby DCI rate $F_V$,
+meaning that it will run faster or slower than it should.  We first
+resample the audio to a DCI rate $F_A$, then perform as with
+Section~\ref{sec:hard1} above.
+
+\medskip
+\textbf{Resample $C_A$ to $C_V F_A / F_V$}
+
+
+\section{The general case}
+
+Given a DCP running at $F_V$ and $F_A$ and a piece of content at $C_V$
+and $C_A$, resample the audio to $R_A$ where
+\begin{align*}
+R_A &= \frac{C_V F_A}{F_V}
+\end{align*}
 
 \end{document}
index bfe0ed61f281816a71e20b66f17c18e429d46ac9..af58e77ac59058097be12614c8c8499656ccff93 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include "audio_analysis.h"
+#include "audio_buffers.h"
 #include "analyse_audio_job.h"
 #include "compose.hpp"
 #include "film.h"
@@ -65,19 +66,18 @@ 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 ());
@@ -87,7 +87,7 @@ AnalyseAudioJob::run ()
 }
 
 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) {
index 3e376634cd0d9be375fc825ae60c6285db16588a..d3c35b67cc2b4d7fdb17dcd48f15c891602f5ffb 100644 (file)
@@ -20,6 +20,7 @@
 #include "job.h"
 #include "audio_analysis.h"
 #include "types.h"
+#include "dcpomatic_time.h"
 
 class AudioBuffers;
 class AudioContent;
@@ -34,10 +35,10 @@ public:
        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;
 
index b96300e15eb6c8c362f5964814a7ad1d4213eda8..6da5afa0c986a22bd45a52f90f434d9fab0df4bf 100644 (file)
@@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
 int const AudioContentProperty::AUDIO_DELAY = 204;
 int const AudioContentProperty::AUDIO_MAPPING = 205;
 
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
        : Content (f, s)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
@@ -149,5 +149,11 @@ AudioContent::audio_analysis_path () const
 string
 AudioContent::technical_summary () const
 {
-       return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
+       return String::compose (
+               "audio: channels %1, length %2, raw rate %3, out rate %4",
+               audio_channels(),
+               audio_length().seconds(),
+               content_audio_frame_rate(),
+               output_audio_frame_rate()
+               );
 }
index d30db02d775709d270eb75eed7a421aa1c877907..cecc8f13d65c1a0875a0dca0f0b2e383a37eb20c 100644 (file)
@@ -43,7 +43,7 @@ class AudioContent : public virtual Content
 public:
        typedef int64_t Frame;
        
-       AudioContent (boost::shared_ptr<const Film>, Time);
+       AudioContent (boost::shared_ptr<const Film>, DCPTime);
        AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -52,7 +52,7 @@ public:
        std::string technical_summary () const;
 
        virtual int audio_channels () const = 0;
-       virtual AudioContent::Frame audio_length () const = 0;
+       virtual ContentTime audio_length () const = 0;
        virtual int content_audio_frame_rate () const = 0;
        virtual int output_audio_frame_rate () const = 0;
        virtual AudioMapping audio_mapping () const = 0;
index c0ef02f65d5ab5518dcb7e53aa145b1ff3f9a598..9606c378c62d3babd09cf28c94b131c312aeaf39 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -22,6 +22,8 @@
 #include "exceptions.h"
 #include "log.h"
 #include "resampler.h"
+#include "util.h"
+#include "film.h"
 
 #include "i18n.h"
 
@@ -32,27 +34,102 @@ using std::cout;
 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)
+       , _decoded_audio (shared_ptr<AudioBuffers> (new AudioBuffers (content->audio_channels(), 0)), 0)
 {
+       if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) {
+               _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ()));
+       }
+}
+
+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->content_audio_frame_rate()), accurate);
+       }
+
+       /* 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) {
+               while (!pass() && _decoded_audio.audio->frames() < length) {}
+       } else {
+               while (!pass() && (_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end)) {}
+       }
+       
+       /* Clean up decoded */
 
+       AudioFrame const decoded_offset = frame - _decoded_audio.frame;
+       AudioFrame const amount_left = _decoded_audio.audio->frames() - decoded_offset;
+       _decoded_audio.audio->move (decoded_offset, 0, amount_left);
+       _decoded_audio.audio->set_frames (amount_left);
+
+       shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), length));
+       out->copy_from (_decoded_audio.audio.get(), length, frame - _decoded_audio.frame, 0);
+
+       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.
+ */
 void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
 {
-       Audio (data, frame);
-       _audio_position = frame + data->frames ();
+       if (_resampler) {
+               data = _resampler->run (data);
+       }
+
+       if (!_audio_position) {
+               _audio_position = time.frames (_audio_content->output_audio_frame_rate ());
+       }
+
+       assert (_audio_position >= (_decoded_audio.frame + _decoded_audio.audio->frames()));
+
+       /* Resize _decoded_audio to fit the new data */
+       _decoded_audio.audio->ensure_size (_audio_position.get() + data->frames() - _decoded_audio.frame);
+
+       /* 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 ();
 }
 
-/** 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.
- */
-bool
-AudioDecoder::has_audio () const
+/* XXX: called? */
+void
+AudioDecoder::flush ()
+{
+       if (!_resampler) {
+               return;
+       }
+
+       /*
+       shared_ptr<const AudioBuffers> b = _resampler->flush ();
+       if (b) {
+               _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (b, _audio_position.get ())));
+               _audio_position = _audio_position.get() + b->frames ();
+       }
+       */
+}
+
+void
+AudioDecoder::seek (ContentTime, bool)
 {
-       return _audio_content->audio_channels () > 0;
+       _audio_position.reset ();
 }
index ab6c4b8a931e12cfe892c8cb74d86ae7162d1a10..c83c356b7488c157dc85a6cd589dc7f256d029d5 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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 "decoder.h"
 #include "content.h"
 #include "audio_content.h"
+#include "content_audio.h"
 
 class AudioBuffers;
+class Resampler;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
@@ -36,18 +38,31 @@ class AudioBuffers;
 class AudioDecoder : public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
-
-       bool has_audio () const;
-
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
+       AudioDecoder (boost::shared_ptr<const AudioContent>);
+       
+       boost::shared_ptr<const AudioContent> audio_content () const {
+               return _audio_content;
+       }
 
+       /** Try to fetch some audio from a specific place in this content.
+        *  @param frame Frame to start from.
+        *  @param length Frames to get.
+        *  @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames.
+        *  @return Time-stamped audio data which may or may not be from the location (and of the length) requested.
+        */
+       boost::shared_ptr<ContentAudio> get_audio (AudioFrame time, AudioFrame length, bool accurate);
+       
 protected:
 
-       void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       void seek (ContentTime time, bool accurate);
+       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+       void flush ();
+
        boost::shared_ptr<const AudioContent> _audio_content;
-       AudioContent::Frame _audio_position;
+       boost::shared_ptr<Resampler> _resampler;
+       boost::optional<AudioFrame> _audio_position;
+       /** Currently-available decoded audio data */
+       ContentAudio _decoded_audio;
 };
 
 #endif
index ae7702998498902717b43df546c069a3e07475e3..1db827046946a8008d54bff21597d4fd1d201bff 100644 (file)
@@ -68,11 +68,11 @@ AudioMapping::make_default ()
 
        if (_content_channels == 1) {
                /* Mono -> Centre */
-               set (0, libdcp::CENTRE, 1);
+               set (0, dcp::CENTRE, 1);
        } else {
                /* 1:1 mapping */
                for (int i = 0; i < _content_channels; ++i) {
-                       set (i, static_cast<libdcp::Channel> (i), 1);
+                       set (i, static_cast<dcp::Channel> (i), 1);
                }
        }
 }
@@ -85,14 +85,14 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version
                /* Old-style: on/off mapping */
                list<cxml::NodePtr> const c = node->node_children ("Map");
                for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
-                       set ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
+                       set ((*i)->number_child<int> ("ContentIndex"), static_cast<dcp::Channel> ((*i)->number_child<int> ("DCP")), 1);
                }
        } else {
                list<cxml::NodePtr> const c = node->node_children ("Gain");
                for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
                        set (
                                (*i)->number_attribute<int> ("Content"),
-                               static_cast<libdcp::Channel> ((*i)->number_attribute<int> ("DCP")),
+                               static_cast<dcp::Channel> ((*i)->number_attribute<int> ("DCP")),
                                lexical_cast<float> ((*i)->content ())
                                );
                }
@@ -100,13 +100,13 @@ AudioMapping::AudioMapping (shared_ptr<const cxml::Node> node, int state_version
 }
 
 void
-AudioMapping::set (int c, libdcp::Channel d, float g)
+AudioMapping::set (int c, dcp::Channel d, float g)
 {
        _gain[c][d] = g;
 }
 
 float
-AudioMapping::get (int c, libdcp::Channel d) const
+AudioMapping::get (int c, dcp::Channel d) const
 {
        return _gain[c][d];
 }
@@ -121,7 +121,7 @@ AudioMapping::as_xml (xmlpp::Node* node) const
                        xmlpp::Element* t = node->add_child ("Gain");
                        t->set_attribute ("Content", lexical_cast<string> (c));
                        t->set_attribute ("DCP", lexical_cast<string> (d));
-                       t->add_child_text (lexical_cast<string> (get (c, static_cast<libdcp::Channel> (d))));
+                       t->add_child_text (lexical_cast<string> (get (c, static_cast<dcp::Channel> (d))));
                }
        }
 }
index 26087bfffa8f9e04924170444eea908640b6bced..f3096764c79c248ddb3bb0a0519efbd6caaa9f8f 100644 (file)
@@ -21,7 +21,7 @@
 #define DCPOMATIC_AUDIO_MAPPING_H
 
 #include <vector>
-#include <libdcp/types.h>
+#include <dcp/types.h>
 #include <boost/shared_ptr.hpp>
 
 namespace xmlpp {
@@ -50,8 +50,8 @@ public:
 
        void make_default ();
 
-       void set (int, libdcp::Channel, float);
-       float get (int, libdcp::Channel) const;
+       void set (int, dcp::Channel, float);
+       float get (int, dcp::Channel) const;
 
        int content_channels () const {
                return _content_channels;
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
deleted file mode 100644 (file)
index 226601e..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "audio_buffers.h"
-#include "util.h"
-
-template <class T, class F>
-class AudioMerger
-{
-public:
-       AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t)
-               : _buffers (new AudioBuffers (channels, 0))
-               , _last_pull (0)
-               , _t_to_f (t_to_f)
-               , _f_to_t (f_to_t)
-       {}
-
-       /** Pull audio up to a given time; after this call, no more data can be pushed
-        *  before the specified time.
-        */
-       TimedAudioBuffers<T>
-       pull (T time)
-       {
-               TimedAudioBuffers<T> out;
-               
-               F const to_return = _t_to_f (time - _last_pull);
-               out.audio.reset (new AudioBuffers (_buffers->channels(), to_return));
-               /* And this is how many we will get from our buffer */
-               F const to_return_from_buffers = min (to_return, _buffers->frames ());
-               
-               /* Copy the data that we have to the back end of the return buffer */
-               out.audio->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers);
-               /* Silence any gap at the start */
-               out.audio->make_silent (0, to_return - to_return_from_buffers);
-               
-               out.time = _last_pull;
-               _last_pull = time;
-               
-               /* And remove the data we're returning from our buffers */
-               if (_buffers->frames() > to_return_from_buffers) {
-                       _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers);
-               }
-               _buffers->set_frames (_buffers->frames() - to_return_from_buffers);
-
-               return out;
-       }
-
-       void
-       push (boost::shared_ptr<const AudioBuffers> audio, T time)
-       {
-               assert (time >= _last_pull);
-
-               F frame = _t_to_f (time);
-               F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_last_pull));
-               _buffers->ensure_size (after);
-               _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_last_pull), audio->frames ());
-               _buffers->set_frames (after);
-       }
-
-       F min (F a, int b)
-       {
-               if (a < b) {
-                       return a;
-               }
-
-               return b;
-       }
-
-       F max (int a, F b)
-       {
-               if (a > b) {
-                       return a;
-               }
-
-               return b;
-       }
-               
-       TimedAudioBuffers<T>
-       flush ()
-       {
-               if (_buffers->frames() == 0) {
-                       return TimedAudioBuffers<T> ();
-               }
-               
-               return TimedAudioBuffers<T> (_buffers, _last_pull);
-       }
-       
-private:
-       boost::shared_ptr<AudioBuffers> _buffers;
-       T _last_pull;
-       boost::function<F (T)> _t_to_f;
-       boost::function<T (F)> _f_to_t;
-};
index fca6b6afda36c80c653940eaaaa8819818327b4b..43a4322396144417041ec801c380895f170079bf 100644 (file)
@@ -70,7 +70,7 @@ Cinema::remove_screen (shared_ptr<Screen> s)
 Screen::Screen (shared_ptr<const cxml::Node> node)
 {
        name = node->string_child ("Name");
-       certificate = shared_ptr<libdcp::Certificate> (new libdcp::Certificate (node->string_child ("Certificate")));
+       certificate = shared_ptr<dcp::Certificate> (new dcp::Certificate (node->string_child ("Certificate")));
 }
 
 void
index 40dc15ae02ebaf66decb9f837ce845b5d40dcc99..d8e28ecfdecfecc569d68396cf5742a063a19d4a 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <boost/enable_shared_from_this.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
 
 class Cinema;
 
@@ -29,7 +29,7 @@ namespace cxml {
 class Screen
 {
 public:
-       Screen (std::string const & n, boost::shared_ptr<libdcp::Certificate> cert)
+       Screen (std::string const & n, boost::shared_ptr<dcp::Certificate> cert)
                : name (n)
                , certificate (cert)
        {}
@@ -40,7 +40,7 @@ public:
        
        boost::shared_ptr<Cinema> cinema;
        std::string name;
-       boost::shared_ptr<libdcp::Certificate> certificate;
+       boost::shared_ptr<dcp::Certificate> certificate;
 };
 
 class Cinema : public boost::enable_shared_from_this<Cinema>
index c3fa05426f3a2877b7b51cbf5d719ee2d2451794..e4a2a84bff4bc826355d0d20c60bbba4eb9b49e0 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <boost/lexical_cast.hpp>
 #include <libxml++/libxml++.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
 #include <libcxml/cxml.h>
 #include "config.h"
 #include "colour_conversion.h"
@@ -43,7 +43,7 @@ ColourConversion::ColourConversion ()
 {
        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];
                }
        }
 }
index eda56416d20a380f4e0ca654540593ba1ce1ae64..ca8d0bc53c407182c8b39dc0770648574f1d9e51 100644 (file)
@@ -23,7 +23,7 @@
 #include <glib.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
 #include <libcxml/cxml.h>
 #include "config.h"
 #include "server.h"
@@ -79,9 +79,9 @@ Config::Config ()
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
 
-       _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
-       _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
-       _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+       _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
+       _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
+       _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
 }
 
 void
@@ -165,7 +165,7 @@ Config::read ()
                /* Loading version 0 (before Rec. 709 was added as a preset).
                   Add it in.
                */
-               _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
+               _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
        }
 
        list<cxml::NodePtr> cin = f.node_children ("Cinema");
index a40e3680aace969bcf3fcfaa00d8dc206bbdc18a..ee11dcadbefb071cf606a07e9114275a5974c469 100644 (file)
@@ -28,7 +28,7 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/signals2.hpp>
 #include <boost/filesystem.hpp>
-#include <libdcp/metadata.h>
+#include <dcp/metadata.h>
 #include "dci_metadata.h"
 #include "colour_conversion.h"
 #include "server.h"
@@ -137,7 +137,7 @@ public:
                return _default_dcp_content_type;
        }
 
-       libdcp::XMLMetadata dcp_metadata () const {
+       dcp::XMLMetadata dcp_metadata () const {
                return _dcp_metadata;
        }
 
@@ -271,7 +271,7 @@ public:
                changed ();
        }
 
-       void set_dcp_metadata (libdcp::XMLMetadata m) {
+       void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
                changed ();
        }
@@ -375,7 +375,7 @@ private:
        int _default_still_length;
        Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
-       libdcp::XMLMetadata _dcp_metadata;
+       dcp::XMLMetadata _dcp_metadata;
        int _default_j2k_bandwidth;
        int _default_audio_delay;
        std::vector<PresetColourConversion> _colour_conversions;
index 8294682475a65b30ceb6717f5eea37b1dff33fba..1fb4681a251b2810c662426912114f6a2af181e7 100644 (file)
@@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f)
 
 }
 
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
        : _film (f)
        , _position (p)
        , _trim_start (0)
@@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
                _paths.push_back ((*i)->content ());
        }
        _digest = node->string_child ("Digest");
-       _position = node->number_child<Time> ("Position");
-       _trim_start = node->number_child<Time> ("TrimStart");
-       _trim_end = node->number_child<Time> ("TrimEnd");
+       _position = DCPTime (node->number_child<double> ("Position"));
+       _trim_start = DCPTime (node->number_child<double> ("TrimStart"));
+       _trim_end = DCPTime (node->number_child<double> ("TrimEnd"));
 }
 
 Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -96,11 +96,11 @@ Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
        , _change_signals_frequent (false)
 {
        for (size_t i = 0; i < c.size(); ++i) {
-               if (i > 0 && c[i]->trim_start ()) {
+               if (i > 0 && c[i]->trim_start() > DCPTime()) {
                        throw JoinError (_("Only the first piece of content to be joined can have a start trim."));
                }
 
-               if (i < (c.size() - 1) && c[i]->trim_end ()) {
+               if (i < (c.size() - 1) && c[i]->trim_end () > DCPTime()) {
                        throw JoinError (_("Only the last piece of content to be joined can have an end trim."));
                }
 
@@ -119,9 +119,9 @@ Content::as_xml (xmlpp::Node* node) const
                node->add_child("Path")->add_child_text (i->string ());
        }
        node->add_child("Digest")->add_child_text (_digest);
-       node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
-       node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
-       node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end));
+       node->add_child("Position")->add_child_text (lexical_cast<string> (_position.get ()));
+       node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start.get ()));
+       node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end.get ()));
 }
 
 void
@@ -146,7 +146,7 @@ Content::signal_changed (int p)
 }
 
 void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -161,7 +161,7 @@ Content::set_position (Time p)
 }
 
 void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -172,7 +172,7 @@ Content::set_trim_start (Time t)
 }
 
 void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -204,24 +204,15 @@ Content::clone () const
 string
 Content::technical_summary () const
 {
-       return String::compose ("%1 %2 %3", path_summary(), digest(), position());
+       return String::compose ("%1 %2 %3", path_summary(), digest(), position().seconds());
 }
 
-Time
+DCPTime
 Content::length_after_trim () const
 {
        return full_length() - trim_start() - trim_end();
 }
 
-/** @param t A time relative to the start of this content (not the position).
- *  @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
-       return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
 /** @return string which includes everything about how this content affects
  *  its playlist.
  */
@@ -231,9 +222,9 @@ Content::identifier () const
        stringstream s;
        
        s << Content::digest()
-         << "_" << position()
-         << "_" << trim_start()
-         << "_" << trim_end();
+         << "_" << position().get()
+         << "_" << trim_start().get()
+         << "_" << trim_end().get();
 
        return s.str ();
 }
index 596a0a905c95217d4daaf8ba116b8a6f518c6555..fc3a531faf8293b69d484581810b0ea3fd351801 100644 (file)
@@ -27,6 +27,7 @@
 #include <boost/enable_shared_from_this.hpp>
 #include <libxml++/libxml++.h>
 #include "types.h"
+#include "dcpomatic_time.h"
 
 namespace cxml {
        class Node;
@@ -49,7 +50,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no
 {
 public:
        Content (boost::shared_ptr<const Film>);
-       Content (boost::shared_ptr<const Film>, Time);
+       Content (boost::shared_ptr<const Film>, DCPTime);
        Content (boost::shared_ptr<const Film>, boost::filesystem::path);
        Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -63,7 +64,7 @@ public:
        virtual std::string technical_summary () const;
        virtual std::string information () const = 0;
        virtual void as_xml (xmlpp::Node *) const;
-       virtual Time full_length () const = 0;
+       virtual DCPTime full_length () const = 0;
        virtual std::string identifier () const;
 
        boost::shared_ptr<Content> clone () const;
@@ -95,42 +96,40 @@ public:
                return _digest;
        }
 
-       void set_position (Time);
+       void set_position (DCPTime);
 
-       /** Time that this content starts; i.e. the time that the first
+       /** DCPTime that this content starts; i.e. the time that the first
         *  bit of the content (trimmed or not) will happen.
         */
-       Time position () const {
+       DCPTime position () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _position;
        }
 
-       void set_trim_start (Time);
+       void set_trim_start (DCPTime);
 
-       Time trim_start () const {
+       DCPTime trim_start () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _trim_start;
        }
 
-       void set_trim_end (Time);
+       void set_trim_end (DCPTime);
        
-       Time trim_end () const {
+       DCPTime trim_end () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _trim_end;
        }
        
-       Time end () const {
-               return position() + length_after_trim() - 1;
+       DCPTime end () const {
+               return position() + length_after_trim();
        }
 
-       Time length_after_trim () const;
+       DCPTime length_after_trim () const;
        
        void set_change_signals_frequent (bool f) {
                _change_signals_frequent = f;
        }
 
-       bool trimmed (Time) const;
-
        boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
 
 protected:
@@ -148,9 +147,9 @@ protected:
        
 private:
        std::string _digest;
-       Time _position;
-       Time _trim_start;
-       Time _trim_end;
+       DCPTime _position;
+       DCPTime _trim_start;
+       DCPTime _trim_end;
        bool _change_signals_frequent;
 };
 
diff --git a/src/lib/content_audio.h b/src/lib/content_audio.h
new file mode 100644 (file)
index 0000000..6ee702f
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_buffers.h"
+
+class ContentAudio
+{
+public:
+       ContentAudio ()
+               : audio (new AudioBuffers (0, 0))
+               , frame (0)
+       {}
+               
+       ContentAudio (boost::shared_ptr<AudioBuffers> a, AudioFrame f)
+               : audio (a)
+               , frame (f)
+       {}
+
+       boost::shared_ptr<AudioBuffers> audio;
+       AudioFrame frame;
+};
index 98b1dd859536643c83c524e3a22a4df45b039c8a..092efd7903d7dba9219c483c57a03307d6733945 100644 (file)
@@ -21,6 +21,7 @@
 #include "ffmpeg_content.h"
 #include "image_content.h"
 #include "sndfile_content.h"
+#include "subrip_content.h"
 #include "util.h"
 
 using std::string;
@@ -40,6 +41,8 @@ content_factory (shared_ptr<const Film> film, cxml::NodePtr node, int version, l
                content.reset (new ImageContent (film, node, version));
        } else if (type == "Sndfile") {
                content.reset (new SndfileContent (film, node, version));
+       } else if (type == "SubRip") {
+               content.reset (new SubRipContent (film, node, version));
        }
 
        return content;
@@ -49,11 +52,16 @@ shared_ptr<Content>
 content_factory (shared_ptr<const Film> film, boost::filesystem::path path)
 {
        shared_ptr<Content> content;
+
+       string ext = path.extension().string ();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
                
        if (valid_image_file (path)) {
                content.reset (new ImageContent (film, path));
        } else if (SndfileContent::valid_file (path)) {
                content.reset (new SndfileContent (film, path));
+       } else if (ext == ".srt") {
+               content.reset (new SubRipContent (film, path));
        } else {
                content.reset (new FFmpegContent (film, path));
        }
diff --git a/src/lib/content_subtitle.cc b/src/lib/content_subtitle.cc
new file mode 100644 (file)
index 0000000..11873af
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "content_subtitle.h"
+
+ContentTime
+ContentTextSubtitle::from () const
+{
+       /* XXX: assuming we have some subs and they are all at the same time */
+       assert (!subs.empty ());
+       return ContentTime::from_seconds (double (subs.front().in().to_ticks()) / 250);
+}
+
+ContentTime
+ContentTextSubtitle::to () const
+{
+       /* XXX: assuming we have some subs and they are all at the same time */
+       assert (!subs.empty ());
+       return ContentTime::from_seconds (double (subs.front().out().to_ticks()) / 250);
+}
diff --git a/src/lib/content_subtitle.h b/src/lib/content_subtitle.h
new file mode 100644 (file)
index 0000000..8c266f4
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_SUBTITLE_H
+#define DCPOMATIC_CONTENT_SUBTITLE_H
+
+#include <list>
+#include <dcp/subtitle_string.h>
+#include "dcpomatic_time.h"
+#include "rect.h"
+
+class Image;
+
+class ContentSubtitle
+{
+public:
+       virtual ContentTime from () const = 0;
+       virtual ContentTime to () const = 0;
+};
+
+class ContentImageSubtitle : public ContentSubtitle
+{
+public:
+       ContentImageSubtitle (ContentTime f, ContentTime t, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r)
+               : image (im)
+               , rectangle (r)
+               , _from (f)
+               , _to (t)
+       {}
+
+       ContentTime from () const {
+               return _from;
+       }
+
+       ContentTime to () const {
+               return _to;
+       }
+       
+       boost::shared_ptr<Image> image;
+       dcpomatic::Rect<double> rectangle;
+
+private:
+       ContentTime _from;
+       ContentTime _to;
+};
+
+class ContentTextSubtitle : public ContentSubtitle
+{
+public:
+       ContentTextSubtitle (std::list<dcp::SubtitleString> s)
+               : subs (s)
+       {}
+
+       ContentTime from () const;
+       ContentTime to () const;
+       
+       std::list<dcp::SubtitleString> subs;
+};
+
+#endif
diff --git a/src/lib/content_video.h b/src/lib/content_video.h
new file mode 100644 (file)
index 0000000..20b5b8d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_CONTENT_VIDEO_H
+#define DCPOMATIC_CONTENT_VIDEO_H
+
+class Image;
+
+/** @class ContentVideo
+ *  @brief A frame of video straight out of some content.
+ */
+class ContentVideo
+{
+public:
+       ContentVideo ()
+               : eyes (EYES_BOTH)
+       {}
+
+       ContentVideo (boost::shared_ptr<const Image> i, Eyes e, VideoFrame f)
+               : image (i)
+               , eyes (e)
+               , frame (f)
+       {}
+       
+       boost::shared_ptr<const Image> image;
+       Eyes eyes;
+       VideoFrame frame;
+};
+
+#endif
index 82bd5fa018429c6ceea666cc580204e58b5a735d..b3a45e40e410e156d5e4b94ff6032ee2e0011d77 100644 (file)
@@ -30,7 +30,7 @@ using namespace std;
 
 vector<DCPContentType const *> DCPContentType::_dcp_content_types;
 
-DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
+DCPContentType::DCPContentType (string p, dcp::ContentKind k, string d)
        : _pretty_name (p)
        , _libdcp_kind (k)
        , _dci_name (d)
@@ -41,16 +41,16 @@ DCPContentType::DCPContentType (string p, libdcp::ContentKind k, string d)
 void
 DCPContentType::setup_dcp_content_types ()
 {
-       _dcp_content_types.push_back (new DCPContentType (_("Feature"), libdcp::FEATURE, N_("FTR")));
-       _dcp_content_types.push_back (new DCPContentType (_("Short"), libdcp::SHORT, N_("SHR")));
-       _dcp_content_types.push_back (new DCPContentType (_("Trailer"), libdcp::TRAILER, N_("TLR")));
-       _dcp_content_types.push_back (new DCPContentType (_("Test"), libdcp::TEST, N_("TST")));
-       _dcp_content_types.push_back (new DCPContentType (_("Transitional"), libdcp::TRANSITIONAL, N_("XSN")));
-       _dcp_content_types.push_back (new DCPContentType (_("Rating"), libdcp::RATING, N_("RTG")));
-       _dcp_content_types.push_back (new DCPContentType (_("Teaser"), libdcp::TEASER, N_("TSR")));
-       _dcp_content_types.push_back (new DCPContentType (_("Policy"), libdcp::POLICY, N_("POL")));
-       _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), libdcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
-       _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), libdcp::ADVERTISEMENT, N_("ADV")));
+       _dcp_content_types.push_back (new DCPContentType (_("Feature"), dcp::FEATURE, N_("FTR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Short"), dcp::SHORT, N_("SHR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Trailer"), dcp::TRAILER, N_("TLR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Test"), dcp::TEST, N_("TST")));
+       _dcp_content_types.push_back (new DCPContentType (_("Transitional"), dcp::TRANSITIONAL, N_("XSN")));
+       _dcp_content_types.push_back (new DCPContentType (_("Rating"), dcp::RATING, N_("RTG")));
+       _dcp_content_types.push_back (new DCPContentType (_("Teaser"), dcp::TEASER, N_("TSR")));
+       _dcp_content_types.push_back (new DCPContentType (_("Policy"), dcp::POLICY, N_("POL")));
+       _dcp_content_types.push_back (new DCPContentType (_("Public Service Announcement"), dcp::PUBLIC_SERVICE_ANNOUNCEMENT, N_("PSA")));
+       _dcp_content_types.push_back (new DCPContentType (_("Advertisement"), dcp::ADVERTISEMENT, N_("ADV")));
 }
 
 DCPContentType const *
index 965c163478af5fa30eeeefe3b36a1b0522a86d4d..05f30af5515d5c4ad1cd5713fb59d6200164a562 100644 (file)
@@ -26,7 +26,7 @@
 
 #include <string>
 #include <vector>
-#include <libdcp/dcp.h>
+#include <dcp/dcp.h>
 
 /** @class DCPContentType
  *  @brief A description of the type of content for a DCP (e.g. feature, trailer etc.)
 class DCPContentType : public boost::noncopyable
 {
 public:
-       DCPContentType (std::string, libdcp::ContentKind, std::string);
+       DCPContentType (std::string, dcp::ContentKind, std::string);
 
        /** @return user-visible `pretty' name */
        std::string pretty_name () const {
                return _pretty_name;
        }
 
-       libdcp::ContentKind libdcp_kind () const {
+       dcp::ContentKind libdcp_kind () const {
                return _libdcp_kind;
        }
 
@@ -58,7 +58,7 @@ public:
 
 private:
        std::string _pretty_name;
-       libdcp::ContentKind _libdcp_kind;
+       dcp::ContentKind _libdcp_kind;
        std::string _dci_name;
 
        /** All available DCP content types */
diff --git a/src/lib/dcp_video.cc b/src/lib/dcp_video.cc
new file mode 100644 (file)
index 0000000..8ea5cec
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "dcp_video.h"
+#include "image.h"
+
+using boost::shared_ptr;
+
+/** From ContentVideo:
+ *  @param in Image.
+ *  @param eyes Eye(s) that the Image is for.
+ *
+ *  From Content:
+ *  @param crop Crop to apply.
+ *  @param inter_size
+ *  @param out_size
+ *  @param scaler Scaler to use.
+ *  @param conversion Colour conversion to use.
+ *
+ *  @param time DCP time.
+ */
+DCPVideo::DCPVideo (
+       shared_ptr<const Image> in,
+       Eyes eyes,
+       Crop crop,
+       dcp::Size inter_size,
+       dcp::Size out_size,
+       Scaler const * scaler,
+       ColourConversion conversion,
+       DCPTime time
+       )
+       : _in (in)
+       , _eyes (eyes)
+       , _crop (crop)
+       , _inter_size (inter_size)
+       , _out_size (out_size)
+       , _scaler (scaler)
+       , _conversion (conversion)
+       , _time (time)
+{
+
+}
+
+void
+DCPVideo::set_subtitle (PositionImage s)
+{
+       _subtitle = s;
+}
+
+shared_ptr<Image>
+DCPVideo::image (AVPixelFormat format, bool aligned) const
+{
+       shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+       
+       Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
+
+       if (_subtitle.image) {
+               out->alpha_blend (_subtitle.image, _subtitle.position);
+       }
+
+       return out;
+}
+
diff --git a/src/lib/dcp_video.h b/src/lib/dcp_video.h
new file mode 100644 (file)
index 0000000..75823f3
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+extern "C" {
+#include <libavutil/avutil.h>
+}
+#include <boost/shared_ptr.hpp>
+#include "types.h"
+#include "colour_conversion.h"
+#include "position.h"
+#include "position_image.h"
+
+class Image;
+class Scaler;
+
+/** @class DCPVideo
+ *
+ *  A ContentVideo image with:
+ *     - content parameters (crop, scaling, colour conversion)
+ *     - merged content (subtitles)
+ *  and with its time converted from a ContentTime to a DCPTime.
+ */
+class DCPVideo
+{
+public:
+       DCPVideo (boost::shared_ptr<const Image>, Eyes eyes, Crop, dcp::Size, dcp::Size, Scaler const *, ColourConversion conversion, DCPTime time);
+
+       void set_subtitle (PositionImage);
+       boost::shared_ptr<Image> image (AVPixelFormat, bool) const;
+
+       Eyes eyes () const {
+               return _eyes;
+       }
+
+       ColourConversion conversion () const {
+               return _conversion;
+       }
+
+private:
+       boost::shared_ptr<const Image> _in;
+       Eyes _eyes;
+       Crop _crop;
+       dcp::Size _inter_size;
+       dcp::Size _out_size;
+       Scaler const * _scaler;
+       ColourConversion _conversion;
+       DCPTime _time;
+       PositionImage _subtitle;
+};
index 54531a0f9947e0eca275f78b36e8b4f6d71d6387..2b7a2e18f6b2ff712ba6baf47b9e074f44681676 100644 (file)
 #include <boost/asio.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
-#include <libdcp/rec709_linearised_gamma_lut.h>
-#include <libdcp/srgb_linearised_gamma_lut.h>
-#include <libdcp/gamma_lut.h>
-#include <libdcp/xyz_frame.h>
-#include <libdcp/rgb_xyz.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/gamma_lut.h>
+#include <dcp/xyz_frame.h>
+#include <dcp/rgb_xyz.h>
+#include <dcp/colour_matrix.h>
 #include <libcxml/cxml.h>
 #include "film.h"
 #include "dcp_video_frame.h"
@@ -68,7 +66,7 @@ using std::stringstream;
 using std::cout;
 using boost::shared_ptr;
 using boost::lexical_cast;
-using libdcp::Size;
+using dcp::Size;
 
 #define DCI_COEFFICENT (48.0 / 52.37)
 
@@ -120,12 +118,8 @@ DCPVideoFrame::DCPVideoFrame (shared_ptr<const Image> image, shared_ptr<const cx
 shared_ptr<EncodedData>
 DCPVideoFrame::encode_locally ()
 {
-       shared_ptr<libdcp::LUT> in_lut;
-       if (_conversion.input_gamma_linearised) {
-               in_lut = libdcp::SRGBLinearisedGammaLUT::cache.get (12, _conversion.input_gamma);
-       } else {
-               in_lut = libdcp::GammaLUT::cache.get (12, _conversion.input_gamma);
-       }
+       shared_ptr<dcp::GammaLUT> in_lut;
+       in_lut = dcp::GammaLUT::cache.get (12, _conversion.input_gamma, _conversion.input_gamma_linearised);
 
        /* XXX: libdcp should probably use boost */
        
@@ -136,10 +130,10 @@ DCPVideoFrame::encode_locally ()
                }
        }
        
-       shared_ptr<libdcp::XYZFrame> xyz = libdcp::rgb_to_xyz (
+       shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
                _image,
                in_lut,
-               libdcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma),
+               dcp::GammaLUT::cache.get (16, 1 / _conversion.output_gamma, false),
                matrix
                );
                
@@ -392,7 +386,7 @@ EncodedData::write (shared_ptr<const Film> film, int frame, Eyes eyes) const
 }
 
 void
-EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, libdcp::FrameInfo fin) const
+EncodedData::write_info (shared_ptr<const Film> film, int frame, Eyes eyes, dcp::FrameInfo fin) const
 {
        boost::filesystem::path const info = film->info_path (frame, eyes);
        FILE* h = fopen_boost (info, "w");
index 40f758c7423ab855e07ac3991984bfa559eeef1d..0a8b5f287f6860a7d172bbdb483430565895506f 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
     Taken from code Copyright (C) 2010-2011 Terrence Meiczinger
 
     This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,7 @@
 
 */
 
-#include <openjpeg.h>
-#include <libdcp/picture_asset.h>
-#include <libdcp/picture_asset_writer.h>
+#include <dcp/picture_mxf_writer.h>
 #include "util.h"
 
 /** @file  src/dcp_video_frame.h
@@ -49,7 +47,7 @@ public:
 
        void send (boost::shared_ptr<Socket> socket);
        void write (boost::shared_ptr<const Film>, int, Eyes) const;
-       void write_info (boost::shared_ptr<const Film>, int, Eyes, libdcp::FrameInfo) const;
+       void write_info (boost::shared_ptr<const Film>, int, Eyes, dcp::FrameInfo) const;
 
        /** @return data */
        uint8_t* data () const {
diff --git a/src/lib/dcpomatic_time.cc b/src/lib/dcpomatic_time.cc
new file mode 100644 (file)
index 0000000..9888864
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "dcpomatic_time.h"
+
+using std::ostream;
+
+ContentTime::ContentTime (DCPTime d, FrameRateChange f)
+       : Time (rint (d.get() * f.speed_up))
+{
+
+}
+
+DCPTime min (DCPTime a, DCPTime b)
+{
+       if (a < b) {
+               return a;
+       }
+
+       return b;
+}
+
+ostream &
+operator<< (ostream& s, ContentTime t)
+{
+       s << "[CONT " << t.get() << " " << t.seconds() << "s]";
+       return s;
+}
+
+ostream &
+operator<< (ostream& s, DCPTime t)
+{
+       s << "[DCP " << t.get() << " " << t.seconds() << "s]";
+       return s;
+}
diff --git a/src/lib/dcpomatic_time.h b/src/lib/dcpomatic_time.h
new file mode 100644 (file)
index 0000000..e0d47d9
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_TIME_H
+#define DCPOMATIC_TIME_H
+
+#include <cmath>
+#include <ostream>
+#include <stdint.h>
+#include "frame_rate_change.h"
+
+class dcpomatic_round_up_test;
+
+class Time;
+
+/** A time in seconds, expressed as a number scaled up by Time::HZ. */
+class Time
+{
+public:
+       Time ()
+               : _t (0)
+       {}
+
+       explicit Time (int64_t t)
+               : _t (t)
+       {}
+
+       virtual ~Time () {}
+
+       int64_t get () const {
+               return _t;
+       }
+
+       double seconds () const {
+               return double (_t) / HZ;
+       }
+
+       template <typename T>
+       int64_t frames (T r) const {
+               return rint (_t * r / HZ);
+       }
+
+protected:
+       friend class dcptime_round_up_test;
+       
+       int64_t _t;
+       static const int HZ = 96000;
+};
+
+class DCPTime;
+
+class ContentTime : public Time
+{
+public:
+       ContentTime () : Time () {}
+       explicit ContentTime (int64_t t) : Time (t) {}
+       ContentTime (int64_t n, int64_t d) : Time (n * HZ / d) {}
+       ContentTime (DCPTime d, FrameRateChange f);
+
+       bool operator< (ContentTime const & o) const {
+               return _t < o._t;
+       }
+
+       bool operator<= (ContentTime const & o) const {
+               return _t <= o._t;
+       }
+
+       bool operator== (ContentTime const & o) const {
+               return _t == o._t;
+       }
+
+       bool operator!= (ContentTime const & o) const {
+               return _t != o._t;
+       }
+
+       bool operator>= (ContentTime const & o) const {
+               return _t >= o._t;
+       }
+
+       bool operator> (ContentTime const & o) const {
+               return _t > o._t;
+       }
+
+       ContentTime operator+ (ContentTime const & o) const {
+               return ContentTime (_t + o._t);
+       }
+
+       ContentTime & operator+= (ContentTime const & o) {
+               _t += o._t;
+               return *this;
+       }
+
+       ContentTime operator- () const {
+               return ContentTime (-_t);
+       }
+
+       ContentTime operator- (ContentTime const & o) const {
+               return ContentTime (_t - o._t);
+       }
+
+       ContentTime & operator-= (ContentTime const & o) {
+               _t -= o._t;
+               return *this;
+       }
+
+       /** Round up to the nearest sampling interval
+        *  at some sampling rate.
+        *  @param r Sampling rate.
+        */
+       ContentTime round_up (int r) {
+               int64_t const n = HZ / r;
+               int64_t const a = _t + n - 1;
+               return ContentTime (a - (a % n));
+       }
+       
+
+       static ContentTime from_seconds (double s) {
+               return ContentTime (s * HZ);
+       }
+
+       template <class T>
+       static ContentTime from_frames (int64_t f, T r) {
+               return ContentTime (f * HZ / r);
+       }
+
+       static ContentTime max () {
+               return ContentTime (INT64_MAX);
+       }
+};
+
+std::ostream& operator<< (std::ostream& s, ContentTime t);
+
+class DCPTime : public Time
+{
+public:
+       DCPTime () : Time () {}
+       explicit DCPTime (int64_t t) : Time (t) {}
+       DCPTime (ContentTime t, FrameRateChange c) : Time (rint (t.get() / c.speed_up)) {}
+
+       bool operator< (DCPTime const & o) const {
+               return _t < o._t;
+       }
+
+       bool operator<= (DCPTime const & o) const {
+               return _t <= o._t;
+       }
+
+       bool operator== (DCPTime const & o) const {
+               return _t == o._t;
+       }
+
+       bool operator!= (DCPTime const & o) const {
+               return _t != o._t;
+       }
+
+       bool operator>= (DCPTime const & o) const {
+               return _t >= o._t;
+       }
+
+       bool operator> (DCPTime const & o) const {
+               return _t > o._t;
+       }
+
+       DCPTime operator+ (DCPTime const & o) const {
+               return DCPTime (_t + o._t);
+       }
+
+       DCPTime & operator+= (DCPTime const & o) {
+               _t += o._t;
+               return *this;
+       }
+
+       DCPTime operator- (DCPTime const & o) const {
+               return DCPTime (_t - o._t);
+       }
+
+       DCPTime & operator-= (DCPTime const & o) {
+               _t -= o._t;
+               return *this;
+       }
+
+       /** Round up to the nearest sampling interval
+        *  at some sampling rate.
+        *  @param r Sampling rate.
+        */
+       DCPTime round_up (int r) {
+               int64_t const n = HZ / r;
+               int64_t const a = _t + n - 1;
+               return DCPTime (a - (a % n));
+       }
+
+       DCPTime abs () const {
+               return DCPTime (std::abs (_t));
+       }
+
+       static DCPTime from_seconds (double s) {
+               return DCPTime (s * HZ);
+       }
+
+       template <class T>
+       static DCPTime from_frames (int64_t f, T r) {
+               return DCPTime (f * HZ / r);
+       }
+
+       static DCPTime delta () {
+               return DCPTime (1);
+       }
+
+       static DCPTime max () {
+               return DCPTime (INT64_MAX);
+       }
+};
+
+DCPTime min (DCPTime a, DCPTime b);
+std::ostream& operator<< (std::ostream& s, DCPTime t);
+
+#endif
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
deleted file mode 100644 (file)
index 3f4cda6..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file  src/decoder.cc
- *  @brief Parent class for decoders of content.
- */
-
-#include "film.h"
-#include "decoder.h"
-
-#include "i18n.h"
-
-using boost::shared_ptr;
-
-/** @param f Film.
- *  @param o Decode options.
- */
-Decoder::Decoder (shared_ptr<const Film> f)
-       : _film (f)
-{
-
-}
index d67592ed812544c644b8766bcb1b1be1c03e84de..df3ac4f39c4a734b9c433673e36ff2500b50d774 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
 #include <boost/utility.hpp>
+#include "types.h"
+#include "dcpomatic_time.h"
 
+class Decoded;
 class Film;
 
 /** @class Decoder.
@@ -36,21 +39,16 @@ class Film;
 class Decoder : public boost::noncopyable
 {
 public:
-       Decoder (boost::shared_ptr<const Film>);
        virtual ~Decoder () {}
 
-       /** Perform one decode pass of the content, which may or may not
-        *  cause the object to emit some data.
+protected:     
+       /** Seek so that the next peek() will yield the next thing
+        *  (video/sound frame, subtitle etc.) at or after the requested
+        *  time.  Pass accurate = true to try harder to get close to
+        *  the request.
         */
-       virtual void pass () = 0;
-       virtual bool done () const = 0;
-
-protected:
-
-       virtual void flush () {};
-       
-       /** The Film that we are decoding in */
-       boost::weak_ptr<const Film> _film;
+       virtual void seek (ContentTime time, bool accurate) = 0;
+       virtual bool pass () = 0;
 };
 
 #endif
index 8e8da6229099002dc1cb6cfb61d5914532c46b58..8f31b596eb8f4d972f38eb95c3b9e84c8e9d921f 100644 (file)
@@ -35,6 +35,7 @@
 #include "writer.h"
 #include "server_finder.h"
 #include "player.h"
+#include "dcp_video.h"
 
 #include "i18n.h"
 
@@ -60,9 +61,7 @@ Encoder::Encoder (shared_ptr<const Film> f, weak_ptr<Job> j)
        , _video_frames_out (0)
        , _terminate (false)
 {
-       _have_a_real_frame[EYES_BOTH] = false;
-       _have_a_real_frame[EYES_LEFT] = false;
-       _have_a_real_frame[EYES_RIGHT] = false;
+
 }
 
 Encoder::~Encoder ()
@@ -179,7 +178,7 @@ Encoder::frame_done ()
 }
 
 void
-Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, bool same)
+Encoder::process_video (shared_ptr<DCPVideo> frame)
 {
        _waker.nudge ();
        
@@ -206,28 +205,28 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
        rethrow ();
 
        if (_writer->can_fake_write (_video_frames_out)) {
-               _writer->fake_write (_video_frames_out, eyes);
-               _have_a_real_frame[eyes] = false;
-               frame_done ();
-       } else if (same && _have_a_real_frame[eyes]) {
-               /* Use the last frame that we encoded. */
-               _writer->repeat (_video_frames_out, eyes);
+               _writer->fake_write (_video_frames_out, frame->eyes ());
                frame_done ();
        } else {
                /* Queue this new frame for encoding */
                TIMING ("adding to queue of %1", _queue.size ());
                _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image->image(), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
-                                                 _film->j2k_bandwidth(), _film->resolution(), _film->log()
+                                                 frame->image(PIX_FMT_RGB24, false),
+                                                 _video_frames_out,
+                                                 frame->eyes(),
+                                                 frame->conversion(),
+                                                 _film->video_frame_rate(),
+                                                 _film->j2k_bandwidth(),
+                                                 _film->resolution(),
+                                                 _film->log()
                                                  )
                                          ));
                
                _condition.notify_all ();
-               _have_a_real_frame[eyes] = true;
        }
 
-       if (eyes != EYES_LEFT) {
+       if (frame->eyes() != EYES_LEFT) {
                ++_video_frames_out;
        }
 }
index e0ee2d41480f3fbf7b413cf0e974a96a4967c735..6c465f816773d12635f1b11dd020a807c7a1b756 100644 (file)
@@ -48,7 +48,7 @@ class EncodedData;
 class Writer;
 class Job;
 class ServerFinder;
-class PlayerImage;
+class DCPVideo;
 
 /** @class Encoder
  *  @brief Encoder to J2K and WAV for DCP.
@@ -67,10 +67,9 @@ public:
        void process_begin ();
 
        /** Call with a frame of video.
-        *  @param i Video frame image.
-        *  @param same true if i is the same as the last time we were called.
+        *  @param f Video frame.
         */
-       void process_video (boost::shared_ptr<PlayerImage> i, Eyes eyes, ColourConversion, bool same);
+       void process_video (boost::shared_ptr<DCPVideo> f);
 
        /** Call with some audio data */
        void process_audio (boost::shared_ptr<const AudioBuffers>);
@@ -106,7 +105,6 @@ private:
        /** Number of video frames written for the DCP so far */
        int _video_frames_out;
 
-       bool _have_a_real_frame[EYES_COUNT];
        bool _terminate;
        std::list<boost::shared_ptr<DCPVideoFrame> > _queue;
        std::list<boost::thread *> _threads;
index 8144f41b999690f526db309a4102547f4e69a05c..e05ac4ff04b3fdebb5617129f5c9a0eb6c9dc10e 100644 (file)
@@ -56,8 +56,14 @@ MissingSettingError::MissingSettingError (string s)
 
 }
 
-PixelFormatError::PixelFormatError (std::string o, AVPixelFormat f)
+PixelFormatError::PixelFormatError (string o, AVPixelFormat f)
        : StringError (String::compose (_("Cannot handle pixel format %1 during %2"), f, o))
 {
 
 }
+
+SubRipError::SubRipError (string saw, string expecting, boost::filesystem::path f)
+       : FileError (String::compose (_("Error in SubRip file: saw %1 while expecting %2"), saw, expecting), f)
+{
+
+}
index 3423a5754e340e3909b6b59ef617b5785d1a2809..213be6186d523a713a0a3f167f77127e0047164e 100644 (file)
@@ -230,6 +230,13 @@ public:
        PixelFormatError (std::string o, AVPixelFormat f);
 };
 
+/** An error that occurs while parsing a SubRip file */
+class SubRipError : public FileError
+{
+public:
+       SubRipError (std::string, std::string, boost::filesystem::path);
+};
+
 /** A parent class for classes which have a need to catch and
  *  re-throw exceptions.  This is intended for classes
  *  which run their own thread; they should do something like
index 60eea6ec7756b626cf76fb996f552940196730b9..a98aa98289e6839dedf04ccc557c33b68d1e9c71 100644 (file)
@@ -192,6 +192,10 @@ 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;
 }
 
index b39113fd1a39ca43def767e34c861460f3ba3714..86ce7503c9483c192e78c1655c99391f0ba68d36 100644 (file)
@@ -152,7 +152,7 @@ FFmpegContent::as_xml (xmlpp::Node* node) const
        }
 
        if (_first_video) {
-               node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+               node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get().get()));
        }
 }
 
@@ -163,14 +163,14 @@ FFmpegContent::examine (shared_ptr<Job> job)
 
        Content::examine (job);
 
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
+       take_from_video_examiner (examiner);
 
-       VideoContent::Frame video_length = 0;
-       video_length = examiner->video_length ();
-       film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
+       ContentTime video_length = examiner->video_length ();
+
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length.frames (video_frame_rate ())));
 
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -190,8 +190,6 @@ FFmpegContent::examine (shared_ptr<Job> job)
                _first_video = examiner->first_video ();
        }
 
-       take_from_video_examiner (examiner);
-
        signal_changed (ContentProperty::LENGTH);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
@@ -233,13 +231,13 @@ FFmpegContent::technical_summary () const
 string
 FFmpegContent::information () const
 {
-       if (video_length() == 0 || video_frame_rate() == 0) {
+       if (video_length() == ContentTime (0) || video_frame_rate() == 0) {
                return "";
        }
        
        stringstream s;
        
-       s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
+       s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
        s << VideoContent::information ();
 
        return s.str ();
@@ -267,19 +265,15 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
 }
 
-AudioContent::Frame
+ContentTime
 FFmpegContent::audio_length () const
 {
-       int const cafr = content_audio_frame_rate ();
-       float const vfr = video_frame_rate ();
-       VideoContent::Frame const vl = video_length_after_3d_combine ();
-
        boost::mutex::scoped_lock lm (_mutex);
        if (!_audio_stream) {
-               return 0;
+               return ContentTime ();
        }
-       
-       return video_frames_to_audio_frames (vl, cafr, vfr);
+
+       return video_length ();
 }
 
 int
@@ -315,16 +309,15 @@ FFmpegContent::output_audio_frame_rate () const
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_frame_rate (content_audio_frame_rate ());
 
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
 
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
           look different in the DCP compared to the source (slower or faster).
-          skip/repeat doesn't come into effect here.
        */
 
        if (frc.change_speed) {
-               t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+               t /= frc.speed_up;
        }
 
        return rint (t);
@@ -372,7 +365,7 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const
        root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
        root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
        if (first_audio) {
-               root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get ()));
+               root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio.get().get()));
        }
        mapping.as_xml (root->add_child("Mapping"));
 }
@@ -422,14 +415,12 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
        FFmpegStream::as_xml (root);
 }
 
-Time
+DCPTime
 FFmpegContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-       
-       FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
-       return video_length_after_3d_combine() * frc.factor() * TIME_HZ / film->video_frame_rate ();
+       return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate (), film->video_frame_rate ()));
 }
 
 AudioMapping
index 6ab95d2fe9d2bf53a08acd050845721ed40fc1a1..e4c4a8a52de35ef3cd2caf47f4a1c8a5c75829b3 100644 (file)
@@ -87,7 +87,7 @@ public:
        int frame_rate;
        int channels;
        AudioMapping mapping;
-       boost::optional<double> first_audio;
+       boost::optional<ContentTime> first_audio;
 
 private:
        friend class ffmpeg_pts_offset_test;
@@ -139,13 +139,13 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        std::string identifier () const;
        
        /* AudioContent */
        int audio_channels () const;
-       AudioContent::Frame audio_length () const;
+       ContentTime audio_length () const;
        int content_audio_frame_rate () const;
        int output_audio_frame_rate () const;
        AudioMapping audio_mapping () const;
@@ -182,7 +182,7 @@ public:
        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
        void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
 
-       boost::optional<double> first_video () const {
+       boost::optional<ContentTime> first_video () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _first_video;
        }
@@ -194,7 +194,7 @@ private:
        boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
        boost::shared_ptr<FFmpegAudioStream> _audio_stream;
-       boost::optional<double> _first_video;
+       boost::optional<ContentTime> _first_video;
        /** Video filters that should be used when generating DCPs */
        std::vector<Filter const *> _filters;
 };
index 1920f9275c66ba92d3ae85c9ddf81fbe2591253c..c9efd80fc47ba87b052362fd34b7940b570ff127 100644 (file)
@@ -33,7 +33,6 @@ extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 }
-#include "film.h"
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
@@ -56,20 +55,15 @@ using std::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)
        , FFmpeg (c)
+       , _log (log)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
-       , _decode_video (video)
-       , _decode_audio (audio)
-       , _pts_offset (0)
-       , _just_sought (false)
 {
        setup_subtitle ();
 
@@ -82,13 +76,11 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
           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 */
 
@@ -102,15 +94,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
 
        /* 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;
+               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;
        }
 }
 
@@ -133,20 +119,15 @@ FFmpegDecoder::flush ()
        
        /* 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);
@@ -156,29 +137,25 @@ FFmpegDecoder::pass ()
                        /* 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);
-                       film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
+                       _log->log (String::compose (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.
@@ -310,77 +287,131 @@ FFmpegDecoder::bytes_per_audio_sample () const
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
 {
-       double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+       int frames_read = 0;
+       optional<ContentTime> last_video;
+       optional<ContentTime> last_audio;
 
-       /* 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.
-       */
-       int initial = frame;
+       while (!finished (last_video, last_audio, frames_read)) {
+               int r = av_read_frame (_format_context, &_packet);
+               if (r < 0) {
+                       /* We should flush our decoders here, possibly yielding a few more frames,
+                          but the consequence of having to do that is too hideous to contemplate.
+                          Instead we give up and say that you can't seek too close to the end
+                          of a file.
+                       */
+                       return frames_read;
+               }
+
+               ++frames_read;
+
+               double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+               if (_packet.stream_index == _video_stream) {
+
+                       avcodec_get_frame_defaults (_frame);
+                       
+                       int finished = 0;
+                       r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+                       if (r >= 0 && finished) {
+                               last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+                       }
+
+               } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
+                       AVPacket copy_packet = _packet;
+                       while (copy_packet.size > 0) {
 
-       if (accurate) {
-               initial -= 5;
+                               int finished;
+                               r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
+                               if (r >= 0 && finished) {
+                                       last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
+                               }
+                                       
+                               copy_packet.data += r;
+                               copy_packet.size -= r;
+                       }
+               }
+               
+               av_free_packet (&_packet);
        }
 
-       if (initial < 0) {
-               initial = 0;
+       return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+       return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+       return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+       ContentTime const u = t - _pts_offset;
+       int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
+
+       if (_ffmpeg_content->audio_stream ()) {
+               s = min (
+                       s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+                       );
        }
 
-       /* Initial seek time in the stream's timebase */
-       int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+       /* Ridiculous empirical hack */
+       s--;
+       if (s < 0) {
+               s = 0;
+       }
 
-       av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+       av_seek_frame (_format_context, _video_stream, s, 0);
 
        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);
        }
+}
 
-       /* 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.
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+       VideoDecoder::seek (time, accurate);
+       AudioDecoder::seek (time, accurate);
+       
+       /* If we are doing an accurate seek, our initial shot will be 200ms (200 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.
        */
-       if (!accurate) {
-               _just_sought = true;
+
+       ContentTime pre_roll = accurate ? ContentTime::from_seconds (0.2) : ContentTime (0);
+       ContentTime initial_seek = time - pre_roll;
+       if (initial_seek < ContentTime (0)) {
+               initial_seek = ContentTime (0);
        }
-       
-       _video_position = frame;
-       
-       if (frame == 0 || !accurate) {
-               /* We're already there, or we're as close as we need to be */
+
+       /* Initial seek time in the video stream's timebase */
+
+       seek_and_flush (initial_seek);
+
+       if (!accurate) {
+               /* That'll do */
                return;
        }
 
-       while (1) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       return;
-               }
-
-               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->video_frame_rate()
-                               );
+       int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
 
-                       if (_video_position >= (frame - 1)) {
-                               av_free_packet (&_packet);
-                               break;
-                       }
-               }
-               
-               av_free_packet (&_packet);
+       seek_and_flush (initial_seek);
+       if (N > 0) {
+               minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
        }
 }
 
@@ -397,39 +428,23 @@ FFmpegDecoder::decode_audio_packet ()
 
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
                if (decode_result < 0) {
-                       shared_ptr<const Film> film = _film.lock ();
-                       assert (film);
-                       film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+                       _log->log (String::compose ("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 */
-                                       shared_ptr<AudioBuffers> silence (
-                                               new AudioBuffers (
-                                                       _ffmpeg_content->audio_channels(),
-                                                       pts * _ffmpeg_content->content_audio_frame_rate()
-                                                       )
-                                               );
-                                       
-                                       silence->make_silent ();
-                                       audio (silence, _audio_position);
-                               }
-                       }
+                       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;
@@ -450,18 +465,14 @@ FFmpegDecoder::decode_video_packet ()
        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);
-
-               film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+               _log->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
        } else {
                graph = *i;
        }
@@ -473,49 +484,10 @@ FFmpegDecoder::decode_video_packet ()
                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->video_frame_rate ());
-                               _just_sought = false;
-                       }
-
-                       double const next = _video_position / _ffmpeg_content->video_frame_rate();
-                       double const one_frame = 1 / _ffmpeg_content->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
-                                               )
-                                       );
-                               
-                               black->make_black ();
-                               video (image, 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 (image, false, _video_position);
-                       }
-                               
+                       double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
+                       video (image, rint (pts * _ffmpeg_content->video_frame_rate ()));
                } else {
-                       shared_ptr<const Film> film = _film.lock ();
-                       assert (film);
-                       film->log()->log ("Dropping frame without PTS");
+                       _log->log ("Dropping frame without PTS");
                }
        }
 
@@ -548,14 +520,6 @@ FFmpegDecoder::setup_subtitle ()
        }
 }
 
-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 ()
 {
@@ -569,20 +533,20 @@ FFmpegDecoder::decode_subtitle_packet ()
           indicate that the previous subtitle should stop.
        */
        if (sub.num_rects <= 0) {
-               subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+               image_subtitle (ContentTime (), ContentTime (), 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;
-
+       ContentTime packet_time = ContentTime::from_seconds (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;
+       ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3);
+       ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3);
 
        AVSubtitleRect const * rect = sub.rects[0];
 
@@ -593,7 +557,7 @@ FFmpegDecoder::decode_subtitle_packet ()
        /* 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];
@@ -615,20 +579,19 @@ FFmpegDecoder::decode_subtitle_packet ()
                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 (
+               from,
+               to,
                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);
 }
index d4b4fa1c02984f8690c42bd264434b5266cff52e..2cda8f89d5b6263db0600454369d7732cd893c1f 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -37,7 +37,7 @@ extern "C" {
 #include "subtitle_decoder.h"
 #include "ffmpeg.h"
 
-class Film;
+class Log;
 class FilterGraph;
 class ffmpeg_pts_offset_test;
 
@@ -47,18 +47,14 @@ class ffmpeg_pts_offset_test;
 class FFmpegDecoder : public VideoDecoder, public AudioDecoder, public SubtitleDecoder, public FFmpeg
 {
 public:
-       FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
+       FFmpegDecoder (boost::shared_ptr<const FFmpegContent>, boost::shared_ptr<Log>);
        ~FFmpegDecoder ();
 
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
-
 private:
        friend class ::ffmpeg_pts_offset_test;
 
-       static double compute_pts_offset (double, double, float);
-
+       void seek (ContentTime time, bool);
+       bool pass ();
        void flush ();
 
        void setup_subtitle ();
@@ -73,15 +69,17 @@ private:
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
 
+       bool seek_overrun_finished (ContentTime, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+       bool seek_final_finished (int, int) const;
+       int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
+       void seek_and_flush (ContentTime);
+
+       boost::shared_ptr<Log> _log;
        AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
        AVCodec* _subtitle_codec;                ///< may be 0 if there is no subtitle
        
        std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
        boost::mutex _filter_graphs_mutex;
 
-       bool _decode_video;
-       bool _decode_audio;
-
-       double _pts_offset;
-       bool _just_sought;
+       ContentTime _pts_offset;
 };
index ec090ed6123745bc147ad06ba1f0b7cccc18a38e..6daba4b4002fee010a90943d182d2089168e7f69 100644 (file)
@@ -102,14 +102,14 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
        }
 }
 
-optional<double>
+optional<ContentTime>
 FFmpegExaminer::frame_time (AVStream* s) const
 {
-       optional<double> t;
+       optional<ContentTime> t;
        
        int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
        if (bet != AV_NOPTS_VALUE) {
-               t = bet * av_q2d (s->time_base);
+               t = ContentTime (bet * av_q2d (s->time_base));
        }
 
        return t;
@@ -127,18 +127,18 @@ FFmpegExaminer::video_frame_rate () const
        return av_q2d (s->r_frame_rate);
 }
 
-libdcp::Size
+dcp::Size
 FFmpegExaminer::video_size () const
 {
-       return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+       return dcp::Size (video_codec_context()->width, video_codec_context()->height);
 }
 
-/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+/** @return Length according to our content's header */
+ContentTime
 FFmpegExaminer::video_length () const
 {
-       VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
-       return max (1, length);
+       ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE);
+       return ContentTime (max (int64_t (1), length.get ()));
 }
 
 string
index 369dac29c992748b3b394b1a42ae1da3a4315235..381c5cea9c9e85a48ec894326f92a482f82bf8e7 100644 (file)
@@ -30,8 +30,8 @@ public:
        FFmpegExaminer (boost::shared_ptr<const FFmpegContent>);
        
        float video_frame_rate () const;
-       libdcp::Size video_size () const;
-       VideoContent::Frame video_length () const;
+       dcp::Size video_size () const;
+       ContentTime video_length () const;
 
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
                return _subtitle_streams;
@@ -41,7 +41,7 @@ public:
                return _audio_streams;
        }
 
-       boost::optional<double> first_video () const {
+       boost::optional<ContentTime> first_video () const {
                return _first_video;
        }
        
@@ -49,9 +49,9 @@ private:
        std::string stream_name (AVStream* s) const;
        std::string audio_stream_name (AVStream* s) const;
        std::string subtitle_stream_name (AVStream* s) const;
-       boost::optional<double> frame_time (AVStream* s) const;
+       boost::optional<ContentTime> frame_time (AVStream* s) const;
        
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
-       boost::optional<double> _first_video;
+       boost::optional<ContentTime> _first_video;
 };
index 8bececf4f3f8e777e7ecd6e0b86b5f65587b35e9..267138ce63b74d51054d695b52777bd405a6ebc9 100644 (file)
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
-#include <boost/date_time.hpp>
 #include <libxml++/libxml++.h>
 #include <libcxml/cxml.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/cpl.h>
-#include <libdcp/signer.h>
-#include <libdcp/util.h>
-#include <libdcp/kdm.h>
+#include <dcp/signer_chain.h>
+#include <dcp/cpl.h>
+#include <dcp/signer.h>
+#include <dcp/util.h>
+#include <dcp/local_time.h>
 #include "film.h"
 #include "job.h"
 #include "util.h"
@@ -78,8 +77,8 @@ using boost::to_upper_copy;
 using boost::ends_with;
 using boost::starts_with;
 using boost::optional;
-using libdcp::Size;
-using libdcp::Signer;
+using dcp::Size;
+using dcp::Signer;
 
 /* 5 -> 6
  * AudioMapping XML changed.
@@ -436,7 +435,7 @@ Film::read_metadata ()
        _sequence_video = f.bool_child ("SequenceVideo");
        _three_d = f.bool_child ("ThreeD");
        _interop = f.bool_child ("Interop");
-       _key = libdcp::Key (f.string_child ("Key"));
+       _key = dcp::Key (f.string_child ("Key"));
 
        list<string> notes;
        /* This method is the only one that can return notes (so far) */
@@ -766,7 +765,7 @@ Film::j2c_path (int f, Eyes e, bool t) const
        return file (p);
 }
 
-/** @return List of subdirectories (not full paths) containing DCPs that can be successfully libdcp::DCP::read() */
+/** @return List of subdirectories (not full paths) containing DCPs that can be successfully dcp::DCP::read() */
 list<boost::filesystem::path>
 Film::dcps () const
 {
@@ -780,7 +779,7 @@ Film::dcps () const
                        ) {
 
                        try {
-                               libdcp::DCP dcp (*i);
+                               dcp::DCP dcp (*i);
                                dcp.read ();
                                out.push_back (i->path().leaf ());
                        } catch (...) {
@@ -875,7 +874,7 @@ Film::move_content_later (shared_ptr<Content> c)
        _playlist->move_later (c);
 }
 
-Time
+DCPTime
 Film::length () const
 {
        return _playlist->length ();
@@ -887,12 +886,18 @@ Film::has_subtitles () const
        return _playlist->has_subtitles ();
 }
 
-OutputVideoFrame
+int
 Film::best_video_frame_rate () const
 {
        return _playlist->best_dcp_frame_rate ();
 }
 
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+       return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
 void
 Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
@@ -911,31 +916,7 @@ Film::playlist_changed ()
        signal_changed (CONTENT);
 }      
 
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
-{
-       return divide_with_round (t * audio_frame_rate (), TIME_HZ);
-}
-
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
-{
-       return divide_with_round (t * video_frame_rate (), TIME_HZ);
-}
-
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
-{
-       return divide_with_round (f * TIME_HZ, audio_frame_rate ());
-}
-
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
-{
-       return divide_with_round (f * TIME_HZ, video_frame_rate ());
-}
-
-OutputAudioFrame
+int
 Film::audio_frame_rate () const
 {
        /* XXX */
@@ -951,38 +932,38 @@ Film::set_sequence_video (bool s)
 }
 
 /** @return Size of the largest possible image in whatever resolution we are using */
-libdcp::Size
+dcp::Size
 Film::full_frame () const
 {
        switch (_resolution) {
        case RESOLUTION_2K:
-               return libdcp::Size (2048, 1080);
+               return dcp::Size (2048, 1080);
        case RESOLUTION_4K:
-               return libdcp::Size (4096, 2160);
+               return dcp::Size (4096, 2160);
        }
 
        assert (false);
-       return libdcp::Size ();
+       return dcp::Size ();
 }
 
 /** @return Size of the frame */
-libdcp::Size
+dcp::Size
 Film::frame_size () const
 {
        return fit_ratio_within (container()->ratio(), full_frame ());
 }
 
-libdcp::KDM
+dcp::EncryptedKDM
 Film::make_kdm (
-       shared_ptr<libdcp::Certificate> target,
+       shared_ptr<dcp::Certificate> target,
        boost::filesystem::path dcp_dir,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime until
+       dcp::LocalTime from,
+       dcp::LocalTime until
        ) const
 {
        shared_ptr<const Signer> signer = make_signer ();
 
-       libdcp::DCP dcp (dir (dcp_dir.string ()));
+       dcp::DCP dcp (dir (dcp_dir.string ()));
        
        try {
                dcp.read ();
@@ -990,24 +971,22 @@ Film::make_kdm (
                throw KDMError (_("Could not read DCP to make KDM for"));
        }
        
-       time_t now = time (0);
-       struct tm* tm = localtime (&now);
-       string const issue_date = libdcp::tm_to_string (tm);
-       
        dcp.cpls().front()->set_mxf_keys (key ());
        
-       return libdcp::KDM (dcp.cpls().front(), signer, target, from, until, "DCP-o-matic", issue_date);
+       return dcp::DecryptedKDM (
+               dcp.cpls().front(), from, until, "DCP-o-matic", dcp.cpls().front()->content_title_text(), dcp::LocalTime().as_string()
+               ).encrypt (signer, target);
 }
 
-list<libdcp::KDM>
+list<dcp::EncryptedKDM>
 Film::make_kdms (
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime until
+       dcp::LocalTime from,
+       dcp::LocalTime until
        ) const
 {
-       list<libdcp::KDM> kdms;
+       list<dcp::EncryptedKDM> kdms;
 
        for (list<shared_ptr<Screen> >::iterator i = screens.begin(); i != screens.end(); ++i) {
                kdms.push_back (make_kdm ((*i)->certificate, dcp, from, until));
@@ -1022,7 +1001,7 @@ Film::make_kdms (
 uint64_t
 Film::required_disk_space () const
 {
-       return uint64_t (j2k_bandwidth() / 8) * length() / TIME_HZ;
+       return uint64_t (j2k_bandwidth() / 8) * length().seconds();
 }
 
 /** This method checks the disk that the Film is on and tries to decide whether or not
index 162b67b351bd6b9d0e2a97a6dc60e5b1e49387e1..ee8756b3dfdcf860d837c0dd90a11188427bd350 100644 (file)
@@ -31,8 +31,9 @@
 #include <boost/signals2.hpp>
 #include <boost/enable_shared_from_this.hpp>
 #include <boost/filesystem.hpp>
-#include <libdcp/key.h>
-#include <libdcp/kdm.h>
+#include <dcp/key.h>
+#include <dcp/decrypted_kdm.h>
+#include <dcp/encrypted_kdm.h>
 #include "util.h"
 #include "types.h"
 #include "dci_metadata.h"
@@ -95,20 +96,15 @@ public:
                return _dirty;
        }
 
-       libdcp::Size full_frame () const;
-       libdcp::Size frame_size () const;
+       dcp::Size full_frame () const;
+       dcp::Size frame_size () const;
 
        std::list<boost::filesystem::path> dcps () const;
 
        boost::shared_ptr<Player> make_player () const;
        boost::shared_ptr<Playlist> playlist () const;
 
-       OutputAudioFrame audio_frame_rate () const;
-
-       OutputAudioFrame time_to_audio_frames (Time) const;
-       OutputVideoFrame time_to_video_frames (Time) const;
-       Time video_frames_to_time (OutputVideoFrame) const;
-       Time audio_frames_to_time (OutputAudioFrame) const;
+       int audio_frame_rate () const;
 
        uint64_t required_disk_space () const;
        bool should_be_enough_disk_space (double &, double &) const;
@@ -116,26 +112,27 @@ public:
        /* Proxies for some Playlist methods */
 
        ContentList content () const;
-       Time length () const;
+       DCPTime length () const;
        bool has_subtitles () const;
-       OutputVideoFrame best_video_frame_rate () const;
+       int best_video_frame_rate () const;
+       FrameRateChange active_frame_rate_change (DCPTime) const;
 
-       libdcp::KDM
+       dcp::EncryptedKDM
        make_kdm (
-               boost::shared_ptr<libdcp::Certificate> target,
+               boost::shared_ptr<dcp::Certificate> target,
                boost::filesystem::path dcp,
-               boost::posix_time::ptime from,
-               boost::posix_time::ptime until
+               dcp::LocalTime from,
+               dcp::LocalTime until
                ) const;
        
-       std::list<libdcp::KDM> make_kdms (
+       std::list<dcp::EncryptedKDM> make_kdms (
                std::list<boost::shared_ptr<Screen> >,
                boost::filesystem::path dcp,
-               boost::posix_time::ptime from,
-               boost::posix_time::ptime until
+               dcp::LocalTime from,
+               dcp::LocalTime until
                ) const;
 
-       libdcp::Key key () const {
+       dcp::Key key () const {
                return _key;
        }
 
@@ -328,7 +325,7 @@ private:
        bool _three_d;
        bool _sequence_video;
        bool _interop;
-       libdcp::Key _key;
+       dcp::Key _key;
 
        int _state_version;
 
index a36a41f43c651abe8073a20f278411e95e848073..5add16d19bcedac612b55eecf5bc85a7feedf99a 100644 (file)
@@ -45,14 +45,14 @@ using std::make_pair;
 using std::cout;
 using boost::shared_ptr;
 using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
 
 /** Construct a FilterGraph for the settings in a piece of content.
  *  @param content Content.
  *  @param s Size of the images to process.
  *  @param p Pixel format of the images to process.
  */
-FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p)
        : _buffer_src_context (0)
        , _buffer_sink_context (0)
        , _size (s)
@@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size
                throw DecodeError (N_("could not configure filter graph."));
        }
 
-       /* XXX: leaking `inputs' / `outputs' ? */
+       avfilter_inout_free (&inputs);
+       avfilter_inout_free (&outputs);
 }
 
 FilterGraph::~FilterGraph ()
@@ -159,7 +160,7 @@ FilterGraph::process (AVFrame* frame)
  *  @return true if this chain can process images with `s' and `p', otherwise false.
  */
 bool
-FilterGraph::can_process (libdcp::Size s, AVPixelFormat p) const
+FilterGraph::can_process (dcp::Size s, AVPixelFormat p) const
 {
        return (_size == s && _pixel_format == p);
 }
index 9b403c2bc65734cf03a9b8458180a0b8695a26fc..45ad5d99874a0ef5c95b21c045a8fd6dd9520a24 100644 (file)
@@ -36,16 +36,16 @@ class FFmpegContent;
 class FilterGraph : public boost::noncopyable
 {
 public:
-       FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p);
+       FilterGraph (boost::shared_ptr<const FFmpegContent> content, dcp::Size s, AVPixelFormat p);
        ~FilterGraph ();
 
-       bool can_process (libdcp::Size s, AVPixelFormat p) const;
+       bool can_process (dcp::Size s, AVPixelFormat p) const;
        std::list<std::pair<boost::shared_ptr<Image>, int64_t> > process (AVFrame * frame);
 
 private:
        AVFilterContext* _buffer_src_context;
        AVFilterContext* _buffer_sink_context;
-       libdcp::Size _size; ///< size of the images that this chain can process
+       dcp::Size _size; ///< size of the images that this chain can process
        AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process
        AVFrame* _frame;
 };
diff --git a/src/lib/frame_rate_change.cc b/src/lib/frame_rate_change.cc
new file mode 100644 (file)
index 0000000..3e9c4b5
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cmath>
+#include "frame_rate_change.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+static bool
+about_equal (float a, float b)
+{
+       /* A film of F seconds at f FPS will be Ff frames;
+          Consider some delta FPS d, so if we run the same
+          film at (f + d) FPS it will last F(f + d) seconds.
+
+          Hence the difference in length over the length of the film will
+          be F(f + d) - Ff frames
+           = Ff + Fd - Ff frames
+           = Fd frames
+           = Fd/f seconds
+          So if we accept a difference of 1 frame, ie 1/f seconds, we can
+          say that
+
+          1/f = Fd/f
+       ie 1 = Fd
+       ie d = 1/F
+          So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
+          FPS error is 1/F ~= 0.0001 ~= 10-e4
+       */
+
+       return (fabs (a - b) < 1e-4);
+}
+
+
+FrameRateChange::FrameRateChange (float source, int dcp)
+       : skip (false)
+       , repeat (1)
+       , change_speed (false)
+{
+       if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
+               /* The difference between source and DCP frame rate will be lower
+                  (i.e. better) if we skip.
+               */
+               skip = true;
+       } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
+               /* The difference between source and DCP frame rate would be better
+                  if we repeated each frame once; it may be better still if we
+                  repeated more than once.  Work out the required repeat.
+               */
+               repeat = round (dcp / source);
+       }
+
+       speed_up = dcp / (source * factor());
+       change_speed = !about_equal (speed_up, 1.0);
+
+       if (!skip && repeat == 1 && !change_speed) {
+               description = _("Content and DCP have the same rate.\n");
+       } else {
+               if (skip) {
+                       description = _("DCP will use every other frame of the content.\n");
+               } else if (repeat == 2) {
+                       description = _("Each content frame will be doubled in the DCP.\n");
+               } else if (repeat > 2) {
+                       description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
+               }
+
+               if (change_speed) {
+                       float const pc = dcp * 100 / (source * factor());
+                       description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
+               }
+       }
+}
diff --git a/src/lib/frame_rate_change.h b/src/lib/frame_rate_change.h
new file mode 100644 (file)
index 0000000..6165f68
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <string>
+
+struct FrameRateChange
+{
+       FrameRateChange (float, int);
+
+       /** @return factor by which to multiply a source frame rate
+           to get the effective rate after any skip or repeat has happened.
+       */
+       float factor () const {
+               if (skip) {
+                       return 0.5;
+               }
+
+               return repeat;
+       }
+
+       /** true to skip every other frame */
+       bool skip;
+       /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
+       int repeat;
+       /** true if this DCP will run its video faster or slower than the source
+        *  without taking into account `repeat' nor `skip'.
+        *  (e.g. change_speed will be true if
+        *          source is 29.97fps, DCP is 30fps
+        *          source is 14.50fps, DCP is 30fps
+        *  but not if
+        *          source is 15.00fps, DCP is 30fps
+        *          source is 12.50fps, DCP is 25fps)
+        */
+       bool change_speed;
+
+       /** Amount by which the video is being sped-up in the DCP; e.g. for a
+        *  24fps source in a 25fps DCP this would be 25/24.
+        */
+       float speed_up;
+
+       std::string description;
+};
index d083cf3f6c17612caa6e249cf86413116f655667..432cfbd54b9279e0b2631cc417b511066dc2ab2f 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -30,6 +30,8 @@ extern "C" {
 #include "image.h"
 #include "exceptions.h"
 #include "scaler.h"
+#include "timer.h"
+#include "rect.h"
 
 #include "i18n.h"
 
@@ -37,8 +39,9 @@ using std::string;
 using std::min;
 using std::cout;
 using std::cerr;
+using std::list;
 using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
 
 int
 Image::line_factor (int n) const
@@ -82,7 +85,7 @@ Image::components () const
 
 /** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size' */
 shared_ptr<Image>
-Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::crop_scale_window (Crop crop, dcp::Size inter_size, dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
 {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
@@ -98,13 +101,13 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
        out->make_black ();
 
        /* Size of the image after any crop */
-       libdcp::Size const cropped_size = crop.apply (size ());
+       dcp::Size const cropped_size = crop.apply (size ());
 
        /* Scale context for a scale from cropped_size to inter_size */
        struct SwsContext* scale_context = sws_getContext (
-               cropped_size.width, cropped_size.height, pixel_format(),
-               inter_size.width, inter_size.height, out_format,
-               scaler->ffmpeg_id (), 0, 0, 0
+                       cropped_size.width, cropped_size.height, pixel_format(),
+                       inter_size.width, inter_size.height, out_format,
+                       scaler->ffmpeg_id (), 0, 0, 0
                );
 
        if (!scale_context) {
@@ -138,7 +141,7 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
 }
 
 shared_ptr<Image>
-Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
+Image::scale (dcp::Size out_size, Scaler const * scaler, AVPixelFormat out_format, bool out_aligned) const
 {
        assert (scaler);
        /* Empirical testing suggests that sws_scale() will crash if
@@ -169,7 +172,7 @@ Image::scale (libdcp::Size out_size, Scaler const * scaler, AVPixelFormat out_fo
 shared_ptr<Image>
 Image::crop (Crop crop, bool aligned) const
 {
-       libdcp::Size cropped_size = crop.apply (size ());
+       dcp::Size cropped_size = crop.apply (size ());
        shared_ptr<Image> out (new Image (pixel_format(), cropped_size, aligned));
 
        for (int c = 0; c < components(); ++c) {
@@ -341,11 +344,31 @@ Image::make_black ()
        }
 }
 
+void
+Image::make_transparent ()
+{
+       if (_pixel_format != PIX_FMT_RGBA) {
+               throw PixelFormatError ("make_transparent()", _pixel_format);
+       }
+
+       memset (data()[0], 0, lines(0) * stride()[0]);
+}
+
 void
 Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
 {
-       /* Only implemented for RGBA onto RGB24 so far */
-       assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+       int this_bpp = 0;
+       int other_bpp = 0;
+
+       if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+               this_bpp = 4;
+               other_bpp = 4;
+       } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+               this_bpp = 3;
+               other_bpp = 4;
+       } else {
+               assert (false);
+       }
 
        int start_tx = position.x;
        int start_ox = 0;
@@ -364,15 +387,15 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
 
        for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
-               uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+               uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
                uint8_t* op = other->data()[0] + oy * other->stride()[0];
                for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
                        float const alpha = float (op[3]) / 255;
                        tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
                        tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
                        tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
-                       tp += 3;
-                       op += 4;
+                       tp += this_bpp;
+                       op += other_bpp;
                }
        }
 }
@@ -456,8 +479,8 @@ Image::bytes_per_pixel (int c) const
  *  @param p Pixel format.
  *  @param s Size in pixels.
  */
-Image::Image (AVPixelFormat p, libdcp::Size s, bool aligned)
-       : libdcp::Image (s)
+Image::Image (AVPixelFormat p, dcp::Size s, bool aligned)
+       : dcp::Image (s)
        , _pixel_format (p)
        , _aligned (aligned)
 {
@@ -494,7 +517,7 @@ Image::allocate ()
 }
 
 Image::Image (Image const & other)
-       : libdcp::Image (other)
+       : dcp::Image (other)
        ,  _pixel_format (other._pixel_format)
        , _aligned (other._aligned)
 {
@@ -512,7 +535,7 @@ Image::Image (Image const & other)
 }
 
 Image::Image (AVFrame* frame)
-       : libdcp::Image (libdcp::Size (frame->width, frame->height))
+       : dcp::Image (dcp::Size (frame->width, frame->height))
        , _pixel_format (static_cast<AVPixelFormat> (frame->format))
        , _aligned (true)
 {
@@ -531,7 +554,7 @@ Image::Image (AVFrame* frame)
 }
 
 Image::Image (shared_ptr<const Image> other, bool aligned)
-       : libdcp::Image (other)
+       : dcp::Image (other)
        , _pixel_format (other->_pixel_format)
        , _aligned (aligned)
 {
@@ -564,7 +587,7 @@ Image::operator= (Image const & other)
 void
 Image::swap (Image & other)
 {
-       libdcp::Image::swap (other);
+       dcp::Image::swap (other);
        
        std::swap (_pixel_format, other._pixel_format);
 
@@ -607,7 +630,7 @@ Image::stride () const
        return _stride;
 }
 
-libdcp::Size
+dcp::Size
 Image::size () const
 {
        return _size;
@@ -619,3 +642,23 @@ Image::aligned () const
        return _aligned;
 }
 
+PositionImage
+merge (list<PositionImage> images)
+{
+       if (images.empty ()) {
+               return PositionImage ();
+       }
+
+       dcpomatic::Rect<int> all (images.front().position, images.front().image->size().width, images.front().image->size().height);
+       for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
+               all.extend (dcpomatic::Rect<int> (i->position, i->image->size().width, i->image->size().height));
+       }
+
+       shared_ptr<Image> merged (new Image (images.front().image->pixel_format (), dcp::Size (all.width, all.height), true));
+       merged->make_transparent ();
+       for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
+               merged->alpha_blend (i->image, i->position);
+       }
+
+       return PositionImage (merged, all.position ());
+}
index 2d9f322310c2c3e66d5a9d4e05f9c88deffb3b51..23b88dd7601066c022bb457f116b8c252c7be256 100644 (file)
@@ -31,16 +31,17 @@ extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavfilter/avfilter.h>
 }
-#include <libdcp/image.h>
+#include <dcp/image.h>
 #include "util.h"
 #include "position.h"
+#include "position_image.h"
 
 class Scaler;
 
-class Image : public libdcp::Image
+class Image : public dcp::Image
 {
 public:
-       Image (AVPixelFormat, libdcp::Size, bool);
+       Image (AVPixelFormat, dcp::Size, bool);
        Image (AVFrame *);
        Image (Image const &);
        Image (boost::shared_ptr<const Image>, bool);
@@ -50,19 +51,20 @@ public:
        uint8_t ** data () const;
        int * line_size () const;
        int * stride () const;
-       libdcp::Size size () const;
+       dcp::Size size () const;
        bool aligned () const;
 
        int components () const;
        int line_factor (int) const;
        int lines (int) const;
 
-       boost::shared_ptr<Image> scale (libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+       boost::shared_ptr<Image> scale (dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
        boost::shared_ptr<Image> crop (Crop c, bool aligned) const;
 
-       boost::shared_ptr<Image> crop_scale_window (Crop c, libdcp::Size, libdcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
+       boost::shared_ptr<Image> crop_scale_window (Crop c, dcp::Size, dcp::Size, Scaler const *, AVPixelFormat, bool aligned) const;
        
        void make_black ();
+       void make_transparent ();
        void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
        void copy (boost::shared_ptr<const Image> image, Position<int> pos);
 
@@ -89,4 +91,6 @@ private:
        bool _aligned;
 };
 
+extern PositionImage merge (std::list<PositionImage> images);
+
 #endif
index 13f7c52e3d12dec4256479d0d0bdbcec7c795253..d7b37a8353fa5f04b63c58006a2a6c8eade95a1f 100644 (file)
@@ -110,7 +110,7 @@ ImageContent::examine (shared_ptr<Job> job)
 }
 
 void
-ImageContent::set_video_length (VideoContent::Frame len)
+ImageContent::set_video_length (ContentTime len)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -120,14 +120,12 @@ ImageContent::set_video_length (VideoContent::Frame len)
        signal_changed (ContentProperty::LENGTH);
 }
 
-Time
+DCPTime
 ImageContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-       
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
-       return video_length_after_3d_combine() * frc.factor() * TIME_HZ / video_frame_rate();
+       return DCPTime (video_length_after_3d_combine(), FrameRateChange (video_frame_rate(), film->video_frame_rate()));
 }
 
 string
@@ -135,7 +133,7 @@ ImageContent::identifier () const
 {
        stringstream s;
        s << VideoContent::identifier ();
-       s << "_" << video_length();
+       s << "_" << video_length().get();
        return s.str ();
 }
 
index e56abce4a07a8218ab0ad4a7a1b26280d131adce..6db24767d5af5850a418444f204f69b896bb6f5b 100644 (file)
@@ -41,11 +41,11 @@ public:
        std::string summary () const;
        std::string technical_summary () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        std::string identifier () const;
        
-       void set_video_length (VideoContent::Frame);
+       void set_video_length (ContentTime);
        bool still () const;
        void set_video_frame_rate (float);
 };
index a7999c02a86b5270b09dd50fd118c00280ef5761..5de0c8582facb1aca80ed8d03479bdf2928331d4 100644 (file)
 
 using std::cout;
 using boost::shared_ptr;
-using libdcp::Size;
+using dcp::Size;
 
-ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageContent> c)
-       : Decoder (f)
-       , VideoDecoder (f, c)
+ImageDecoder::ImageDecoder (shared_ptr<const ImageContent> c)
+       : VideoDecoder (c)
        , _image_content (c)
 {
 
 }
 
-void
+bool
 ImageDecoder::pass ()
 {
-       if (_video_position >= _image_content->video_length ()) {
-               return;
+       if (_video_position >= _image_content->video_length().frames (_image_content->video_frame_rate ())) {
+               return true;
        }
 
        if (_image && _image_content->still ()) {
-               video (_image, true, _video_position);
-               return;
+               video (_image, _video_position);
+               ++_video_position;
+               return false;
        }
 
        Magick::Image* magick_image = 0;
+
        boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position);
+       
        try {
                magick_image = new Magick::Image (path.string ());
        } catch (...) {
                throw OpenFileError (path);
        }
        
-       libdcp::Size size (magick_image->columns(), magick_image->rows());
+       dcp::Size size (magick_image->columns(), magick_image->rows());
 
        _image.reset (new Image (PIX_FMT_RGB24, size, true));
 
@@ -80,17 +82,15 @@ ImageDecoder::pass ()
 
        delete magick_image;
 
-       video (_image, false, _video_position);
-}
+       video (_image, _video_position);
+       ++_video_position;
 
-void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
-       _video_position = frame;
+       return false;
 }
 
-bool
-ImageDecoder::done () const
+void
+ImageDecoder::seek (ContentTime time, bool accurate)
 {
-       return _video_position >= _image_content->video_length ();
+       VideoDecoder::seek (time, accurate);
+       _video_position = time.frames (_image_content->video_frame_rate ());
 }
index c7500243e08091ec3d6669f3c14273dfb93454e4..8d88df3de44eda5536e97e9a02e5560e32084c80 100644 (file)
@@ -28,20 +28,19 @@ class ImageContent;
 class ImageDecoder : public VideoDecoder
 {
 public:
-       ImageDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>);
+       ImageDecoder (boost::shared_ptr<const ImageContent> c);
 
        boost::shared_ptr<const ImageContent> content () {
                return _image_content;
        }
 
-       /* Decoder */
-
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
+       void seek (ContentTime, bool);
 
 private:
+       bool pass ();
+       
        boost::shared_ptr<const ImageContent> _image_content;
        boost::shared_ptr<Image> _image;
+       VideoFrame _video_position;
 };
 
index 12fe2b8a61d757f4c561bb4bcd867782d13edfdf..9ceb7b896515da1f983503d7aa2f0663319ef57a 100644 (file)
@@ -39,21 +39,20 @@ using boost::bad_lexical_cast;
 ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const ImageContent> content, shared_ptr<Job>)
        : _film (film)
        , _image_content (content)
-       , _video_length (0)
 {
        using namespace MagickCore;
        Magick::Image* image = new Magick::Image (content->path(0).string());
-       _video_size = libdcp::Size (image->columns(), image->rows());
+       _video_size = dcp::Size (image->columns(), image->rows());
        delete image;
 
        if (content->still ()) {
-               _video_length = Config::instance()->default_still_length() * video_frame_rate();
+               _video_length = ContentTime::from_seconds (Config::instance()->default_still_length());
        } else {
-               _video_length = _image_content->number_of_paths ();
+               _video_length = ContentTime (double (_image_content->number_of_paths ()) / video_frame_rate ());
        }
 }
 
-libdcp::Size
+dcp::Size
 ImageExaminer::video_size () const
 {
        return _video_size.get ();
index 8887f0d3d15a2587f56faff713a62835b3af8a7b..6ae0422cbbd4477f8ac7878e05a2430b251711b0 100644 (file)
@@ -31,14 +31,14 @@ public:
        ImageExaminer (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageContent>, boost::shared_ptr<Job>);
 
        float video_frame_rate () const;
-       libdcp::Size video_size () const;
-       VideoContent::Frame video_length () const {
+       dcp::Size video_size () const;
+       ContentTime video_length () const {
                return _video_length;
        }
 
 private:
        boost::weak_ptr<const Film> _film;
        boost::shared_ptr<const ImageContent> _image_content;
-       boost::optional<libdcp::Size> _video_size;
-       VideoContent::Frame _video_length;
+       boost::optional<dcp::Size> _video_size;
+       ContentTime _video_length;
 };
index 52499b43329348c0a97739e6d81cdf6c974783bf..e48daa4109bbfb935fdc1f532a0d6947c9a276f9 100644 (file)
@@ -23,7 +23,7 @@
 
 #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"
@@ -66,7 +66,7 @@ Job::run_wrapper ()
 
                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());
 
@@ -204,7 +204,7 @@ Job::set_state (State s)
        }       
 }
 
-/** @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
 {
index cf551285bb5444a476bd5b5f319b3ac556f056fd..793a3fa0eb5c0cea99d0edc60424742490ea3fae 100644 (file)
@@ -21,7 +21,7 @@
 #include <boost/shared_ptr.hpp>
 #include <quickmail.h>
 #include <zip.h>
-#include <libdcp/kdm.h>
+#include <dcp/encrypted_kdm.h>
 #include "kdm.h"
 #include "cinema.h"
 #include "exceptions.h"
@@ -36,13 +36,13 @@ using boost::shared_ptr;
 
 struct ScreenKDM
 {
-       ScreenKDM (shared_ptr<Screen> s, libdcp::KDM k)
+       ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
                : screen (s)
                , kdm (k)
        {}
        
        shared_ptr<Screen> screen;
-       libdcp::KDM kdm;
+       dcp::EncryptedKDM kdm;
 };
 
 static string
@@ -103,16 +103,16 @@ make_screen_kdms (
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to
+       dcp::LocalTime from,
+       dcp::LocalTime to
        )
 {
-       list<libdcp::KDM> kdms = film->make_kdms (screens, dcp, from, to);
+       list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, dcp, from, to);
           
        list<ScreenKDM> screen_kdms;
        
        list<shared_ptr<Screen> >::iterator i = screens.begin ();
-       list<libdcp::KDM>::iterator j = kdms.begin ();
+       list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
        while (i != screens.end() && j != kdms.end ()) {
                screen_kdms.push_back (ScreenKDM (*i, *j));
                ++i;
@@ -127,8 +127,8 @@ make_cinema_kdms (
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to
+       dcp::LocalTime from,
+       dcp::LocalTime to
        )
 {
        list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
@@ -169,8 +169,8 @@ write_kdm_files (
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to,
+       dcp::LocalTime from,
+       dcp::LocalTime to,
        boost::filesystem::path directory
        )
 {
@@ -189,8 +189,8 @@ write_kdm_zip_files (
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to,
+       dcp::LocalTime from,
+       dcp::LocalTime to,
        boost::filesystem::path directory
        )
 {
@@ -208,8 +208,8 @@ email_kdms (
        shared_ptr<const Film> film,
        list<shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to
+       dcp::LocalTime from,
+       dcp::LocalTime to
        )
 {
        list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
index c4fd43d49bfc315e623c1bf005070e61e2031778..5df161b2a03404beafc39d88fa2d7dddb0b83e8e 100644 (file)
@@ -27,8 +27,8 @@ extern void write_kdm_files (
        boost::shared_ptr<const Film> film,
        std::list<boost::shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to,
+       dcp::LocalTime from,
+       dcp::LocalTime to,
        boost::filesystem::path directory
        );
 
@@ -36,8 +36,8 @@ extern void write_kdm_zip_files (
        boost::shared_ptr<const Film> film,
        std::list<boost::shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to,
+       dcp::LocalTime from,
+       dcp::LocalTime to,
        boost::filesystem::path directory
        );
 
@@ -45,7 +45,7 @@ extern void email_kdms (
        boost::shared_ptr<const Film> film,
        std::list<boost::shared_ptr<Screen> > screens,
        boost::filesystem::path dcp,
-       boost::posix_time::ptime from,
-       boost::posix_time::ptime to
+       dcp::LocalTime from,
+       dcp::LocalTime to
        );
 
index c4558f33f7286dc7b7a3992c11258de7078e83cd..02ae4e5c91b1b94b77b40d51c199942ab4ef10a6 100644 (file)
 */
 
 #include <stdint.h>
+#include <algorithm>
 #include "player.h"
 #include "film.h"
 #include "ffmpeg_decoder.h"
+#include "audio_buffers.h"
 #include "ffmpeg_content.h"
 #include "image_decoder.h"
 #include "image_content.h"
 #include "sndfile_decoder.h"
 #include "sndfile_content.h"
 #include "subtitle_content.h"
+#include "subrip_decoder.h"
+#include "subrip_content.h"
 #include "playlist.h"
 #include "job.h"
 #include "image.h"
 #include "ratio.h"
-#include "resampler.h"
 #include "log.h"
 #include "scaler.h"
+#include "render_subtitles.h"
+#include "dcp_video.h"
+#include "config.h"
+#include "content_video.h"
 
 using std::list;
 using std::cout;
 using std::min;
 using std::max;
+using std::min;
 using std::vector;
 using std::pair;
 using std::map;
+using std::make_pair;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
-
-class Piece
-{
-public:
-       Piece (shared_ptr<Content> c)
-               : content (c)
-               , video_position (c->position ())
-               , audio_position (c->position ())
-               , repeat_to_do (0)
-               , repeat_done (0)
-       {}
-       
-       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
-               : content (c)
-               , decoder (d)
-               , video_position (c->position ())
-               , audio_position (c->position ())
-               , repeat_to_do (0)
-               , repeat_done (0)
-       {}
-
-       /** Set this piece to repeat a video frame a given number of times */
-       void set_repeat (IncomingVideo video, int num)
-       {
-               repeat_video = video;
-               repeat_to_do = num;
-               repeat_done = 0;
-       }
-
-       void reset_repeat ()
-       {
-               repeat_video.image.reset ();
-               repeat_to_do = 0;
-               repeat_done = 0;
-       }
-
-       bool repeating () const
-       {
-               return repeat_done != repeat_to_do;
-       }
-
-       void repeat (Player* player)
-       {
-               player->process_video (
-                       repeat_video.weak_piece,
-                       repeat_video.image,
-                       repeat_video.eyes,
-                       repeat_done > 0,
-                       repeat_video.frame,
-                       (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
-                       );
-
-               ++repeat_done;
-       }
-       
-       shared_ptr<Content> content;
-       shared_ptr<Decoder> decoder;
-       /** Time of the last video we emitted relative to the start of the DCP */
-       Time video_position;
-       /** Time of the last audio we emitted relative to the start of the DCP */
-       Time audio_position;
-
-       IncomingVideo repeat_video;
-       int repeat_to_do;
-       int repeat_done;
-};
+using boost::optional;
 
 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        : _film (f)
        , _playlist (p)
-       , _video (true)
-       , _audio (true)
        , _have_valid_pieces (false)
-       , _video_position (0)
-       , _audio_position (0)
-       , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
-       , _last_emit_was_black (false)
+       , _approximate_size (false)
+       , _burn_subtitles (false)
 {
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
@@ -129,381 +69,87 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        set_video_container_size (_film->frame_size ());
 }
 
-void
-Player::disable_video ()
-{
-       _video = false;
-}
-
-void
-Player::disable_audio ()
-{
-       _audio = false;
-}
-
-bool
-Player::pass ()
-{
-       if (!_have_valid_pieces) {
-               setup_pieces ();
-       }
-
-       Time earliest_t = TIME_MAX;
-       shared_ptr<Piece> earliest;
-       enum {
-               VIDEO,
-               AUDIO
-       } type = VIDEO;
-
-       for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               if ((*i)->decoder->done ()) {
-                       continue;
-               }
-
-               shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
-               shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
-
-               if (_video && vd) {
-                       if ((*i)->video_position < earliest_t) {
-                               earliest_t = (*i)->video_position;
-                               earliest = *i;
-                               type = VIDEO;
-                       }
-               }
-
-               if (_audio && ad && ad->has_audio ()) {
-                       if ((*i)->audio_position < earliest_t) {
-                               earliest_t = (*i)->audio_position;
-                               earliest = *i;
-                               type = AUDIO;
-                       }
-               }
-       }
-
-       if (!earliest) {
-               flush ();
-               return true;
-       }
-
-       switch (type) {
-       case VIDEO:
-               if (earliest_t > _video_position) {
-                       emit_black ();
-               } else {
-                       if (earliest->repeating ()) {
-                               earliest->repeat (this);
-                       } else {
-                               earliest->decoder->pass ();
-                       }
-               }
-               break;
-
-       case AUDIO:
-               if (earliest_t > _audio_position) {
-                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
-               } else {
-                       earliest->decoder->pass ();
-
-                       if (earliest->decoder->done()) {
-                               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
-                               assert (ac);
-                               shared_ptr<Resampler> re = resampler (ac, false);
-                               if (re) {
-                                       shared_ptr<const AudioBuffers> b = re->flush ();
-                                       if (b->frames ()) {
-                                               process_audio (earliest, b, ac->audio_length ());
-                                       }
-                               }
-                       }
-               }
-               break;
-       }
-
-       if (_audio) {
-               boost::optional<Time> audio_done_up_to;
-               for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-                       if ((*i)->decoder->done ()) {
-                               continue;
-                       }
-
-                       shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
-                       if (ad && ad->has_audio ()) {
-                               audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
-                       }
-               }
-
-               if (audio_done_up_to) {
-                       TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
-                       Audio (tb.audio, tb.time);
-                       _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
-               }
-       }
-               
-       return false;
-}
-
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
-void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
-{
-       /* Keep a note of what came in so that we can repeat it if required */
-       _last_incoming_video.weak_piece = weak_piece;
-       _last_incoming_video.image = image;
-       _last_incoming_video.eyes = eyes;
-       _last_incoming_video.same = same;
-       _last_incoming_video.frame = frame;
-       _last_incoming_video.extra = extra;
-       
-       shared_ptr<Piece> piece = weak_piece.lock ();
-       if (!piece) {
-               return;
-       }
-
-       shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
-       assert (content);
-
-       FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
-       if (frc.skip && (frame % 2) == 1) {
-               return;
-       }
-
-       Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
-       if (content->trimmed (relative_time)) {
-               return;
-       }
-
-       Time const time = content->position() + relative_time + extra - content->trim_start ();
-       libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
-
-       shared_ptr<PlayerImage> pi (
-               new PlayerImage (
-                       image,
-                       content->crop(),
-                       image_size,
-                       _video_container_size,
-                       _film->scaler()
-                       )
-               );
-       
-       if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
-
-               Position<int> const container_offset (
-                       (_video_container_size.width - image_size.width) / 2,
-                       (_video_container_size.height - image_size.width) / 2
-                       );
-
-               pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
-       }
-               
-                                           
-#ifdef DCPOMATIC_DEBUG
-       _last_video = piece->content;
-#endif
-
-       Video (pi, eyes, content->colour_conversion(), same, time);
-
-       _last_emit_was_black = false;
-       _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
-
-       if (frc.repeat > 1 && !piece->repeating ()) {
-               piece->set_repeat (_last_incoming_video, frc.repeat - 1);
-       }
-}
-
-void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
-{
-       shared_ptr<Piece> piece = weak_piece.lock ();
-       if (!piece) {
-               return;
-       }
-
-       shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
-       assert (content);
-
-       /* Gain */
-       if (content->audio_gain() != 0) {
-               shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
-               gain->apply_gain (content->audio_gain ());
-               audio = gain;
-       }
-
-       /* Resample */
-       if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
-               shared_ptr<Resampler> r = resampler (content, true);
-               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
-               audio = ro.first;
-               frame = ro.second;
-       }
-       
-       Time const relative_time = _film->audio_frames_to_time (frame);
-
-       if (content->trimmed (relative_time)) {
-               return;
-       }
-
-       Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-       
-       /* Remap channels */
-       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
-       dcp_mapped->make_silent ();
-
-       AudioMapping map = content->audio_mapping ();
-       for (int i = 0; i < map.content_channels(); ++i) {
-               for (int j = 0; j < _film->audio_channels(); ++j) {
-                       if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
-                               dcp_mapped->accumulate_channel (
-                                       audio.get(),
-                                       i,
-                                       static_cast<libdcp::Channel> (j),
-                                       map.get (i, static_cast<libdcp::Channel> (j))
-                                       );
-                       }
-               }
-       }
-
-       audio = dcp_mapped;
-
-       /* We must cut off anything that comes before the start of all time */
-       if (time < 0) {
-               int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
-               if (frames >= audio->frames ()) {
-                       return;
-               }
-
-               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
-               trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
-
-               audio = trimmed;
-               time = 0;
-       }
-
-       _audio_merger.push (audio, time);
-       piece->audio_position += _film->audio_frames_to_time (audio->frames ());
-}
-
-void
-Player::flush ()
-{
-       TimedAudioBuffers<Time> tb = _audio_merger.flush ();
-       if (_audio && tb.audio) {
-               Audio (tb.audio, tb.time);
-               _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
-       }
-
-       while (_video && _video_position < _audio_position) {
-               emit_black ();
-       }
-
-       while (_audio && _audio_position < _video_position) {
-               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
-       }
-       
-}
-
-/** Seek so that the next pass() will yield (approximately) the requested frame.
- *  Pass accurate = true to try harder to get close to the request.
- *  @return true on error
- */
-void
-Player::seek (Time t, bool accurate)
-{
-       if (!_have_valid_pieces) {
-               setup_pieces ();
-       }
-
-       if (_pieces.empty ()) {
-               return;
-       }
-
-       for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
-               if (!vc) {
-                       continue;
-               }
-
-               /* s is the offset of t from the start position of this content */
-               Time s = t - vc->position ();
-               s = max (static_cast<Time> (0), s);
-               s = min (vc->length_after_trim(), s);
-
-               /* Hence set the piece positions to the `global' time */
-               (*i)->video_position = (*i)->audio_position = vc->position() + s;
-
-               /* And seek the decoder */
-               dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
-                       vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
-                       );
-
-               (*i)->reset_repeat ();
-       }
-
-       _video_position = _audio_position = t;
-
-       /* XXX: don't seek audio because we don't need to... */
-}
-
 void
 Player::setup_pieces ()
 {
        list<shared_ptr<Piece> > old_pieces = _pieces;
-
        _pieces.clear ();
 
        ContentList content = _playlist->content ();
-       sort (content.begin(), content.end(), ContentSorter ());
 
        for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 
                if (!(*i)->paths_valid ()) {
                        continue;
                }
+               
+               shared_ptr<Decoder> decoder;
+               optional<FrameRateChange> frc;
+
+               /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+               DCPTime best_overlap_t;
+               shared_ptr<VideoContent> best_overlap;
+               for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+                       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+                       if (!vc) {
+                               continue;
+                       }
+                       
+                       DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
+                       if (overlap > best_overlap_t) {
+                               best_overlap = vc;
+                               best_overlap_t = overlap;
+                       }
+               }
 
-               shared_ptr<Piece> piece (new Piece (*i));
-
-               /* XXX: into content? */
+               optional<FrameRateChange> best_overlap_frc;
+               if (best_overlap) {
+                       best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+               } else {
+                       /* No video overlap; e.g. if the DCP is just audio */
+                       best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+               }
 
+               /* FFmpeg */
                shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
                if (fc) {
-                       shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-                       
-                       fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                       fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
-                       fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
-                       fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
-                       piece->decoder = fd;
+                       decoder.reset (new FFmpegDecoder (fc, _film->log()));
+                       frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
                }
-               
+
+               /* ImageContent */
                shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
                if (ic) {
-                       bool reusing = false;
-                       
                        /* See if we can re-use an old ImageDecoder */
                        for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
                                shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
                                if (imd && imd->content() == ic) {
-                                       piece = *j;
-                                       reusing = true;
+                                       decoder = imd;
                                }
                        }
 
-                       if (!reusing) {
-                               shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
-                               id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                               piece->decoder = id;
+                       if (!decoder) {
+                               decoder.reset (new ImageDecoder (ic));
                        }
+
+                       frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
                }
 
+               /* SndfileContent */
                shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
                if (sc) {
-                       shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
-                       sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+                       decoder.reset (new SndfileDecoder (sc));
+                       frc = best_overlap_frc;
+               }
 
-                       piece->decoder = sd;
+               /* SubRipContent */
+               shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
+               if (rc) {
+                       decoder.reset (new SubRipDecoder (rc));
+                       frc = best_overlap_frc;
                }
 
-               _pieces.push_back (piece);
+               _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
        }
 
        _have_valid_pieces = true;
@@ -518,9 +164,12 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
        }
 
        if (
-               property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
-               property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
-               property == VideoContentProperty::VIDEO_FRAME_TYPE 
+               property == ContentProperty::POSITION ||
+               property == ContentProperty::LENGTH ||
+               property == ContentProperty::TRIM_START ||
+               property == ContentProperty::TRIM_END ||
+               property == ContentProperty::PATH ||
+               property == VideoContentProperty::VIDEO_FRAME_TYPE
                ) {
                
                _have_valid_pieces = false;
@@ -529,23 +178,13 @@ Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
        } else if (
                property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
                property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
-               property == SubtitleContentProperty::SUBTITLE_SCALE
-               ) {
-
-               update_subtitle ();
-               Changed (frequent);
-
-       } else if (
-               property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE ||
+               property == SubtitleContentProperty::SUBTITLE_SCALE ||
+               property == VideoContentProperty::VIDEO_CROP ||
+               property == VideoContentProperty::VIDEO_SCALE ||
                property == VideoContentProperty::VIDEO_FRAME_RATE
                ) {
                
                Changed (frequent);
-
-       } else if (property == ContentProperty::PATH) {
-
-               _have_valid_pieces = false;
-               Changed (frequent);
        }
 }
 
@@ -557,71 +196,12 @@ Player::playlist_changed ()
 }
 
 void
-Player::set_video_container_size (libdcp::Size s)
+Player::set_video_container_size (dcp::Size s)
 {
        _video_container_size = s;
 
-       shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
-       im->make_black ();
-       
-       _black_frame.reset (
-               new PlayerImage (
-                       im,
-                       Crop(),
-                       _video_container_size,
-                       _video_container_size,
-                       Scaler::from_id ("bicubic")
-                       )
-               );
-}
-
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
-       map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
-       if (i != _resamplers.end ()) {
-               return i->second;
-       }
-
-       if (!create) {
-               return shared_ptr<Resampler> ();
-       }
-
-       _film->log()->log (
-               String::compose (
-                       "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
-                       )
-               );
-       
-       shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
-       _resamplers[c] = r;
-       return r;
-}
-
-void
-Player::emit_black ()
-{
-#ifdef DCPOMATIC_DEBUG
-       _last_video.reset ();
-#endif
-
-       Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
-       _video_position += _film->video_frames_to_time (1);
-       _last_emit_was_black = true;
-}
-
-void
-Player::emit_silence (OutputAudioFrame most)
-{
-       if (most == 0) {
-               return;
-       }
-       
-       OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
-       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
-       silence->make_silent ();
-       Audio (silence, _audio_position);
-       _audio_position += _film->audio_frames_to_time (N);
+       _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
+       _black_image->make_black ();
 }
 
 void
@@ -637,134 +217,289 @@ Player::film_changed (Film::Property p)
        }
 }
 
-void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+list<PositionImage>
+Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs)
+{
+       list<PositionImage> all;
+       
+       for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+               if (!(*i)->image) {
+                       continue;
+               }
+
+               dcpomatic::Rect<double> in_rect = (*i)->rectangle;
+               dcp::Size scaled_size;
+               
+               in_rect.x += content->subtitle_x_offset ();
+               in_rect.y += content->subtitle_y_offset ();
+               
+               /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
+               scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
+               scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
+               
+               /* Then we need a corrective translation, consisting of two parts:
+                *
+                * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
+                *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
+                *
+                * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
+                *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
+                *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
+                *
+                * Combining these two translations gives these expressions.
+                */
+
+               all.push_back (
+                       PositionImage (
+                               (*i)->image->scale (
+                                       scaled_size,
+                                       Scaler::from_id ("bicubic"),
+                                       (*i)->image->pixel_format (),
+                                       true
+                                       ),
+                               Position<int> (
+                                       rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
+                                       rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
+                                       )
+                               )
+                       );
+       }
+
+       return all;
+}
+
+list<PositionImage>
+Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub)
 {
-       _in_subtitle.piece = weak_piece;
-       _in_subtitle.image = image;
-       _in_subtitle.rect = rect;
-       _in_subtitle.from = from;
-       _in_subtitle.to = to;
+       list<PositionImage> all;
+       for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
+               if (!(*i)->subs.empty ()) {
+                       all.push_back (render_subtitles ((*i)->subs, _video_container_size));
+               }
+       }
 
-       update_subtitle ();
+       return all;
 }
 
 void
-Player::update_subtitle ()
+Player::set_approximate_size ()
 {
-       shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
-       if (!piece) {
-               return;
+       _approximate_size = true;
+}
+
+shared_ptr<DCPVideo>
+Player::get_video (DCPTime time, bool accurate)
+{
+       if (!_have_valid_pieces) {
+               setup_pieces ();
+       }
+       
+       list<shared_ptr<Piece> > ov = overlaps<VideoContent> (time);
+       if (ov.empty ()) {
+               /* No video content at this time: return a black frame */
+               return shared_ptr<DCPVideo> (
+                       new DCPVideo (
+                               _black_image,
+                               EYES_BOTH,
+                               Crop (),
+                               _video_container_size,
+                               _video_container_size,
+                               Scaler::from_id ("bicubic"),
+                               Config::instance()->colour_conversions().front().conversion,
+                               time
+                               )
+                       );
        }
 
-       if (!_in_subtitle.image) {
-               _out_subtitle.image.reset ();
-               return;
+       /* Create a DCPVideo from the content's video at this time */
+
+       shared_ptr<Piece> piece = ov.back ();
+       shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
+       assert (decoder);
+       shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+       assert (content);
+
+       optional<ContentVideo> dec = decoder->get_video (dcp_to_content_video (piece, time), accurate);
+       assert (dec);
+
+       dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
+       if (_approximate_size) {
+               image_size.width &= ~3;
+               image_size.height &= ~3;
        }
 
-       shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
-       assert (sc);
-
-       dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
-       libdcp::Size scaled_size;
-
-       in_rect.x += sc->subtitle_x_offset ();
-       in_rect.y += sc->subtitle_y_offset ();
-
-       /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
-       scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
-       scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
-
-       /* Then we need a corrective translation, consisting of two parts:
-        *
-        * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
-        *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
-        *
-        * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
-        *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
-        *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
-        *
-        * Combining these two translations gives these expressions.
-        */
-       
-       _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
-       _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
-       
-       _out_subtitle.image = _in_subtitle.image->scale (
-               scaled_size,
-               Scaler::from_id ("bicubic"),
-               _in_subtitle.image->pixel_format (),
-               true
+       shared_ptr<DCPVideo> dcp_video (
+               new DCPVideo (
+                       dec->image,
+                       dec->eyes,
+                       content->crop (),
+                       image_size,
+                       _video_container_size,
+                       _film->scaler(),
+                       content->colour_conversion (),
+                       time
+                       )
                );
 
-       /* XXX: hack */
-       Time from = _in_subtitle.from;
-       Time to = _in_subtitle.to;
-       shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
-       if (vc) {
-               from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
-               to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
-       }
+       /* Add subtitles */
+
+       ov = overlaps<SubtitleContent> (time);
+       list<PositionImage> sub_images;
        
-       _out_subtitle.from = from + piece->content->position ();
-       _out_subtitle.to = to + piece->content->position ();
+       for (list<shared_ptr<Piece> >::const_iterator i = ov.begin(); i != ov.end(); ++i) {
+               shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
+               shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
+               ContentTime const from = dcp_to_content_subtitle (*i, time);
+               ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
+               
+               list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (from, to);
+               if (!image_subtitles.empty ()) {
+                       list<PositionImage> im = process_content_image_subtitles (
+                               subtitle_content,
+                               image_subtitles
+                               );
+
+                       copy (im.begin(), im.end(), back_inserter (sub_images));
+               }
+
+               if (_burn_subtitles) {
+                       list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (from, to);
+                       if (!text_subtitles.empty ()) {
+                               list<PositionImage> im = process_content_text_subtitles (text_subtitles);
+                               copy (im.begin(), im.end(), back_inserter (sub_images));
+                       }
+               }
+       }
+
+       if (!sub_images.empty ()) {
+               dcp_video->set_subtitle (merge (sub_images));
+       }
+
+       return dcp_video;
 }
 
-/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
- *  @return false if this could not be done.
- */
-bool
-Player::repeat_last_video ()
+shared_ptr<AudioBuffers>
+Player::get_audio (DCPTime time, DCPTime length, bool accurate)
 {
-       if (!_last_incoming_video.image || !_have_valid_pieces) {
-               return false;
+       if (!_have_valid_pieces) {
+               setup_pieces ();
        }
 
-       process_video (
-               _last_incoming_video.weak_piece,
-               _last_incoming_video.image,
-               _last_incoming_video.eyes,
-               _last_incoming_video.same,
-               _last_incoming_video.frame,
-               _last_incoming_video.extra
-               );
+       AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
+
+       shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
+       audio->make_silent ();
+       
+       list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time);
+       if (ov.empty ()) {
+               return audio;
+       }
 
-       return true;
+       for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
+
+               shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
+               assert (content);
+               shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+               assert (decoder);
+
+               AudioFrame const content_time = dcp_to_content_audio (*i, time);
+
+               /* Audio from this piece's decoder (which might be more than what we asked for) */
+               shared_ptr<ContentAudio> all = decoder->get_audio (content_time, length_frames, accurate);
+
+               /* Gain */
+               if (content->audio_gain() != 0) {
+                       shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
+                       gain->apply_gain (content->audio_gain ());
+                       all->audio = gain;
+               }
+
+               /* Remap channels */
+               shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
+               dcp_mapped->make_silent ();
+               AudioMapping map = content->audio_mapping ();
+               for (int i = 0; i < map.content_channels(); ++i) {
+                       for (int j = 0; j < _film->audio_channels(); ++j) {
+                               if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
+                                       dcp_mapped->accumulate_channel (
+                                               all->audio.get(),
+                                               i,
+                                               j,
+                                               map.get (i, static_cast<dcp::Channel> (j))
+                                               );
+                               }
+                       }
+               }
+               
+               all->audio = dcp_mapped;
+               
+               /* Delay */
+               /* XXX
+               audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+               if (audio->dcp_time < 0) {
+                       int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+                       if (frames >= audio->audio->frames ()) {
+                               return;
+                       }
+                       
+                       shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->audio->channels(), audio->audio->frames() - frames));
+                       trimmed->copy_from (audio->audio.get(), audio->audio->frames() - frames, frames, 0);
+                       
+                       audio->audio = trimmed;
+                       audio->dcp_time = 0;
+               }
+               */
+
+               audio->accumulate_frames (all->audio.get(), all->frame - content_time, 0, min (AudioFrame (all->audio->frames()), length_frames));
+       }
+
+       return audio;
 }
 
-PlayerImage::PlayerImage (
-       shared_ptr<const Image> in,
-       Crop crop,
-       libdcp::Size inter_size,
-       libdcp::Size out_size,
-       Scaler const * scaler
-       )
-       : _in (in)
-       , _crop (crop)
-       , _inter_size (inter_size)
-       , _out_size (out_size)
-       , _scaler (scaler)
+VideoFrame
+Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
 {
+       /* s is the offset of t from the start position of this content */
+       DCPTime s = t - piece->content->position ();
+       s = DCPTime (max (int64_t (0), s.get ()));
+       s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
 
+       /* Convert this to the content frame */
+       return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
 }
 
-void
-PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
+AudioFrame
+Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
 {
-       _subtitle_image = image;
-       _subtitle_position = pos;
+       /* s is the offset of t from the start position of this content */
+       DCPTime s = t - piece->content->position ();
+       s = DCPTime (max (int64_t (0), s.get ()));
+       s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
+
+       /* Convert this to the content frame */
+       return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
 }
 
-shared_ptr<Image>
-PlayerImage::image ()
+ContentTime
+Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
 {
-       shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
+       /* s is the offset of t from the start position of this content */
+       DCPTime s = t - piece->content->position ();
+       s = DCPTime (max (int64_t (0), s.get ()));
+       s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
 
-       Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
+       return ContentTime (s, piece->frc);
+}
 
-       if (_subtitle_image) {
-               out->alpha_blend (_subtitle_image, _subtitle_position);
-       }
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+       log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+       log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()));
+}
 
-       return out;
+PlayerStatistics const &
+Player::statistics () const
+{
+       return _statistics;
 }
index f7896d7d913515c6656cabd9dc9717fb1682467b..852e7243f1355546cab3a6dba2252c57bf635f86 100644 (file)
 #include "content.h"
 #include "film.h"
 #include "rect.h"
-#include "audio_merger.h"
 #include "audio_content.h"
+#include "dcpomatic_time.h"
+#include "content_subtitle.h"
+#include "position_image.h"
 
 class Job;
 class Film;
@@ -36,76 +38,74 @@ class Playlist;
 class AudioContent;
 class Piece;
 class Image;
-class Resampler;
+class DCPVideo;
+class Decoder;
 
-/** @class Player
- *  @brief A class which can `play' a Playlist; emitting its audio and video.
- */
-
-struct IncomingVideo
+class PlayerStatistics
 {
 public:
-       boost::weak_ptr<Piece> weak_piece;
-       boost::shared_ptr<const Image> image;
-       Eyes eyes;
-       bool same;
-       VideoContent::Frame frame;
-       Time extra;
+       struct Video {
+               Video ()
+                       : black (0)
+                       , repeat (0)
+                       , good (0)
+                       , skip (0)
+               {}
+               
+               int black;
+               int repeat;
+               int good;
+               int skip;
+       } video;
+
+       struct Audio {
+               Audio ()
+                       : silence (0)
+                       , good (0)
+                       , skip (0)
+               {}
+               
+               DCPTime silence;
+               int64_t good;
+               int64_t skip;
+       } audio;
+
+       void dump (boost::shared_ptr<Log>) const;
 };
 
-/** A wrapper for an Image which contains some pending operations; these may
- *  not be necessary if the receiver of the PlayerImage throws it away.
- */
-class PlayerImage
+class Piece
 {
 public:
-       PlayerImage (boost::shared_ptr<const Image>, Crop, libdcp::Size, libdcp::Size, Scaler const *);
-
-       void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
-       
-       boost::shared_ptr<Image> image ();
-
-private:
-       boost::shared_ptr<const Image> _in;
-       Crop _crop;
-       libdcp::Size _inter_size;
-       libdcp::Size _out_size;
-       Scaler const * _scaler;
-       boost::shared_ptr<const Image> _subtitle_image;
-       Position<int> _subtitle_position;
+       Piece (boost::shared_ptr<Content> c, boost::shared_ptr<Decoder> d, FrameRateChange f)
+               : content (c)
+               , decoder (d)
+               , frc (f)
+       {}
+
+       boost::shared_ptr<Content> content;
+       boost::shared_ptr<Decoder> decoder;
+       FrameRateChange frc;
 };
+
+/** @class Player
+ *  @brief A class which can `play' a Playlist.
+ */
 class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
 {
 public:
        Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
 
-       void disable_video ();
-       void disable_audio ();
+       boost::shared_ptr<DCPVideo> get_video (DCPTime time, bool accurate);
+       boost::shared_ptr<AudioBuffers> get_audio (DCPTime time, DCPTime length, bool accurate);
 
-       bool pass ();
-       void seek (Time, bool);
-
-       Time video_position () const {
-               return _video_position;
+       void set_video_container_size (dcp::Size);
+       void set_approximate_size ();
+       void set_burn_subtitles (bool burn) {
+               _burn_subtitles = burn;
        }
 
-       void set_video_container_size (libdcp::Size);
-
-       bool repeat_last_video ();
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is the eye(s) that should see this image.
-        *  Third parameter is the colour conversion that should be used for this image.
-        *  Fourth parameter is true if the image is the same as the last one that was emitted.
-        *  Fifth parameter is the time.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video;
+       PlayerStatistics const & statistics () const;
        
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
-
        /** Emitted when something has changed such that if we went back and emitted
         *  the last frame again it would look different.  This is not emitted after
         *  a seek.
@@ -118,63 +118,48 @@ private:
        friend class PlayerWrapper;
        friend class Piece;
 
-       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time);
-       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
-       void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
        void setup_pieces ();
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int, bool);
-       void do_seek (Time, bool);
        void flush ();
-       void emit_black ();
-       void emit_silence (OutputAudioFrame);
-       boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
        void film_changed (Film::Property);
-       void update_subtitle ();
-
+       std::list<PositionImage> process_content_image_subtitles (
+               boost::shared_ptr<SubtitleContent>, std::list<boost::shared_ptr<ContentImageSubtitle> >
+               );
+       std::list<PositionImage> process_content_text_subtitles (std::list<boost::shared_ptr<ContentTextSubtitle> >);
+       void update_subtitle_from_text ();
+       VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+       AudioFrame dcp_to_content_audio (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+       ContentTime dcp_to_content_subtitle (boost::shared_ptr<const Piece> piece, DCPTime t) const;
+
+       template<class C>
+       std::list<boost::shared_ptr<Piece> >
+       overlaps (DCPTime t)
+       {
+               std::list<boost::shared_ptr<Piece> > overlaps;
+               for (typename std::list<boost::shared_ptr<Piece> >::const_iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
+                       if (boost::dynamic_pointer_cast<C> ((*i)->content) && (*i)->content->position() <= t && t < (*i)->content->end()) {
+                               overlaps.push_back (*i);
+                       }
+               }
+               
+               return overlaps;
+       }
+       
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
-       
-       bool _video;
-       bool _audio;
 
        /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
        bool _have_valid_pieces;
        std::list<boost::shared_ptr<Piece> > _pieces;
 
-       /** The time after the last video that we emitted */
-       Time _video_position;
-       /** The time after the last audio that we emitted */
-       Time _audio_position;
-
-       AudioMerger<Time, AudioContent::Frame> _audio_merger;
-
-       libdcp::Size _video_container_size;
-       boost::shared_ptr<PlayerImage> _black_frame;
-       std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
-
-       struct {
-               boost::weak_ptr<Piece> piece;
-               boost::shared_ptr<Image> image;
-               dcpomatic::Rect<double> rect;
-               Time from;
-               Time to;
-       } _in_subtitle;
-
-       struct {
-               boost::shared_ptr<Image> image;
-               Position<int> position;
-               Time from;
-               Time to;
-       } _out_subtitle;
-
-#ifdef DCPOMATIC_DEBUG
-       boost::shared_ptr<Content> _last_video;
-#endif
+       dcp::Size _video_container_size;
+       boost::shared_ptr<Image> _black_image;
 
-       bool _last_emit_was_black;
+       bool _approximate_size;
+       bool _burn_subtitles;
 
-       IncomingVideo _last_incoming_video;
+       PlayerStatistics _statistics;
 
        boost::signals2::scoped_connection _playlist_changed_connection;
        boost::signals2::scoped_connection _playlist_content_changed_connection;
index a2bec83bb3266948802c14f4c9fe4436e3b4e649..1e8a3319c54ed0569f0d555902d973eb61155d81 100644 (file)
@@ -81,20 +81,20 @@ Playlist::maybe_sequence_video ()
        _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 ();
                }
        }
 
@@ -261,12 +261,12 @@ Playlist::best_dcp_frame_rate () const
        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;
@@ -286,10 +286,10 @@ Playlist::reconnect ()
        }
 }
 
-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 ());
@@ -299,6 +299,23 @@ Playlist::video_end () const
        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) {
+                       break;
+               }
+
+               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)
 {
@@ -321,7 +338,7 @@ Playlist::content () const
 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 ());
@@ -329,7 +346,7 @@ Playlist::repeat (ContentList c, int n)
                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 ();
@@ -362,7 +379,7 @@ Playlist::move_earlier (shared_ptr<Content> c)
                return;
        }
        
-       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 ());
@@ -389,7 +406,7 @@ Playlist::move_later (shared_ptr<Content> c)
                return;
        }
 
-       Time const p = (*next)->position ();
+       DCPTime const p = (*next)->position ();
        (*next)->set_position (c->position ());
        c->set_position (p + c->length_after_trim ());
        sort (_content.begin(), _content.end(), ContentSorter ());
index 394023f5c5c90e4315b1fc9d4f37d8ef394a1f5e..444eb9ae5ebb5ae1aac34932440025c92c3f8b43 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/enable_shared_from_this.hpp>
 #include "ffmpeg_content.h"
 #include "audio_mapping.h"
+#include "util.h"
 
 class Content;
 class FFmpegContent;
@@ -70,10 +71,11 @@ public:
 
        std::string video_identifier () const;
 
-       Time length () const;
+       DCPTime length () const;
        
        int best_dcp_frame_rate () const;
-       Time video_end () const;
+       DCPTime video_end () const;
+       FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
 
        void set_sequence_video (bool);
        void maybe_sequence_video ();
diff --git a/src/lib/position_image.h b/src/lib/position_image.h
new file mode 100644 (file)
index 0000000..dbd3c60
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_POSITION_IMAGE_H
+#define DCPOMATIC_POSITION_IMAGE_H
+
+#include "position.h"
+
+class Image;
+
+class PositionImage
+{
+public:
+       PositionImage () {}
+       
+       PositionImage (boost::shared_ptr<Image> i, Position<int> p)
+               : image (i)
+               , position (p)
+       {}
+       
+       boost::shared_ptr<Image> image;
+       Position<int> position;
+};
+
+#endif
index a47b2101e54a175ae983025852f9d16a7c81ca9a..275f4ef15ab5215a14b6679b6e52ed58b01b77dd 100644 (file)
@@ -17,7 +17,7 @@
 
 */
 
-#include <libdcp/types.h>
+#include <dcp/types.h>
 #include "ratio.h"
 #include "util.h"
 
index f3354f1b631c06bad6eda7073c1d75a57dc64866..cd7d0d6e4de276b138f92733b3bbcee99ad5d10a 100644 (file)
@@ -22,7 +22,7 @@
 
 #include <vector>
 #include <boost/utility.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
 
 class Ratio : public boost::noncopyable
 {
index 6f4709c088ce137b2c589df64cb69304c1b62373..1feb8ad4fed460d19a9f83439e21bdd0abcc1ea0 100644 (file)
@@ -42,6 +42,13 @@ public:
                , height (0)
        {}
 
+       Rect (Position<T> p, T w_, T h_)
+               : x (p.x)
+               , y (p.y)
+               , width (w_)
+               , height (h_)
+       {}
+
        Rect (T x_, T y_, T w_, T h_)
                : x (x_)
                , y (y_)
@@ -54,11 +61,13 @@ public:
        T width;
        T height;
 
-       Position<T> position () const {
+       Position<T> position () const
+       {
                return Position<T> (x, y);
        }
 
-       Rect<T> intersection (Rect<T> const & other) const {
+       Rect<T> intersection (Rect<T> const & other) const
+       {
                T const tx = max (x, other.x);
                T const ty = max (y, other.y);
        
@@ -69,7 +78,16 @@ public:
                        );
        }
 
-       bool contains (Position<T> p) const {
+       void extend (Rect<T> const & other)
+       {
+               x = std::min (x, other.x);
+               y = std::min (y, other.y);
+               width = std::max (x + width, other.x + other.width) - x;
+               height = std::max (y + height, other.y + other.height) - y;
+       }
+
+       bool contains (Position<T> p) const
+       {
                return (p.x >= x && p.x <= (x + width) && p.y >= y && p.y <= (y + height));
        }
 };
diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc
new file mode 100644 (file)
index 0000000..5364b8d
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cairomm/cairomm.h>
+#include <pangomm.h>
+#include "render_subtitles.h"
+#include "types.h"
+#include "image.h"
+
+using std::list;
+using std::cout;
+using std::string;
+using std::min;
+using std::max;
+using std::pair;
+using boost::shared_ptr;
+using boost::optional;
+
+static int
+calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset)
+{
+       switch (v_align) {
+       case dcp::TOP:
+               return (v_position / 100) * target_height - offset;
+       case dcp::CENTER:
+               return (0.5 + v_position / 100) * target_height - offset;
+       case dcp::BOTTOM:
+               return (1.0 - v_position / 100) * target_height - offset;
+       }
+
+       return 0;
+}
+
+PositionImage
+render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target)
+{
+       if (subtitles.empty ()) {
+               return PositionImage ();
+       }
+
+       /* Estimate height that the subtitle image needs to be */
+       optional<int> top;
+       optional<int> bottom;
+       for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+               int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
+               int const t = b - i->size() * target.height / (11 * 72);
+
+               top = min (top.get_value_or (t), t);
+               bottom = max (bottom.get_value_or (b), b);
+       }
+
+       top = top.get() - 32;
+       bottom = bottom.get() + 32;
+
+       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false));
+       image->make_black ();
+
+       Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
+               image->data()[0],
+               Cairo::FORMAT_ARGB32,
+               image->size().width,
+               image->size().height,
+               Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
+               );
+       
+       Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
+       Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
+
+       layout->set_width (image->size().width * PANGO_SCALE);
+       layout->set_alignment (Pango::ALIGN_CENTER);
+
+       context->set_line_width (1);
+
+       for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+               string f = i->font ();
+               if (f.empty ()) {
+                       f = "Arial";
+               }
+               Pango::FontDescription font (f);
+               font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
+               if (i->italic ()) {
+                       font.set_style (Pango::STYLE_ITALIC);
+               }
+               layout->set_font_description (font);
+               layout->set_text (i->text ());
+
+               /* Compute fade factor */
+               /* XXX */
+               float fade_factor = 1;
+#if 0          
+               dcp::Time now (time * 1000 / (4 * TIME_HZ));
+               dcp::Time end_fade_up = i->in() + i->fade_up_time ();
+               dcp::Time start_fade_down = i->out() - i->fade_down_time ();
+               if (now < end_fade_up) {
+                       fade_factor = (now - i->in()) / i->fade_up_time();
+               } else if (now > start_fade_down) {
+                       fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
+               }
+#endif         
+
+               layout->update_from_cairo_context (context);
+               
+               /* Work out position */
+
+               int const x = 0;
+               int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
+
+               if (i->effect() == dcp::SHADOW) {
+                       /* Drop-shadow effect */
+                       dcp::Color const ec = i->effect_color ();
+                       context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+                       context->move_to (x + 4, y + 4);
+                       layout->add_to_cairo_context (context);
+                       context->fill ();
+               }
+
+               /* The actual subtitle */
+               context->move_to (x, y);
+               dcp::Color const c = i->color ();
+               context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
+               layout->add_to_cairo_context (context);
+               context->fill ();
+
+               if (i->effect() == dcp::BORDER) {
+                       /* Border effect */
+                       context->move_to (x, y);
+                       dcp::Color ec = i->effect_color ();
+                       context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
+                       layout->add_to_cairo_context (context);
+                       context->stroke ();
+               }
+       }
+
+       return PositionImage (image, Position<int> (0, top.get ()));
+}
+
diff --git a/src/lib/render_subtitles.h b/src/lib/render_subtitles.h
new file mode 100644 (file)
index 0000000..d83dc11
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <dcp/subtitle_string.h>
+#include <dcp/util.h>
+#include "position_image.h"
+
+PositionImage render_subtitles (std::list<dcp::SubtitleString>, dcp::Size);
index d897bf562dc4a97dcf5c314a8e7242e050f93e2f..00121384dce7617ab59f0ee85ea0373a7ab0c440 100644 (file)
@@ -64,11 +64,9 @@ Resampler::~Resampler ()
        swr_free (&_swr_context);
 }
 
-pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
-Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
 {
-       AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
-               
        /* Compute the resampled frames count and add 32 for luck */
        int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
        shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
@@ -84,7 +82,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
        }
        
        resampled->set_frames (resampled_frames);
-       return make_pair (resampled, resamp_time);
+       return resampled;
 }      
 
 shared_ptr<const AudioBuffers>
index 69ec83ba93047f3493cd23005487b7845580944e..4ee11a7f0954958e69a99bf09be75196394838c3 100644 (file)
@@ -33,7 +33,7 @@ public:
        Resampler (int, int, int);
        ~Resampler ();
 
-       std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
        boost::shared_ptr<const AudioBuffers> flush ();
 
 private:       
index 1f3f61e425a0392b791453245e0fcd6b2cd7b671..bf7541c3379f1f294f735f4c130b06994b0311d8 100644 (file)
@@ -57,7 +57,7 @@ using boost::bind;
 using boost::scoped_array;
 using boost::optional;
 using boost::lexical_cast;
-using libdcp::Size;
+using dcp::Size;
 
 Server::Server (shared_ptr<Log> log, bool verbose)
        : _log (log)
@@ -85,7 +85,7 @@ Server::process (shared_ptr<Socket> socket, struct timeval& after_read, struct t
                return -1;
        }
 
-       libdcp::Size size (
+       dcp::Size size (
                xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
                );
 
index a8388ab77b24cb1441e1c19a0905e01ec775a193..2d7fa1c1cf9446e0a26cedf13db62bd911cc6df6 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -33,8 +33,6 @@ using std::cout;
 using boost::shared_ptr;
 using boost::lexical_cast;
 
-int const SndfileContentProperty::VIDEO_FRAME_RATE = 600;
-
 SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
        , AudioContent (f, p)
@@ -51,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml:
        , _audio_mapping (node->node_child ("AudioMapping"), version)
 {
        _audio_channels = node->number_child<int> ("AudioChannels");
-       _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+       _audio_length = ContentTime (node->number_child<int64_t> ("AudioLength"));
        _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
 }
 
@@ -83,7 +81,7 @@ SndfileContent::information () const
                _("%1 channels, %2kHz, %3 samples"),
                audio_channels(),
                content_audio_frame_rate() / 1000.0,
-               audio_length()
+               audio_length().frames (content_audio_frame_rate ())
                );
        
        return s.str ();
@@ -104,10 +102,7 @@ SndfileContent::examine (shared_ptr<Job> job)
        job->set_progress_unknown ();
        Content::examine (job);
 
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-
-       SndfileDecoder dec (film, shared_from_this());
+       SndfileDecoder dec (shared_from_this());
 
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -138,24 +133,17 @@ SndfileContent::as_xml (xmlpp::Node* node) const
        AudioContent::as_xml (node);
 
        node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (audio_channels ()));
-       node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length ()));
+       node->add_child("AudioLength")->add_child_text (lexical_cast<string> (audio_length().get ()));
        node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (content_audio_frame_rate ()));
        _audio_mapping.as_xml (node->add_child("AudioMapping"));
 }
 
-Time
+DCPTime
 SndfileContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-
-       float const rate = _video_frame_rate.get_value_or (film->video_frame_rate ());
-       OutputAudioFrame const len = divide_with_round (
-               audio_length() * output_audio_frame_rate() * rate,
-               content_audio_frame_rate() * film->video_frame_rate()
-               );
-       
-       return film->audio_frames_to_time (len);
+       return DCPTime (audio_length(), film->active_frame_rate_change (position ()));
 }
 
 int
@@ -178,17 +166,3 @@ SndfileContent::set_audio_mapping (AudioMapping m)
        signal_changed (AudioContentProperty::AUDIO_MAPPING);
 }
 
-float
-SndfileContent::video_frame_rate () const
-{
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               if (_video_frame_rate) {
-                       return _video_frame_rate.get ();
-               }
-       }
-
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       return film->video_frame_rate ();
-}
index a79fb99b6bd01f6497d6e3dd2d2184c2c57d5ec1..04d157131843470640dd0567b675d6521d1b116c 100644 (file)
@@ -29,12 +29,6 @@ namespace cxml {
        class Node;
 }
 
-class SndfileContentProperty
-{
-public:
-       static int const VIDEO_FRAME_RATE;
-};
-
 class SndfileContent : public AudioContent
 {
 public:
@@ -50,7 +44,7 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        /* AudioContent */
        int audio_channels () const {
@@ -58,7 +52,7 @@ public:
                return _audio_channels;
        }
        
-       AudioContent::Frame audio_length () const {
+       ContentTime audio_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_length;
        }
@@ -75,30 +69,15 @@ public:
                return _audio_mapping;
        }
 
-       void set_video_frame_rate (float r) {
-               {
-                       boost::mutex::scoped_lock lm (_mutex);
-                       _video_frame_rate = r;
-               }
-
-               signal_changed (SndfileContentProperty::VIDEO_FRAME_RATE);
-       }
-
-       float video_frame_rate () const;
-
        void set_audio_mapping (AudioMapping);
        
        static bool valid_file (boost::filesystem::path);
 
 private:
        int _audio_channels;
-       AudioContent::Frame _audio_length;
+       ContentTime _audio_length;
        int _audio_frame_rate;
        AudioMapping _audio_mapping;
-       /** Video frame rate that this audio has been prepared for,
-           if specified.
-       */
-       boost::optional<float> _video_frame_rate;
 };
 
 #endif
index f66a7c7dc9dd363f649f2c71632a9a5057d78949..c37a8447483ba49d3e39ade148d08ebfd26f6841 100644 (file)
@@ -25,7 +25,6 @@
 #include <sndfile.h>
 #include "sndfile_content.h"
 #include "sndfile_decoder.h"
-#include "film.h"
 #include "exceptions.h"
 #include "audio_buffers.h"
 
@@ -37,9 +36,8 @@ using std::min;
 using std::cout;
 using boost::shared_ptr;
 
-SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
-       : Decoder (f)
-       , AudioDecoder (f, c)
+SndfileDecoder::SndfileDecoder (shared_ptr<const SndfileContent> c)
+       : AudioDecoder (c)
        , _sndfile_content (c)
        , _deinterleave_buffer (0)
 {
@@ -66,9 +64,13 @@ SndfileDecoder::~SndfileDecoder ()
        delete[] _deinterleave_buffer;
 }
 
-void
+bool
 SndfileDecoder::pass ()
 {
+       if (_remaining == 0) {
+               return true;
+       }
+       
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
@@ -101,9 +103,11 @@ SndfileDecoder::pass ()
        }
                
        data->set_frames (this_time);
-       audio (data, _done);
+       audio (data, ContentTime::from_frames (_done, audio_frame_rate ()));
        _done += this_time;
        _remaining -= this_time;
+
+       return _remaining == 0;
 }
 
 int
@@ -112,10 +116,10 @@ SndfileDecoder::audio_channels () const
        return _info.channels;
 }
 
-AudioContent::Frame
+ContentTime
 SndfileDecoder::audio_length () const
 {
-       return _info.frames;
+       return ContentTime::from_frames (_info.frames, audio_frame_rate ());
 }
 
 int
@@ -124,8 +128,11 @@ SndfileDecoder::audio_frame_rate () const
        return _info.samplerate;
 }
 
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
 {
-       return _audio_position >= _sndfile_content->audio_length ();
+       AudioDecoder::seek (t, accurate);
+
+       _done = t.frames (audio_frame_rate ());
+       _remaining = _info.frames - _done;
 }
index 77fa6d17734da4096757dae504a759c9925e0e8b..52590ef24a3b99bc841edff4687b2a97ced69a9c 100644 (file)
@@ -26,21 +26,22 @@ class SndfileContent;
 class SndfileDecoder : public AudioDecoder
 {
 public:
-       SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+       SndfileDecoder (boost::shared_ptr<const SndfileContent> c);
        ~SndfileDecoder ();
 
-       void pass ();
-       bool done () const;
+       void seek (ContentTime, bool);
 
        int audio_channels () const;
-       AudioContent::Frame audio_length () const;
+       ContentTime audio_length () const;
        int audio_frame_rate () const;
 
 private:
+       bool pass ();
+       
        boost::shared_ptr<const SndfileContent> _sndfile_content;
        SNDFILE* _sndfile;
        SF_INFO _info;
-       AudioContent::Frame _done;
-       AudioContent::Frame _remaining;
+       int64_t _done;
+       int64_t _remaining;
        float* _deinterleave_buffer;
 };
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
new file mode 100644 (file)
index 0000000..aa4a0b5
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include "subrip.h"
+#include "subrip_content.h"
+#include "subrip_subtitle.h"
+#include "cross.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::algorithm::trim;
+
+SubRip::SubRip (shared_ptr<const SubRipContent> content)
+{
+       FILE* f = fopen_boost (content->path (0), "r");
+       if (!f) {
+               throw OpenFileError (content->path (0));
+       }
+
+       enum {
+               COUNTER,
+               METADATA,
+               CONTENT
+       } state = COUNTER;
+
+       char buffer[256];
+       int next_count = 1;
+
+       boost::optional<SubRipSubtitle> current;
+       list<string> lines;
+       
+       while (!feof (f)) {
+               fgets (buffer, sizeof (buffer), f);
+               if (feof (f)) {
+                       break;
+               }
+               
+               string line (buffer);
+               trim_right_if (line, boost::is_any_of ("\n\r"));
+               
+               switch (state) {
+               case COUNTER:
+               {
+                       int x = 0;
+                       try {
+                               x = lexical_cast<int> (line);
+                       } catch (...) {
+
+                       }
+                       
+                       if (x == next_count) {
+                               state = METADATA;
+                               ++next_count;
+                               current = SubRipSubtitle ();
+                       } else {
+                               throw SubRipError (line, _("a subtitle count"), content->path (0));
+                       }
+               }
+               break;
+               case METADATA:
+               {
+                       vector<string> p;
+                       boost::algorithm::split (p, line, boost::algorithm::is_any_of (" "));
+                       if (p.size() != 3 && p.size() != 7) {
+                               throw SubRipError (line, _("a time/position line"), content->path (0));
+                       }
+
+                       current->from = convert_time (p[0]);
+                       current->to = convert_time (p[2]);
+
+                       if (p.size() > 3) {
+                               current->x1 = convert_coordinate (p[3]);
+                               current->x2 = convert_coordinate (p[4]);
+                               current->y1 = convert_coordinate (p[5]);
+                               current->y2 = convert_coordinate (p[6]);
+                       }
+                       state = CONTENT;
+                       break;
+               }
+               case CONTENT:
+                       if (line.empty ()) {
+                               state = COUNTER;
+                               current->pieces = convert_content (lines);
+                               _subtitles.push_back (current.get ());
+                               current.reset ();
+                               lines.clear ();
+                       } else {
+                               lines.push_back (line);
+                       }
+                       break;
+               }
+       }
+
+       if (state == CONTENT) {
+               current->pieces = convert_content (lines);
+               _subtitles.push_back (current.get ());
+       }
+
+       fclose (f);
+}
+
+ContentTime
+SubRip::convert_time (string t)
+{
+       ContentTime r;
+
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 3);
+       r += ContentTime::from_seconds (lexical_cast<int> (a[0]) * 60 * 60);
+       r += ContentTime::from_seconds (lexical_cast<int> (a[1]) * 60);
+
+       vector<string> b;
+       boost::algorithm::split (b, a[2], boost::is_any_of (","));
+       r += ContentTime::from_seconds (lexical_cast<int> (b[0]));
+       r += ContentTime::from_seconds (lexical_cast<float> (b[1]) / 1000);
+
+       return r;
+}
+
+int
+SubRip::convert_coordinate (string t)
+{
+       vector<string> a;
+       boost::algorithm::split (a, t, boost::is_any_of (":"));
+       assert (a.size() == 2);
+       return lexical_cast<int> (a[1]);
+}
+
+void
+SubRip::maybe_content (list<SubRipSubtitlePiece>& pieces, SubRipSubtitlePiece& p)
+{
+       if (!p.text.empty ()) {
+               pieces.push_back (p);
+               p.text.clear ();
+       }
+}
+
+list<SubRipSubtitlePiece>
+SubRip::convert_content (list<string> t)
+{
+       list<SubRipSubtitlePiece> pieces;
+       
+       SubRipSubtitlePiece p;
+
+       enum {
+               TEXT,
+               TAG
+       } state = TEXT;
+
+       string tag;
+
+       /* XXX: missing <font> support */
+       /* XXX: nesting of tags e.g. <b>foo<i>bar<b>baz</b>fred</i>jim</b> might
+          not work, I think.
+       */
+
+       for (list<string>::const_iterator i = t.begin(); i != t.end(); ++i) {
+               for (size_t j = 0; j < i->size(); ++j) {
+                       switch (state) {
+                       case TEXT:
+                               if ((*i)[j] == '<' || (*i)[j] == '{') {
+                                       state = TAG;
+                               } else {
+                                       p.text += (*i)[j];
+                               }
+                               break;
+                       case TAG:
+                               if ((*i)[j] == '>' || (*i)[j] == '}') {
+                                       if (tag == "b") {
+                                               maybe_content (pieces, p);
+                                               p.bold = true;
+                                       } else if (tag == "/b") {
+                                               maybe_content (pieces, p);
+                                               p.bold = false;
+                                       } else if (tag == "i") {
+                                               maybe_content (pieces, p);
+                                               p.italic = true;
+                                       } else if (tag == "/i") {
+                                               maybe_content (pieces, p);
+                                               p.italic = false;
+                                       } else if (tag == "u") {
+                                               maybe_content (pieces, p);
+                                               p.underline = true;
+                                       } else if (tag == "/u") {
+                                               maybe_content (pieces, p);
+                                               p.underline = false;
+                                       }
+                                       tag.clear ();
+                                       state = TEXT;
+                               } else {
+                                       tag += (*i)[j];
+                               }
+                               break;
+                       }
+               }
+       }
+
+       maybe_content (pieces, p);
+
+       return pieces;
+}
+
+ContentTime
+SubRip::length () const
+{
+       if (_subtitles.empty ()) {
+               return ContentTime ();
+       }
+
+       return _subtitles.back().to;
+}
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
new file mode 100644 (file)
index 0000000..e7d2167
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_H
+#define DCPOMATIC_SUBRIP_H
+
+#include "subrip_subtitle.h"
+
+class SubRipContent;
+class subrip_time_test;
+class subrip_coordinate_test;
+class subrip_content_test;
+class subrip_parse_test;
+
+class SubRip
+{
+public:
+       SubRip (boost::shared_ptr<const SubRipContent>);
+
+       ContentTime length () const;
+
+protected:
+       std::vector<SubRipSubtitle> _subtitles;
+       
+private:
+       friend class subrip_time_test;
+       friend class subrip_coordinate_test;
+       friend class subrip_content_test;
+       friend class subrip_parse_test;
+       
+       static ContentTime convert_time (std::string);
+       static int convert_coordinate (std::string);
+       static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
+       static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
+};
+
+#endif
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
new file mode 100644 (file)
index 0000000..9524cf9
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subrip_content.h"
+#include "util.h"
+#include "subrip.h"
+#include "film.h"
+
+#include "i18n.h"
+
+using std::stringstream;
+using std::string;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, boost::filesystem::path path)
+       : Content (film, path)
+       , SubtitleContent (film, path)
+{
+
+}
+
+SubRipContent::SubRipContent (shared_ptr<const Film> film, shared_ptr<const cxml::Node> node, int version)
+       : Content (film, node)
+       , SubtitleContent (film, node, version)
+       , _length (node->number_child<int64_t> ("Length"))
+{
+
+}
+
+void
+SubRipContent::examine (boost::shared_ptr<Job> job)
+{
+       Content::examine (job);
+       SubRip s (shared_from_this ());
+       shared_ptr<const Film> film = _film.lock ();
+       DCPTime len (s.length (), film->active_frame_rate_change (position ()));
+
+       boost::mutex::scoped_lock lm (_mutex);
+       _length = len;
+}
+
+string
+SubRipContent::summary () const
+{
+       return path_summary() + " " + _("[subtitles]");
+}
+
+string
+SubRipContent::technical_summary () const
+{
+       return Content::technical_summary() + " - " + _("SubRip subtitles");
+}
+
+string
+SubRipContent::information () const
+{
+       
+}
+       
+void
+SubRipContent::as_xml (xmlpp::Node* node) const
+{
+       LocaleGuard lg;
+       
+       node->add_child("Type")->add_child_text ("SubRip");
+       Content::as_xml (node);
+       SubtitleContent::as_xml (node);
+       node->add_child("Length")->add_child_text (lexical_cast<string> (_length.get ()));
+}
+
+DCPTime
+SubRipContent::full_length () const
+{
+       /* XXX: this assumes that the timing of the SubRip file is appropriate
+          for the DCP's frame rate.
+       */
+       return _length;
+}
+
+string
+SubRipContent::identifier () const
+{
+       LocaleGuard lg;
+
+       stringstream s;
+       s << Content::identifier()
+         << "_" << subtitle_scale()
+         << "_" << subtitle_x_offset()
+         << "_" << subtitle_y_offset();
+
+       return s.str ();
+}
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
new file mode 100644 (file)
index 0000000..3a8380c
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+
+class SubRipContent : public SubtitleContent
+{
+public:
+       SubRipContent (boost::shared_ptr<const Film>, boost::filesystem::path);
+       SubRipContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
+
+       boost::shared_ptr<SubRipContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<SubRipContent> (Content::shared_from_this ());
+       }
+       
+       void examine (boost::shared_ptr<Job>);
+       std::string summary () const;
+       std::string technical_summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       DCPTime full_length () const;
+       std::string identifier () const;
+
+private:
+       DCPTime _length;
+};
diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc
new file mode 100644 (file)
index 0000000..013c6fa
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <dcp/subtitle_string.h>
+#include "subrip_decoder.h"
+
+using std::list;
+using boost::shared_ptr;
+
+SubRipDecoder::SubRipDecoder (shared_ptr<const SubRipContent> content)
+       : SubRip (content)
+       , _next (0)
+{
+
+}
+
+void
+SubRipDecoder::seek (ContentTime time, bool)
+{
+       _next = 0;
+       list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin();
+       while (i != _subtitles[_next].pieces.end() && _subtitles[_next].from < time) {
+               ++i;
+       }
+       
+}
+
+bool
+SubRipDecoder::pass ()
+{
+       if (_next >= _subtitles.size ()) {
+               return true;
+       }
+       
+       list<dcp::SubtitleString> out;
+       for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
+               out.push_back (
+                       dcp::SubtitleString (
+                               "Arial",
+                               i->italic,
+                               dcp::Color (255, 255, 255),
+                               72,
+                               dcp::Time (rint (_subtitles[_next].from.seconds() * 250)),
+                               dcp::Time (rint (_subtitles[_next].to.seconds() * 250)),
+                               0.9,
+                               dcp::BOTTOM,
+                               i->text,
+                               dcp::NONE,
+                               dcp::Color (255, 255, 255),
+                               0,
+                               0
+                               )
+                       );
+       }
+
+       text_subtitle (out);
+       _next++;
+       return false;
+}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
new file mode 100644 (file)
index 0000000..ca885a2
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_DECODER_H
+#define DCPOMATIC_SUBRIP_DECODER_H
+
+#include "subtitle_decoder.h"
+#include "subrip.h"
+
+class SubRipContent;
+
+class SubRipDecoder : public SubtitleDecoder, public SubRip
+{
+public:
+       SubRipDecoder (boost::shared_ptr<const SubRipContent>);
+
+protected:
+       void seek (ContentTime time, bool accurate);
+       bool pass ();
+
+private:
+       size_t _next;
+};
+
+#endif
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
new file mode 100644 (file)
index 0000000..dd46b6c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_SUBRIP_SUBTITLE_H
+#define DCPOMATIC_SUBRIP_SUBTITLE_H
+
+#include <boost/optional.hpp>
+#include <dcp/types.h>
+#include "types.h"
+#include "dcpomatic_time.h"
+
+struct SubRipSubtitlePiece
+{
+       SubRipSubtitlePiece ()
+               : bold (false)
+               , italic (false)
+               , underline (false)
+       {}
+       
+       std::string text;
+       bool bold;
+       bool italic;
+       bool underline;
+       dcp::Color color;
+};
+
+struct SubRipSubtitle
+{
+       SubRipSubtitle ()
+               : from (0)
+               , to (0)
+       {}
+       
+       ContentTime from;
+       ContentTime to;
+       boost::optional<int> x1;
+       boost::optional<int> x2;
+       boost::optional<int> y1;
+       boost::optional<int> y2;
+       std::list<SubRipSubtitlePiece> pieces;
+};
+
+#endif
index 8f88574e5e7ff53950c903b710c571fe1b151cce..4c6e601926d2ebf096450a443ee7560bda152fba 100644 (file)
@@ -26,6 +26,7 @@
 
 using std::string;
 using std::vector;
+using std::cout;
 using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::dynamic_pointer_cast;
index c06f3d718a812e9403266e3ccf91f5c494fa69b8..3daa6e431820ef5b249c1a86ceacc27cf5d85f84 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include "subtitle_decoder.h"
 
+using std::list;
 using boost::shared_ptr;
+using boost::optional;
 
-SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
-       : Decoder (f)
+SubtitleDecoder::SubtitleDecoder ()
 {
 
 }
 
-
-/** Called by subclasses when a subtitle is ready.
+/** Called by subclasses when an image subtitle is ready.
  *  Image may be 0 to say that there is no current subtitle.
  */
 void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::image_subtitle (ContentTime from, ContentTime to, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
+{
+       _decoded_image_subtitles.push_back (shared_ptr<ContentImageSubtitle> (new ContentImageSubtitle (from, to, image, rect)));
+}
+
+void
+SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s)
+{
+       _decoded_text_subtitles.push_back (shared_ptr<ContentTextSubtitle> (new ContentTextSubtitle (s)));
+}
+
+template <class T>
+list<shared_ptr<T> >
+get (list<shared_ptr<T> > const & subs, ContentTime from, ContentTime to)
+{
+       /* XXX: inefficient */
+       list<shared_ptr<T> > out;
+       for (typename list<shared_ptr<T> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+               if ((*i)->from() <= to && (*i)->to() >= from) {
+                       out.push_back (*i);
+               }
+       }
+
+       return out;
+}
+
+list<shared_ptr<ContentTextSubtitle> >
+SubtitleDecoder::get_text_subtitles (ContentTime from, ContentTime to)
+{
+       return get<ContentTextSubtitle> (_decoded_text_subtitles, from, to);
+}
+
+list<shared_ptr<ContentImageSubtitle> >
+SubtitleDecoder::get_image_subtitles (ContentTime from, ContentTime to)
 {
-       Subtitle (image, rect, from, to);
+       return get<ContentImageSubtitle> (_decoded_image_subtitles, from, to);
 }
index eeeadbd3f65fdda2f9ee46b90c9745f2ce713f8f..efa90fd92c7950280e8c900b42ccf3ce591a1b1d 100644 (file)
 
 */
 
-#include <boost/signals2.hpp>
+#ifndef DCPOMATIC_SUBTITLE_DECODER_H
+#define DCPOMATIC_SUBTITLE_DECODER_H
+
+#include <dcp/subtitle_string.h>
 #include "decoder.h"
 #include "rect.h"
 #include "types.h"
+#include "content_subtitle.h"
 
 class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
 class Image;
 
 class SubtitleDecoder : public virtual Decoder
 {
 public:
-       SubtitleDecoder (boost::shared_ptr<const Film>);
+       SubtitleDecoder ();
 
-       boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
+       std::list<boost::shared_ptr<ContentImageSubtitle> > get_image_subtitles (ContentTime from, ContentTime to);
+       std::list<boost::shared_ptr<ContentTextSubtitle> > get_text_subtitles (ContentTime from, ContentTime to);
 
 protected:
-       void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+       void image_subtitle (ContentTime from, ContentTime to, boost::shared_ptr<Image>, dcpomatic::Rect<double>);
+       void text_subtitle (std::list<dcp::SubtitleString>);
+
+       std::list<boost::shared_ptr<ContentImageSubtitle> > _decoded_image_subtitles;
+       std::list<boost::shared_ptr<ContentTextSubtitle> > _decoded_text_subtitles;
 };
+
+#endif
index 7b304cb35b48d549e683bd403ec8b5d923b0e4e9..fd4dfcef6dfc10c89b699777514377b1b47820b2 100644 (file)
@@ -103,6 +103,7 @@ TranscodeJob::status () const
        return s.str ();
 }
 
+/** @return Approximate remaining time in seconds */
 int
 TranscodeJob::remaining_time () const
 {
@@ -120,6 +121,5 @@ 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;
 }
index 1c8f7e3eb0b021d03f6c62a0076ce4cd41f56188..8146fc167928aa90e328b63d75dfd0ea89985a7c 100644 (file)
@@ -39,45 +39,34 @@ using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 
-static void
-video_proxy (weak_ptr<Encoder> encoder, shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, bool same)
-{
-       shared_ptr<Encoder> e = encoder.lock ();
-       if (e) {
-               e->process_video (image, eyes, conversion, same);
-       }
-}
-
-static void
-audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
-{
-       shared_ptr<Encoder> e = encoder.lock ();
-       if (e) {
-               e->process_audio (audio);
-       }
-}
-
 /** Construct a transcoder using a Decoder that we create and a supplied Encoder.
  *  @param f Film that we are transcoding.
  *  @param e Encoder to use.
  */
 Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
-       : _player (f->make_player ())
+       : _film (f)
+       , _player (f->make_player ())
        , _encoder (new Encoder (f, j))
        , _finishing (false)
 {
-       _player->Video.connect (bind (video_proxy, _encoder, _1, _2, _3, _4));
-       _player->Audio.connect (bind (audio_proxy, _encoder, _1));
+
 }
 
 void
 Transcoder::go ()
 {
        _encoder->process_begin ();
-       while (!_player->pass ()) {}
+
+       DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate ());
+       for (DCPTime t; t < _film->length(); t += frame) {
+               _encoder->process_video (_player->get_video (t, true));
+               _encoder->process_audio (_player->get_audio (t, frame, true));
+       }
 
        _finishing = true;
        _encoder->process_end ();
+
+       _player->statistics().dump (_film->log ());
 }
 
 float
index d7736d4e8e56dd3a1cdf7440e6a5872bb7a59db1..25b2ef90841c68a23dd077eb12d41d0c4a282969 100644 (file)
@@ -42,6 +42,7 @@ public:
        }
 
 private:
+       boost::shared_ptr<const Film> _film;
        boost::shared_ptr<Player> _player;
        boost::shared_ptr<Encoder> _encoder;
        bool _finishing;
index c255bd0d8a43acd6c80a9a5a38f4040fa0e82dca..35c7a91f9785865e1dada8d2449fa725f4149ae4 100644 (file)
@@ -23,7 +23,9 @@
 #include <vector>
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include "dcpomatic_time.h"
+#include "position.h"
 
 class Content;
 class VideoContent;
@@ -38,31 +40,29 @@ class AudioBuffers;
  */
 #define SERVER_LINK_VERSION 1
 
-typedef int64_t Time;
-#define TIME_MAX INT64_MAX
-#define TIME_HZ         ((Time) 96000)
-typedef int64_t OutputAudioFrame;
-typedef int    OutputVideoFrame;
 typedef std::vector<boost::shared_ptr<Content> > ContentList;
 typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
 typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
 typedef std::vector<boost::shared_ptr<SubtitleContent> > SubtitleContentList;
 typedef std::vector<boost::shared_ptr<FFmpegContent> > FFmpegContentList;
 
-template<class T>
+typedef int64_t VideoFrame;
+typedef int64_t AudioFrame;
+
+/* XXX -> DCPAudio */
 struct TimedAudioBuffers
 {
        TimedAudioBuffers ()
                : time (0)
        {}
        
-       TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, T t)
+       TimedAudioBuffers (boost::shared_ptr<AudioBuffers> a, DCPTime t)
                : audio (a)
                , time (t)
        {}
        
        boost::shared_ptr<AudioBuffers> audio;
-       T time;
+       DCPTime time;
 };
 
 enum VideoFrameType
@@ -102,7 +102,7 @@ struct Crop
        /** Number of pixels to remove from the bottom */
        int bottom;
 
-       libdcp::Size apply (libdcp::Size s, int minimum = 4) const {
+       dcp::Size apply (dcp::Size s, int minimum = 4) const {
                s.width -= left + right;
                s.height -= top + bottom;
 
index 7a19790eb4ec261d1131ebd5e015565324f562a6..40e9d9c2eed730795a8f8476533ae16290c403c0 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
     Copyright (C) 2000-2007 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
 #include <glib.h>
 #include <openjpeg.h>
 #include <openssl/md5.h>
+#include <pangomm/init.h>
 #include <magick/MagickCore.h>
 #include <magick/version.h>
-#include <libdcp/version.h>
-#include <libdcp/util.h>
-#include <libdcp/signer_chain.h>
-#include <libdcp/signer.h>
+#include <dcp/version.h>
+#include <dcp/util.h>
+#include <dcp/signer_chain.h>
+#include <dcp/signer.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
@@ -70,6 +71,7 @@ extern "C" {
 #include "job.h"
 #include "cross.h"
 #include "video_content.h"
+#include "rect.h"
 #ifdef DCPOMATIC_WINDOWS
 #include "stack.hpp"
 #endif
@@ -101,7 +103,7 @@ using boost::shared_ptr;
 using boost::thread;
 using boost::lexical_cast;
 using boost::optional;
-using libdcp::Size;
+using dcp::Size;
 
 static boost::thread::id ui_thread;
 static boost::filesystem::path backtrace_file;
@@ -237,24 +239,6 @@ ffmpeg_version_to_string (int v)
        return s.str ();
 }
 
-/** Return a user-readable string summarising the versions of our dependencies */
-string
-dependency_version_summary ()
-{
-       stringstream s;
-       s << N_("libopenjpeg ") << opj_version () << N_(", ")
-         << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
-         << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
-         << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
-         << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
-         << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
-         << MagickVersion << N_(", ")
-         << N_("libssh ") << ssh_version (0) << N_(", ")
-         << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
-
-       return s.str ();
-}
-
 double
 seconds (struct timeval t)
 {
@@ -341,7 +325,8 @@ dcpomatic_setup ()
 
        set_terminate (terminate);
 
-       libdcp::init ();
+       Pango::init ();
+       dcp::init ();
        
        Ratio::setup_ratios ();
        VideoContentScale::setup_scales ();
@@ -490,33 +475,6 @@ md5_digest (vector<boost::filesystem::path> files, shared_ptr<Job> job)
        return s.str ();
 }
 
-static bool
-about_equal (float a, float b)
-{
-       /* A film of F seconds at f FPS will be Ff frames;
-          Consider some delta FPS d, so if we run the same
-          film at (f + d) FPS it will last F(f + d) seconds.
-
-          Hence the difference in length over the length of the film will
-          be F(f + d) - Ff frames
-           = Ff + Fd - Ff frames
-           = Fd frames
-           = Fd/f seconds
-          So if we accept a difference of 1 frame, ie 1/f seconds, we can
-          say that
-
-          1/f = Fd/f
-       ie 1 = Fd
-       ie d = 1/F
-          So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
-          FPS error is 1/F ~= 0.0001 ~= 10-e4
-       */
-
-       return (fabs (a - b) < 1e-4);
-}
-
 /** @param An arbitrary audio frame rate.
  *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
  */
@@ -776,17 +734,6 @@ ensure_ui_thread ()
        assert (boost::this_thread::get_id() == ui_thread);
 }
 
-/** @param v Content video frame.
- *  @param audio_sample_rate Source audio sample rate.
- *  @param frames_per_second Number of video frames per second.
- *  @return Equivalent number of audio frames for `v'.
- */
-int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
-{
-       return ((int64_t) v * audio_sample_rate / frames_per_second);
-}
-
 string
 audio_channel_name (int c)
 {
@@ -814,44 +761,6 @@ audio_channel_name (int c)
        return channels[c];
 }
 
-FrameRateConversion::FrameRateConversion (float source, int dcp)
-       : skip (false)
-       , repeat (1)
-       , change_speed (false)
-{
-       if (fabs (source / 2.0 - dcp) < fabs (source - dcp)) {
-               /* The difference between source and DCP frame rate will be lower
-                  (i.e. better) if we skip.
-               */
-               skip = true;
-       } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
-               /* The difference between source and DCP frame rate would be better
-                  if we repeated each frame once; it may be better still if we
-                  repeated more than once.  Work out the required repeat.
-               */
-               repeat = round (dcp / source);
-       }
-
-       change_speed = !about_equal (source * factor(), dcp);
-
-       if (!skip && repeat == 1 && !change_speed) {
-               description = _("Content and DCP have the same rate.\n");
-       } else {
-               if (skip) {
-                       description = _("DCP will use every other frame of the content.\n");
-               } else if (repeat == 2) {
-                       description = _("Each content frame will be doubled in the DCP.\n");
-               } else if (repeat > 2) {
-                       description = String::compose (_("Each content frame will be repeated %1 more times in the DCP.\n"), repeat - 1);
-               }
-
-               if (change_speed) {
-                       float const pc = dcp * 100 / (source * factor());
-                       description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
-               }
-       }
-}
-
 LocaleGuard::LocaleGuard ()
        : _old (0)
 {
@@ -894,7 +803,7 @@ tidy_for_filename (string f)
        return t;
 }
 
-shared_ptr<const libdcp::Signer>
+shared_ptr<const dcp::Signer>
 make_signer ()
 {
        boost::filesystem::path const sd = Config::instance()->signer_chain_directory ();
@@ -914,37 +823,37 @@ make_signer ()
                if (!boost::filesystem::exists (p)) {
                        boost::filesystem::remove_all (sd);
                        boost::filesystem::create_directories (sd);
-                       libdcp::make_signer_chain (sd, openssl_path ());
+                       dcp::make_signer_chain (sd, openssl_path ());
                        break;
                }
 
                ++i;
        }
        
-       libdcp::CertificateChain chain;
+       dcp::CertificateChain chain;
 
        {
                boost::filesystem::path p (sd);
                p /= "ca.self-signed.pem";
-               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+               chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
 
        {
                boost::filesystem::path p (sd);
                p /= "intermediate.signed.pem";
-               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+               chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
 
        {
                boost::filesystem::path p (sd);
                p /= "leaf.signed.pem";
-               chain.add (shared_ptr<libdcp::Certificate> (new libdcp::Certificate (p)));
+               chain.add (shared_ptr<dcp::Certificate> (new dcp::Certificate (p)));
        }
 
        boost::filesystem::path signer_key (sd);
        signer_key /= "leaf.key";
 
-       return shared_ptr<const libdcp::Signer> (new libdcp::Signer (chain, signer_key));
+       return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
 }
 
 map<string, string>
@@ -993,14 +902,14 @@ split_get_request (string url)
        return r;
 }
 
-libdcp::Size
-fit_ratio_within (float ratio, libdcp::Size full_frame)
+dcp::Size
+fit_ratio_within (float ratio, dcp::Size full_frame)
 {
        if (ratio < full_frame.ratio ()) {
-               return libdcp::Size (rint (full_frame.height * ratio), full_frame.height);
+               return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
        }
        
-       return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
+       return dcp::Size (full_frame.width, rint (full_frame.width / ratio));
 }
 
 void *
@@ -1031,6 +940,24 @@ divide_with_round (int64_t a, int64_t b)
        }
 }
 
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
+       stringstream s;
+       s << N_("libopenjpeg ") << opj_version () << N_(", ")
+         << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+         << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+         << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+         << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+         << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+         << MagickVersion << N_(", ")
+         << N_("libssh ") << ssh_version (0) << N_(", ")
+         << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
+
+       return s.str ();
+}
+
 ScopedTemporary::ScopedTemporary ()
        : _open (0)
 {
index 0bbab83058644a1e833f60834672a52db90d933f..579b1c231bf144874af2aa6fcc78b239e419dbe3 100644 (file)
@@ -31,7 +31,8 @@
 #include <boost/asio.hpp>
 #include <boost/optional.hpp>
 #include <boost/filesystem.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
+#include <dcp/signer.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavfilter/avfilter.h>
@@ -76,44 +77,10 @@ extern bool valid_image_file (boost::filesystem::path);
 extern boost::filesystem::path mo_path ();
 #endif
 extern std::string tidy_for_filename (std::string);
-extern boost::shared_ptr<const libdcp::Signer> make_signer ();
-extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
+extern boost::shared_ptr<const dcp::Signer> make_signer ();
+extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
 extern std::string entities_to_text (std::string e);
 extern std::map<std::string, std::string> split_get_request (std::string url);
-
-struct FrameRateConversion
-{
-       FrameRateConversion (float, int);
-
-       /** @return factor by which to multiply a source frame rate
-           to get the effective rate after any skip or repeat has happened.
-       */
-       float factor () const {
-               if (skip) {
-                       return 0.5;
-               }
-
-               return repeat;
-       }
-
-       /** true to skip every other frame */
-       bool skip;
-       /** number of times to use each frame (e.g. 1 is normal, 2 means repeat each frame once, and so on) */
-       int repeat;
-       /** true if this DCP will run its video faster or slower than the source
-        *  without taking into account `repeat' nor `skip'.
-        *  (e.g. change_speed will be true if
-        *          source is 29.97fps, DCP is 30fps
-        *          source is 14.50fps, DCP is 30fps
-        *  but not if
-        *          source is 15.00fps, DCP is 30fps
-        *          source is 12.50fps, DCP is 25fps)
-        */
-       bool change_speed;
-
-       std::string description;
-};
-
 extern int dcp_audio_frame_rate (int);
 extern int stride_round_up (int, int const *, int);
 extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
@@ -164,8 +131,6 @@ private:
        int _timeout;
 };
 
-extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
-
 class LocaleGuard
 {
 public:
index b704b64477b1ceaef7208dafd5ae2e0b775fcd89..9edbc104a92d3daa77a0a19b46dd18d55317c14e 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <iomanip>
 #include <libcxml/cxml.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
 #include "video_content.h"
 #include "video_examiner.h"
 #include "compose.hpp"
@@ -61,7 +61,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
        setup_default_colour_conversion ();
 }
 
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
        : Content (f, s)
        , _video_length (len)
        , _video_frame_rate (0)
@@ -84,7 +84,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
 VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node, int version)
        : Content (f, node)
 {
-       _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+       _video_length = ContentTime (node->number_child<int64_t> ("VideoLength"));
        _video_size.width = node->number_child<int> ("VideoWidth");
        _video_size.height = node->number_child<int> ("VideoHeight");
        _video_frame_rate = node->number_child<float> ("VideoFrameRate");
@@ -155,7 +155,7 @@ void
 VideoContent::as_xml (xmlpp::Node* node) const
 {
        boost::mutex::scoped_lock lm (_mutex);
-       node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length));
+       node->add_child("VideoLength")->add_child_text (lexical_cast<string> (_video_length.get ()));
        node->add_child("VideoWidth")->add_child_text (lexical_cast<string> (_video_size.width));
        node->add_child("VideoHeight")->add_child_text (lexical_cast<string> (_video_size.height));
        node->add_child("VideoFrameRate")->add_child_text (lexical_cast<string> (_video_frame_rate));
@@ -171,14 +171,14 @@ VideoContent::as_xml (xmlpp::Node* node) const
 void
 VideoContent::setup_default_colour_conversion ()
 {
-       _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
+       _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
 }
 
 void
 VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
 {
        /* These examiner calls could call other content methods which take a lock on the mutex */
-       libdcp::Size const vs = d->video_size ();
+       dcp::Size const vs = d->video_size ();
        float const vfr = d->video_frame_rate ();
        
        {
@@ -319,14 +319,17 @@ VideoContent::technical_summary () const
 {
        return String::compose (
                "video: length %1, size %2x%3, rate %4",
-               video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate()
+               video_length_after_3d_combine().seconds(),
+               video_size().width,
+               video_size().height,
+               video_frame_rate()
                );
 }
 
-libdcp::Size
+dcp::Size
 VideoContent::video_size_after_3d_split () const
 {
-       libdcp::Size const s = video_size ();
+       dcp::Size const s = video_size ();
        switch (video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
        case VIDEO_FRAME_TYPE_3D_ALTERNATE:
@@ -334,9 +337,9 @@ VideoContent::video_size_after_3d_split () const
        case VIDEO_FRAME_TYPE_3D_RIGHT:
                return s;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
-               return libdcp::Size (s.width / 2, s.height);
+               return dcp::Size (s.width / 2, s.height);
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
-               return libdcp::Size (s.width, s.height / 2);
+               return dcp::Size (s.width, s.height / 2);
        }
 
        assert (false);
@@ -354,28 +357,21 @@ VideoContent::set_colour_conversion (ColourConversion c)
 }
 
 /** @return Video size after 3D split and crop */
-libdcp::Size
+dcp::Size
 VideoContent::video_size_after_crop () const
 {
        return crop().apply (video_size_after_3d_split ());
 }
 
 /** @param t A time offset from the start of this piece of content.
- *  @return Corresponding frame index.
+ *  @return Corresponding time with respect to the content.
  */
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+ContentTime
+VideoContent::dcp_time_to_content_time (DCPTime t) const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
-       
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
-       /* Here we are converting from time (in the DCP) to a frame number in the content.
-          Hence we need to use the DCP's frame rate and the double/skip correction, not
-          the source's rate.
-       */
-       return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+       return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
 }
 
 VideoContentScale::VideoContentScale (Ratio const * r)
@@ -452,14 +448,14 @@ VideoContentScale::name () const
 /** @param display_container Size of the container that we are displaying this content in.
  *  @param film_container The size of the film's image.
  */
-libdcp::Size
-VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
+dcp::Size
+VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
 {
        if (_ratio) {
                return fit_ratio_within (_ratio->ratio (), display_container);
        }
 
-       libdcp::Size const ac = c->video_size_after_crop ();
+       dcp::Size const ac = c->video_size_after_crop ();
 
        /* Force scale if the film_container is smaller than the content's image */
        if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
@@ -469,7 +465,7 @@ VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_
        /* Scale the image so that it will be in the right place in film_container, even if display_container is a
           different size.
        */
-       return libdcp::Size (
+       return dcp::Size (
                c->video_size().width  * float(display_container.width)  / film_container.width,
                c->video_size().height * float(display_container.height) / film_container.height
                );
index fbba9c09da21ea8b015ad441c92e446e4bb9d499..6b91997c9f4076c91ffdd9d536ee21106bc0e9d0 100644 (file)
@@ -45,7 +45,7 @@ public:
        VideoContentScale (bool);
        VideoContentScale (boost::shared_ptr<cxml::Node>);
 
-       libdcp::Size size (boost::shared_ptr<const VideoContent>, libdcp::Size, libdcp::Size) const;
+       dcp::Size size (boost::shared_ptr<const VideoContent>, dcp::Size, dcp::Size) const;
        std::string id () const;
        std::string name () const;
        void as_xml (xmlpp::Node *) const;
@@ -81,7 +81,7 @@ public:
        typedef int Frame;
 
        VideoContent (boost::shared_ptr<const Film>);
-       VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+       VideoContent (boost::shared_ptr<const Film>, DCPTime, ContentTime);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>, int);
        VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -91,21 +91,21 @@ public:
        virtual std::string information () const;
        virtual std::string identifier () const;
 
-       VideoContent::Frame video_length () const {
+       ContentTime video_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_length;
        }
 
-       VideoContent::Frame video_length_after_3d_combine () const {
+       ContentTime video_length_after_3d_combine () const {
                boost::mutex::scoped_lock lm (_mutex);
                if (_video_frame_type == VIDEO_FRAME_TYPE_3D_ALTERNATE) {
-                       return _video_length / 2;
+                       return ContentTime (_video_length.get() / 2);
                }
                
                return _video_length;
        }
 
-       libdcp::Size video_size () const {
+       dcp::Size video_size () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_size;
        }
@@ -166,15 +166,15 @@ public:
                return _colour_conversion;
        }
 
-       libdcp::Size video_size_after_3d_split () const;
-       libdcp::Size video_size_after_crop () const;
+       dcp::Size video_size_after_3d_split () const;
+       dcp::Size video_size_after_crop () const;
 
-       VideoContent::Frame time_to_content_video_frames (Time) const;
+       ContentTime dcp_time_to_content_time (DCPTime) const;
 
 protected:
        void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
 
-       VideoContent::Frame _video_length;
+       ContentTime _video_length;
        float _video_frame_rate;
 
 private:
@@ -185,7 +185,7 @@ private:
 
        void setup_default_colour_conversion ();
        
-       libdcp::Size _video_size;
+       dcp::Size _video_size;
        VideoFrameType _video_frame_type;
        Crop _crop;
        VideoContentScale _scale;
index 2a33a8c3a260d78bc2697f80f217e78d635ff1ff..6a7a62b742bad5655beb6915db7de5ec0dff60b1 100644 (file)
 
 #include "video_decoder.h"
 #include "image.h"
+#include "content_video.h"
 
 #include "i18n.h"
 
 using std::cout;
+using std::list;
 using boost::shared_ptr;
+using boost::optional;
 
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
-       : Decoder (f)
-       , _video_content (c)
-       , _video_position (0)
+VideoDecoder::VideoDecoder (shared_ptr<const VideoContent> c)
+       : _video_content (c)
 {
 
 }
 
+optional<ContentVideo>
+VideoDecoder::decoded_video (VideoFrame frame)
+{
+       for (list<ContentVideo>::const_iterator i = _decoded_video.begin(); i != _decoded_video.end(); ++i) {
+               if (i->frame == frame) {
+                       return *i;
+               }
+       }
+
+       return optional<ContentVideo> ();
+}
+
+optional<ContentVideo>
+VideoDecoder::get_video (VideoFrame frame, bool accurate)
+{
+       if (_decoded_video.empty() || (frame < _decoded_video.front().frame || frame > (_decoded_video.back().frame + 1))) {
+               /* 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, _video_content->video_frame_rate()), accurate);
+       }
+
+       optional<ContentVideo> dec;
+
+       /* Now enough pass() calls will either:
+        *  (a) give us what we want, or
+        *  (b) hit the end of the decoder.
+        */
+       if (accurate) {
+               /* We are being accurate, so we want the right frame */
+               while (!decoded_video (frame) && !pass ()) {}
+               dec = decoded_video (frame);
+       } else {
+               /* Any frame will do: use the first one that comes out of pass() */
+               while (_decoded_video.empty() && !pass ()) {}
+               if (!_decoded_video.empty ()) {
+                       dec = _decoded_video.front ();
+               }
+       }
+
+       /* Clean up decoded_video */
+       while (!_decoded_video.empty() && _decoded_video.front().frame < (frame - 1)) {
+               _decoded_video.pop_front ();
+       }
+
+       return dec;
+}
+
+
+/** Called by subclasses when they have a video frame ready */
 void
-VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const Image> image, VideoFrame frame)
 {
+       /* Fill in gaps */
+       /* XXX: 3D */
+       while (!_decoded_video.empty () && (_decoded_video.back().frame + 1) < frame) {
+               _decoded_video.push_back (
+                       ContentVideo (
+                               _decoded_video.back().image,
+                               _decoded_video.back().eyes,
+                               _decoded_video.back().frame + 1
+                               )
+                       );
+       }
+       
        switch (_video_content->video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
-               Video (image, EYES_BOTH, same, frame);
+               _decoded_video.push_back (ContentVideo (image, EYES_BOTH, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_ALTERNATE:
-               Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, same, frame / 2);
+               _decoded_video.push_back (ContentVideo (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
        {
                int const half = image->size().width / 2;
-               Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
-               Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+               _decoded_video.push_back (ContentVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, frame));
+               _decoded_video.push_back (ContentVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, frame));
                break;
        }
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
        {
                int const half = image->size().height / 2;
-               Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
-               Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
+               _decoded_video.push_back (ContentVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, frame));
+               _decoded_video.push_back (ContentVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, frame));
                break;
        }
        case VIDEO_FRAME_TYPE_3D_LEFT:
-               Video (image, EYES_LEFT, same, frame);
+               _decoded_video.push_back (ContentVideo (image, EYES_LEFT, frame));
                break;
        case VIDEO_FRAME_TYPE_3D_RIGHT:
-               Video (image, EYES_RIGHT, same, frame);
+               _decoded_video.push_back (ContentVideo (image, EYES_RIGHT, frame));
                break;
+       default:
+               assert (false);
        }
-       
-       _video_position = frame + 1;
+}
+
+void
+VideoDecoder::seek (ContentTime, bool)
+{
+       _decoded_video.clear ();
 }
 
index 255a038a9073f75be2a1c268e11aaf709642072b..86a9489b111338241dc5907cb7d8f9ad0e685229 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -25,6 +25,7 @@
 #include "decoder.h"
 #include "video_content.h"
 #include "util.h"
+#include "content_video.h"
 
 class VideoContent;
 class Image;
@@ -32,29 +33,22 @@ class Image;
 class VideoDecoder : public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
-
-       /** Seek so that the next pass() will yield (approximately) the requested frame.
-        *  Pass accurate = true to try harder to get close to the request.
-        */
-       virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is the eye(s) which should see this image.
-        *  Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
-        *  Fourth parameter is the frame within our source.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
-       
+       VideoDecoder (boost::shared_ptr<const VideoContent> c);
+
+       boost::optional<ContentVideo> get_video (VideoFrame frame, bool accurate);
+
+       boost::shared_ptr<const VideoContent> video_content () const {
+               return _video_content;
+       }
+
 protected:
 
-       void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       void seek (ContentTime time, bool accurate);
+       void video (boost::shared_ptr<const Image>, VideoFrame frame);
+       boost::optional<ContentVideo> decoded_video (VideoFrame frame);
+
        boost::shared_ptr<const VideoContent> _video_content;
-       /** This is in frames without taking 3D into account (e.g. if we are doing 3D alternate,
-        *  this would equal 2 on the left-eye second frame (not 1)).
-        */
-       VideoContent::Frame _video_position;
+       std::list<ContentVideo> _decoded_video;
 };
 
 #endif
index 039c494b52fe98f630798729207b65b26b4d46f3..d4897213beb69634d1b777b955e4d0b9efa9a44b 100644 (file)
@@ -17,7 +17,7 @@
 
 */
 
-#include <libdcp/types.h>
+#include <dcp/types.h>
 #include "types.h"
 #include "video_content.h"
 
@@ -26,6 +26,6 @@ class VideoExaminer
 public:
        virtual ~VideoExaminer () {}
        virtual float video_frame_rate () const = 0;
-       virtual libdcp::Size video_size () const = 0;
-       virtual VideoContent::Frame video_length () const = 0;
+       virtual dcp::Size video_size () const = 0;
+       virtual ContentTime video_length () const = 0;
 };
index 6396851492be8fc2322b13b7d43387bfc81adf2e..8508a07163218a8e16b446640007e9431956fc18 100644 (file)
 
 #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/dcp.h>
+#include <dcp/cpl.h>
 #include "writer.h"
 #include "compose.hpp"
 #include "film.h"
@@ -37,6 +41,7 @@
 #include "config.h"
 #include "job.h"
 #include "cross.h"
+#include "audio_buffers.h"
 
 #include "i18n.h"
 
@@ -51,6 +56,7 @@ 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;
 
@@ -65,7 +71,6 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
        , _last_written_eyes (EYES_RIGHT)
        , _full_written (0)
        , _fake_written (0)
-       , _repeat_written (0)
        , _pushed_to_disk (0)
 {
        /* Remove any old DCP */
@@ -83,36 +88,34 @@ Writer::Writer (shared_ptr<const Film> f, weak_ptr<Job> j)
        */
 
        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);
        }
 
        _thread = new boost::thread (boost::bind (&Writer::thread, this));
@@ -166,7 +169,7 @@ Writer::fake_write (int frame, Eyes eyes)
        }
        
        FILE* ifi = fopen_boost (_film->info_path (frame, eyes), "r");
-       libdcp::FrameInfo info (ifi);
+       dcp::FrameInfo info (ifi);
        fclose (ifi);
        
        QueueItem qi;
@@ -190,8 +193,8 @@ Writer::fake_write (int frame, Eyes eyes)
 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());
        }
 }
 
@@ -265,7 +268,7 @@ try
                                        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;
@@ -273,39 +276,27 @@ try
                        }
                        case QueueItem::FAKE:
                                _film->log()->log (String::compose (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:
-                       {
-                               _film->log()->log (String::compose (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);
                        }
                }
 
@@ -380,15 +371,11 @@ Writer::finish ()
        
        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();
@@ -406,14 +393,9 @@ Writer::finish ()
                _film->log()->log ("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 ());
-
        /* 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 ();
@@ -424,78 +406,55 @@ Writer::finish ()
                                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);
        }
-       
-       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 ());
 
-       cpl->add_reel (shared_ptr<libdcp::Reel> (new libdcp::Reel (
-                                                        _picture_asset,
-                                                        _sound_asset,
-                                                        shared_ptr<libdcp::SubtitleAsset> ()
-                                                        )
-                              ));
+       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)));
+       }
+
+       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)));
+       }
+
+       reel->add (shared_ptr<dcp::ReelSoundAsset> (new dcp::ReelSoundAsset (_sound_mxf, 0)));
+       
+       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> ());
+       dcp.write_xml (_film->interop () ? dcp::INTEROP : dcp::SMPTE, meta, _film->is_signed() ? make_signer () : shared_ptr<const dcp::Signer> ());
 
        _film->log()->log (
-               String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT; %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk)
+               String::compose (N_("Wrote %1 FULL, %2 FAKE, %3 pushed to disk"), _full_written, _fake_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) {
-               _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);
-       }
-
-       _empty_condition.notify_all ();
-}
-
 bool
 Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
 {
@@ -506,7 +465,7 @@ Writer::check_existing_picture_mxf_frame (FILE* mxf, int f, Eyes eyes)
                return false;
        }
        
-       libdcp::FrameInfo info (ifi);
+       dcp::FrameInfo info (ifi);
        fclose (ifi);
        if (info.size == 0) {
                _film->log()->log (String::compose ("Existing frame %1 has no info file", f));
index 7af79a417ed193780931e805eda92d5ca1d58515..e817b1ca205beadc9d7ebbfe256ba2d04183a963 100644 (file)
 
 */
 
+/** @file  src/lib/writer.h
+ *  @brief Writer class.
+ */
+
 #include <list>
 #include <boost/shared_ptr.hpp>
 #include <boost/thread.hpp>
@@ -29,15 +33,15 @@ class EncodedData;
 class AudioBuffers;
 class Job;
 
-namespace libdcp {
-       class MonoPictureAsset;
-       class MonoPictureAssetWriter;
-       class StereoPictureAsset;
-       class StereoPictureAssetWriter;
-       class PictureAsset;
-       class PictureAssetWriter;
-       class SoundAsset;
-       class SoundAssetWriter;
+namespace dcp {
+       class MonoPictureMXF;
+       class MonoPictureMXFWriter;
+       class StereoPictureMXF;
+       class StereoPictureMXFWriter;
+       class PictureMXF;
+       class PictureMXFWriter;
+       class SoundMXF;
+       class SoundMXFWriter;
 }
 
 struct QueueItem
@@ -51,8 +55,6 @@ public:
                    state but we use the data that is already on disk.
                */
                FAKE,
-               /** this is a repeat of the last frame to be written */
-               REPEAT
        } type;
 
        /** encoded data for FULL */
@@ -67,6 +69,17 @@ public:
 bool operator< (QueueItem const & a, QueueItem const & b);
 bool operator== (QueueItem const & a, QueueItem const & b);
 
+/** @class Writer
+ *  @brief Class to manage writing JPEG2000 and audio data to MXFs on disk.
+ *
+ *  This class creates sound and picture MXFs, then takes EncodedData
+ *  or AudioBuffers objects (containing image or sound data respectively)
+ *  and writes them to the MXFs.
+ *
+ *  ::write() for EncodedData can be called out of order, and the Writer
+ *  will sort it out.  write() for AudioBuffers must be called in order.
+ */
+
 class Writer : public ExceptionStore, public boost::noncopyable
 {
 public:
@@ -123,15 +136,13 @@ private:
        int _full_written;
        /** number of FAKE written frames */
        int _fake_written;
-       /** number of REPEAT written frames */
-       int _repeat_written;
        /** number of frames pushed to disk and then recovered
            due to the limit of frames to be held in memory.
        */
        int _pushed_to_disk;
        
-       boost::shared_ptr<libdcp::PictureAsset> _picture_asset;
-       boost::shared_ptr<libdcp::PictureAssetWriter> _picture_asset_writer;
-       boost::shared_ptr<libdcp::SoundAsset> _sound_asset;
-       boost::shared_ptr<libdcp::SoundAssetWriter> _sound_asset_writer;
+       boost::shared_ptr<dcp::PictureMXF> _picture_mxf;
+       boost::shared_ptr<dcp::PictureMXFWriter> _picture_mxf_writer;
+       boost::shared_ptr<dcp::SoundMXF> _sound_mxf;
+       boost::shared_ptr<dcp::SoundMXFWriter> _sound_mxf_writer;
 };
index a50216f6d500755cf338c00fd8308ca075215633..433f50b3fdca4565902baf3b4b7e7246a59514d4 100644 (file)
@@ -13,11 +13,13 @@ sources = """
           config.cc
           content.cc
           content_factory.cc
+          content_subtitle.cc
           cross.cc
           dci_metadata.cc
           dcp_content_type.cc
+          dcp_video.cc
           dcp_video_frame.cc
-          decoder.cc
+          dcpomatic_time.cc
           dolby_cp750.cc
           encoder.cc
           examine_content_job.cc
@@ -30,6 +32,7 @@ sources = """
           ffmpeg_examiner.cc
           film.cc
           filter.cc
+          frame_rate_change.cc
           internet.cc
           image.cc
           image_content.cc
@@ -43,6 +46,7 @@ sources = """
           player.cc
           playlist.cc
           ratio.cc
+          render_subtitles.cc
           resampler.cc
           scp_dcp_job.cc
           scaler.cc
@@ -52,6 +56,9 @@ sources = """
           sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
+          subrip.cc
+          subrip_content.cc
+          subrip_decoder.cc
           subtitle_content.cc
           subtitle_decoder.cc
           timer.cc
@@ -78,7 +85,7 @@ def build(bld):
                  AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                  BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2
                  SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++
-                 CURL ZIP QUICKMAIL
+                 CURL ZIP QUICKMAIL PANGOMM CAIROMM
                  """
 
     if bld.env.TARGET_OSX:
index a74ece149af2b9e96e7cd46103917dec5bfe98f1..d95b2ed99f63fac0a2c0e923c4272bedf1fa69ae 100644 (file)
@@ -20,7 +20,7 @@
 #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"
index a49780276b1a0a6d8105850246bb792908eea832..c75f8f9539ffe9f06d9b4d61936304ae038a7301 100644 (file)
@@ -190,7 +190,7 @@ main (int argc, char* argv[])
                for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
                        shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (*i);
                        if (ic) {
-                               ic->set_video_length (still_length * 24);
+                               ic->set_video_length (ContentTime::from_seconds (still_length));
                        }
                }
 
index 3a2068d2bb4c45b848ce864f58cfdd1a673d54ff..2f6916df2be684ae5569e30a73af3922eaba3c72 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -18,7 +18,7 @@
 */
 
 #include <getopt.h>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
 #include "lib/film.h"
 #include "lib/cinema.h"
 #include "lib/kdm.h"
@@ -40,8 +40,8 @@ help ()
        cerr << "Syntax: " << program_name << " [OPTION] [<FILM>]\n"
                "  -h, --help             show this help\n"
                "  -o, --output           output file or directory\n"
-               "  -f, --valid-from       valid from time (e.g. \"2013-09-28 01:41:51\") or \"now\"\n"
-               "  -t, --valid-to         valid to time (e.g. \"2014-09-28 01:41:51\")\n"
+               "  -f, --valid-from       valid from time (in local time zone) (e.g. \"2013-09-28 01:41:51\") or \"now\"\n"
+               "  -t, --valid-to         valid to time (in local time zone) (e.g. \"2014-09-28 01:41:51\")\n"
                "  -d, --valid-duration   valid duration (e.g. \"1 day\", \"4 hours\", \"2 weeks\")\n"
                "  -z, --zip              ZIP each cinema's KDMs into its own file\n"
                "  -v, --verbose          be verbose\n"
@@ -234,8 +234,8 @@ int main (int argc, char* argv[])
                        error ("you must specify --output");
                }
                
-               shared_ptr<libdcp::Certificate> certificate (new libdcp::Certificate (boost::filesystem::path (certificate_file)));
-               libdcp::KDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get());
+               shared_ptr<dcp::Certificate> certificate (new dcp::Certificate (boost::filesystem::path (certificate_file)));
+               dcp::EncryptedKDM kdm = film->make_kdm (certificate, dcp, valid_from.get(), valid_to.get());
                kdm.as_xml (output);
                if (verbose) {
                        cout << "Generated KDM " << output << " for certificate.\n";
@@ -259,12 +259,12 @@ int main (int argc, char* argv[])
 
                try {
                        if (zip) {
-                               write_kdm_zip_files (film, (*i)->screens(), dcp, valid_from.get(), valid_to.get(), output);
+                               write_kdm_zip_files (film, (*i)->screens(), dcp, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
                                if (verbose) {
                                        cout << "Wrote ZIP files to " << output << "\n";
                                }
                        } else {
-                               write_kdm_files (film, (*i)->screens(), dcp, valid_from.get(), valid_to.get(), output);
+                               write_kdm_files (film, (*i)->screens(), dcp, dcp::LocalTime (valid_from.get()), dcp::LocalTime (valid_to.get()), output);
                                if (verbose) {
                                        cout << "Wrote KDM files to " << output << "\n";
                                }
index 039088862ef8bf7801af1d76da482ff85c425373..ad3cf94819be44dff7dd0a0c3ed49df9390473e8 100644 (file)
@@ -34,6 +34,7 @@
 #include "lib/log.h"
 #include "lib/video_decoder.h"
 #include "lib/player.h"
+#include "lib/dcp_video.h"
 
 using std::cout;
 using std::cerr;
@@ -44,18 +45,27 @@ using boost::shared_ptr;
 static shared_ptr<Film> film;
 static ServerDescription* server;
 static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log"));
-static int frame = 0;
+static int frame_count = 0;
 
 void
-process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, Time)
+process_video (shared_ptr<DCPVideo> frame)
 {
-       shared_ptr<DCPVideoFrame> local  (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
-       shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
+       shared_ptr<DCPVideoFrame> local  (
+               new DCPVideoFrame (
+                       frame->image (PIX_FMT_RGB24, false), frame_count, frame->eyes(), frame->conversion(), film->video_frame_rate(), 250000000, RESOLUTION_2K, log_
+                       )
+               );
+       
+       shared_ptr<DCPVideoFrame> remote (
+               new DCPVideoFrame (
+                       frame->image (PIX_FMT_RGB24, false), frame_count, frame->eyes(), frame->conversion(), film->video_frame_rate(), 250000000, RESOLUTION_2K, log_
+                       )
+               );
 
-       cout << "Frame " << frame << ": ";
+       cout << "Frame " << frame_count << ": ";
        cout.flush ();
 
-       ++frame;
+       ++frame_count;
 
        shared_ptr<EncodedData> local_encoded = local->encode_locally ();
        shared_ptr<EncodedData> remote_encoded;
@@ -143,12 +153,10 @@ main (int argc, char* argv[])
                film->read_metadata ();
                
                shared_ptr<Player> player = film->make_player ();
-               player->disable_audio ();
 
-               player->Video.connect (boost::bind (process_video, _1, _2, _3, _5));
-               bool done = false;
-               while (!done) {
-                       done = player->pass ();
+               DCPTime const frame = DCPTime::from_frames (1, film->video_frame_rate ());
+               for (DCPTime t; t < film->length(); t += frame) {
+                       process_video (player->get_video (t, true));
                }
        } catch (std::exception& e) {
                cerr << "Error: " << e.what() << "\n";
index 7fdecb8d52a8dc9d3cee8ff54e8781b68b3ca422..52303ac14b2e9b94996bbf8950f42e3bf4458abc 100644 (file)
@@ -20,7 +20,7 @@
 #include <wx/wx.h>
 #include <wx/renderer.h>
 #include <wx/grid.h>
-#include <libdcp/types.h>
+#include <dcp/types.h>
 #include "lib/audio_mapping.h"
 #include "lib/util.h"
 #include "audio_mapping_view.h"
@@ -156,7 +156,7 @@ AudioMappingView::left_click (wxGridEvent& ev)
                return;
        }
 
-       libdcp::Channel d = static_cast<libdcp::Channel> (ev.GetCol() - 1);
+       dcp::Channel d = static_cast<dcp::Channel> (ev.GetCol() - 1);
        
        if (_map.get (ev.GetRow(), d) > 0) {
                _map.set (ev.GetRow(), d, 0);
@@ -182,28 +182,28 @@ AudioMappingView::right_click (wxGridEvent& ev)
 void
 AudioMappingView::off ()
 {
-       _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 0);
+       _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 0);
        map_changed ();
 }
 
 void
 AudioMappingView::full ()
 {
-       _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1);
+       _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1);
        map_changed ();
 }
 
 void
 AudioMappingView::minus3dB ()
 {
-       _map.set (_menu_row, static_cast<libdcp::Channel> (_menu_column - 1), 1 / sqrt (2));
+       _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1 / sqrt (2));
        map_changed ();
 }
 
 void
 AudioMappingView::edit ()
 {
-       libdcp::Channel d = static_cast<libdcp::Channel> (_menu_column - 1);
+       dcp::Channel d = static_cast<dcp::Channel> (_menu_column - 1);
        
        AudioGainDialog* dialog = new AudioGainDialog (this, _menu_row, _menu_column - 1, _map.get (_menu_row, d));
        if (dialog->ShowModal () == wxID_OK) {
@@ -242,7 +242,7 @@ AudioMappingView::update_cells ()
                _grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1));
 
                for (int j = 1; j < _grid->GetNumberCols(); ++j) {
-                       _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<libdcp::Channel> (j - 1)))));
+                       _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
                }
        }
 
@@ -346,7 +346,7 @@ AudioMappingView::mouse_moved (wxMouseEvent& ev)
        if (row != _last_tooltip_row || column != _last_tooltip_column) {
 
                wxString s;
-               float const gain = _map.get (row, static_cast<libdcp::Channel> (column - 1));
+               float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1));
                if (gain == 0) {
                        s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column);
                } else if (gain == 1) {
index e7133a534a7dc5cc808066ebce4dbaba5eb5dcf0..799067920d0fbed4aad634dc484eb4c53ce1645b 100644 (file)
@@ -28,7 +28,7 @@
 #include <wx/preferences.h>
 #include <wx/filepicker.h>
 #include <wx/spinctrl.h>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
 #include "lib/config.h"
 #include "lib/ratio.h"
 #include "lib/scaler.h"
@@ -460,14 +460,14 @@ private:
 
        void issuer_changed ()
        {
-               libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.issuer = wx_to_std (_issuer->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
        
        void creator_changed ()
        {
-               libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
+               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
                m.creator = wx_to_std (_creator->GetValue ());
                Config::instance()->set_dcp_metadata (m);
        }
index 31b9b8368b0097e006c974c20aef437fa4db1111..7fa34c516c60875e0208b0f907adf6d332078b94 100644 (file)
@@ -849,7 +849,7 @@ FilmEditor::setup_content_sensitivity ()
 
        _video_panel->Enable    (video_selection.size() > 0 && _generally_sensitive);
        _audio_panel->Enable    (audio_selection.size() > 0 && _generally_sensitive);
-       _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
+       _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive);
        _timing_panel->Enable   (selection.size() == 1 && _generally_sensitive);
 }
 
index ce5eab00e1db8e8f0bd4bb7e707fe470e59bc7cb..33af202bc7164c31efd650fd8c854ad30b697f16 100644 (file)
@@ -36,6 +36,8 @@
 #include "lib/player.h"
 #include "lib/video_content.h"
 #include "lib/video_decoder.h"
+#include "lib/timer.h"
+#include "lib/dcp_video.h"
 #include "film_viewer.h"
 #include "wx_util.h"
 
@@ -50,7 +52,7 @@ using std::make_pair;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
 using boost::weak_ptr;
-using libdcp::Size;
+using dcp::Size;
 
 FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        : wxPanel (p)
@@ -61,7 +63,6 @@ FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
        , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
        , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
        , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
-       , _got_frame (false)
 {
 #ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
@@ -121,7 +122,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
        _frame.reset ();
        
        _slider->SetValue (0);
-       set_position_text (0);
+       set_position_text ();
        
        if (!_film) {
                return;
@@ -135,30 +136,31 @@ FilmViewer::set_film (shared_ptr<Film> f)
                return;
        }
        
-       _player->disable_audio ();
-       _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5));
+       _player->set_approximate_size ();
        _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 
        calculate_sizes ();
-       fetch_next_frame ();
+       get (_position, true);
 }
 
 void
-FilmViewer::fetch_current_frame_again ()
+FilmViewer::get (DCPTime p, bool accurate)
 {
        if (!_player) {
                return;
        }
 
-       /* We could do this with a seek and a fetch_next_frame, but this is
-          a shortcut to make it quicker.
-       */
-
-       _got_frame = false;
-       if (!_player->repeat_last_video ()) {
-               fetch_next_frame ();
+       shared_ptr<DCPVideo> dcp_video = _player->get_video (p, accurate);
+       if (dcp_video) {
+               _frame = dcp_video->image (PIX_FMT_BGRA, true);
+               _frame = _frame->scale (_frame->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
+       } else {
+               _frame.reset ();
        }
+
+       _position = p;
        
+       set_position_text ();
        _panel->Refresh ();
        _panel->Update ();
 }
@@ -166,16 +168,12 @@ FilmViewer::fetch_current_frame_again ()
 void
 FilmViewer::timer ()
 {
-       if (!_player) {
-               return;
-       }
-       
-       fetch_next_frame ();
+       get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true);
 
-       Time const len = _film->length ();
+       DCPTime const len = _film->length ();
 
-       if (len) {
-               int const new_slider_position = 4096 * _player->video_position() / len;
+       if (len.get ()) {
+               int const new_slider_position = 4096 * _position.get() / len.get();
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@ -218,15 +216,16 @@ FilmViewer::paint_panel ()
 void
 FilmViewer::slider_moved ()
 {
-       if (_film && _player) {
-               Time t = _slider->GetValue() * _film->length() / 4096;
-               /* Ensure that we hit the end of the film at the end of the slider */
-               if (t >= _film->length ()) {
-                       t = _film->length() - _film->video_frames_to_time (1);
-               }
-               _player->seek (t, false);
-               fetch_next_frame ();
+       if (!_film) {
+               return;
+       }
+
+       DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
+       /* Ensure that we hit the end of the film at the end of the slider */
+       if (t >= _film->length ()) {
+               t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ());
        }
+       get (t, false);
 }
 
 void
@@ -235,7 +234,7 @@ FilmViewer::panel_sized (wxSizeEvent& ev)
        _panel_size.width = ev.GetSize().GetWidth();
        _panel_size.height = ev.GetSize().GetHeight();
        calculate_sizes ();
-       fetch_current_frame_again ();
+       get (_position, true);
 }
 
 void
@@ -264,6 +263,13 @@ FilmViewer::calculate_sizes ()
        _out_size.width = max (64, _out_size.width);
        _out_size.height = max (64, _out_size.height);
 
+       /* The player will round its image down to the nearest 4 pixels
+          to speed up its scale, so do similar here to avoid black borders
+          around things.  This is a bit of a hack.
+       */
+       _out_size.width &= ~3;
+       _out_size.height &= ~3;
+
        _player->set_video_container_size (_out_size);
 }
 
@@ -288,20 +294,7 @@ FilmViewer::check_play_state ()
 }
 
 void
-FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t)
-{
-       if (eyes == EYES_RIGHT) {
-               return;
-       }
-       
-       _frame = image->image ();
-       _got_frame = true;
-
-       set_position_text (t);
-}
-
-void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text ()
 {
        if (!_film) {
                _frame_number->SetLabel ("0");
@@ -311,9 +304,9 @@ FilmViewer::set_position_text (Time t)
                
        double const fps = _film->video_frame_rate ();
        /* Count frame number from 1 ... not sure if this is the best idea */
-       _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
+       _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (_position.seconds() * fps)) + 1));
        
-       double w = static_cast<double>(t) / TIME_HZ;
+       double w = _position.seconds ();
        int const h = (w / 3600);
        w -= h * 3600;
        int const m = (w / 60);
@@ -324,35 +317,6 @@ FilmViewer::set_position_text (Time t)
        _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f));
 }
 
-/** Ask the player to emit its next frame, then update our display */
-void
-FilmViewer::fetch_next_frame ()
-{
-       /* Clear our frame in case we don't get a new one */
-       _frame.reset ();
-
-       if (!_player) {
-               return;
-       }
-
-       _got_frame = false;
-       
-       try {
-               while (!_got_frame && !_player->pass ()) {}
-       } catch (DecodeError& e) {
-               _play_button->SetValue (false);
-               check_play_state ();
-               error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data()));
-       } catch (OpenFileError& e) {
-               /* There was a problem opening a content file; we'll let this slide as it
-                  probably means a missing content file, which we're already taking care of.
-               */
-       }
-
-       _panel->Refresh ();
-       _panel->Update ();
-}
-
 void
 FilmViewer::active_jobs_changed (bool a)
 {
@@ -376,31 +340,18 @@ FilmViewer::active_jobs_changed (bool a)
 void
 FilmViewer::back_clicked ()
 {
-       if (!_player) {
-               return;
+       DCPTime p = _position - DCPTime::from_frames (1, _film->video_frame_rate ());
+       if (p < DCPTime ()) {
+               p = DCPTime ();
        }
 
-       /* Player::video_position is the time after the last frame that we received.
-          We want to see the one before it, so we need to go back 2.
-       */
-
-       Time p = _player->video_position() - _film->video_frames_to_time (2);
-       if (p < 0) {
-               p = 0;
-       }
-       
-       _player->seek (p, true);
-       fetch_next_frame ();
+       get (p, true);
 }
 
 void
 FilmViewer::forward_clicked ()
 {
-       if (!_player) {
-               return;
-       }
-
-       fetch_next_frame ();
+       get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true);
 }
 
 void
@@ -411,5 +362,5 @@ FilmViewer::player_changed (bool frequent)
        }
 
        calculate_sizes ();
-       fetch_current_frame_again ();
+       get (_position, true);
 }
index c99c7344047448b088115a9e085bb5eb389937fa..207004f29715b6a5958b478df586f24fb5124719 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -32,19 +32,6 @@ class PlayerImage;
 
 /** @class FilmViewer
  *  @brief A wx widget to view a preview of a Film.
- *
- *  The film takes the following path through the viewer:
- *
- *  1. fetch_next_frame() asks our _player to decode some data.  If it does, process_video()
- *     will be called.
- *
- *  2. process_video() takes the image from the player (_frame).
- *
- *  3. fetch_next_frame() calls _panel->Refresh() and _panel->Update() which results in
- *     paint_panel() being called; this creates frame_bitmap from _frame and blits it to the display.
- *
- * fetch_current_frame_again() asks the player to re-emit its current frame on the next pass(), and then
- * starts from step #1.
  */
 class FilmViewer : public wxPanel
 {
@@ -59,16 +46,14 @@ private:
        void slider_moved ();
        void play_clicked ();
        void timer ();
-       void process_video (boost::shared_ptr<PlayerImage>, Eyes, Time);
        void calculate_sizes ();
        void check_play_state ();
-       void fetch_current_frame_again ();
-       void fetch_next_frame ();
        void active_jobs_changed (bool);
        void back_clicked ();
        void forward_clicked ();
        void player_changed (bool);
-       void set_position_text (Time);
+       void set_position_text ();
+       void get (DCPTime, bool);
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
@@ -84,10 +69,10 @@ private:
        wxTimer _timer;
 
        boost::shared_ptr<const Image> _frame;
-       bool _got_frame;
+       DCPTime _position;
 
        /** Size of our output (including padding if we have any) */
-       libdcp::Size _out_size;
+       dcp::Size _out_size;
        /** Size of the panel that we have available */
-       libdcp::Size _panel_size;
+       dcp::Size _panel_size;
 };
index 28247bc33eb3c62b9ef85a7dea83785be7b4fa54..801996efa7bcb611111285a847c41848591f69b7 100644 (file)
@@ -45,8 +45,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
        add (_("Frames already encoded"), true);
        _encoded = add (new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)));
        _encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this));
-       
-       _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
+       _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().frames (_film->video_frame_rate ()))));
        double const disk = double (_film->required_disk_space()) / 1073741824.0f;
        stringstream s;
        s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
@@ -64,10 +63,11 @@ PropertiesDialog::frames_already_encoded () const
        } catch (boost::thread_interrupted &) {
                return "";
        }
-       
-       if (_film->length()) {
+
+       uint64_t const frames = _film->length().frames (_film->video_frame_rate ());
+       if (frames) {
                /* XXX: encoded_frames() should check which frames have been encoded */
-               u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)";
+               u << " (" << (_film->encoded_frames() * 100 / frames) << "%)";
        }
        return u.str ();
 }
index b702ae0ad8188be5892108c5b8ebef9afee3157e..0d46a46ec215dfc5f7de8cdaf89821d955bf3fe2 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <wx/filepicker.h>
 #include <wx/validate.h>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
 #include "lib/compose.hpp"
 #include "lib/util.h"
 #include "screen_dialog.h"
@@ -31,7 +31,7 @@ using std::string;
 using std::cout;
 using boost::shared_ptr;
 
-ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<libdcp::Certificate> certificate)
+ScreenDialog::ScreenDialog (wxWindow* parent, string title, string name, shared_ptr<dcp::Certificate> certificate)
        : TableDialog (parent, std_to_wx (title), 2, true)
        , _certificate (certificate)
 {
@@ -79,7 +79,7 @@ ScreenDialog::name () const
        return wx_to_std (_name->GetValue());
 }
 
-shared_ptr<libdcp::Certificate>
+shared_ptr<dcp::Certificate>
 ScreenDialog::certificate () const
 {
        return _certificate;
@@ -89,9 +89,9 @@ void
 ScreenDialog::load_certificate (boost::filesystem::path file)
 {
        try {
-               _certificate.reset (new libdcp::Certificate (file));
+               _certificate.reset (new dcp::Certificate (file));
                _certificate_text->SetValue (_certificate->certificate ());
-       } catch (libdcp::MiscError& e) {
+       } catch (dcp::MiscError& e) {
                error_dialog (this, String::compose ("Could not read certificate file (%1)", e.what()));
        }
 }
@@ -128,7 +128,7 @@ void
 ScreenDialog::setup_sensitivity ()
 {
        wxButton* ok = dynamic_cast<wxButton*> (FindWindowById (wxID_OK, this));
-       ok->Enable (_certificate);
+       ok->Enable (_certificate.get ());
 
        _download_certificate->Enable (
                _manufacturer->GetStringSelection() == _("Doremi") ||
index 3601a8f6c133e57a556ce22db289fa5bd732629a..5c6d964b8297681405d501543c14843f463d5253 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <wx/wx.h>
 #include <boost/shared_ptr.hpp>
-#include <libdcp/certificates.h>
+#include <dcp/certificates.h>
 #include "table_dialog.h"
 
 class Progress;
@@ -27,10 +27,10 @@ class Progress;
 class ScreenDialog : public TableDialog
 {
 public:
-       ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<libdcp::Certificate> c = boost::shared_ptr<libdcp::Certificate> ());
+       ScreenDialog (wxWindow *, std::string, std::string name = "", boost::shared_ptr<dcp::Certificate> c = boost::shared_ptr<dcp::Certificate> ());
 
        std::string name () const;
-       boost::shared_ptr<libdcp::Certificate> certificate () const;
+       boost::shared_ptr<dcp::Certificate> certificate () const;
        
 private:
        void select_certificate ();
@@ -44,5 +44,5 @@ private:
        wxButton* _download_certificate;
        wxTextCtrl* _certificate_text;
 
-       boost::shared_ptr<libdcp::Certificate> _certificate;
+       boost::shared_ptr<dcp::Certificate> _certificate;
 };
index 63a18b0ce874ce5daea97ded5204ef92287ac4ba..b4b5a7b4239e332ba55886cbcdcc3cd329d249df 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2013 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/lexical_cast.hpp>
 #include <wx/spinctrl.h>
 #include "lib/ffmpeg_content.h"
+#include "lib/subrip_content.h"
 #include "subtitle_panel.h"
 #include "film_editor.h"
 #include "wx_util.h"
+#include "subtitle_view.h"
 
 using std::vector;
 using std::string;
@@ -32,6 +34,7 @@ using boost::dynamic_pointer_cast;
 
 SubtitlePanel::SubtitlePanel (FilmEditor* e)
        : FilmEditorPanel (e, _("Subtitles"))
+       , _view (0)
 {
        wxFlexGridSizer* grid = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        _sizer->Add (grid, 0, wxALL, 8);
@@ -70,6 +73,9 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e)
        add_label_to_sizer (grid, this, _("Subtitle Stream"), true);
        _stream = new wxChoice (this, wxID_ANY);
        grid->Add (_stream, 1, wxEXPAND);
+
+       _view_button = new wxButton (this, wxID_ANY, _("View..."));
+       grid->Add (_view_button);
        
        _x_offset->SetRange (-100, 100);
        _y_offset->SetRange (-100, 100);
@@ -81,6 +87,7 @@ SubtitlePanel::SubtitlePanel (FilmEditor* e)
        _y_offset->Bind       (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_offset_changed, this));
        _scale->Bind          (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::scale_changed, this));
        _stream->Bind         (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&SubtitlePanel::stream_changed, this));
+       _view_button->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&SubtitlePanel::view_clicked, this));
 }
 
 void
@@ -164,6 +171,9 @@ SubtitlePanel::setup_sensitivity ()
        _y_offset->Enable (j);
        _scale->Enable (j);
        _stream->Enable (j);
+
+       SubtitleContentList c = _editor->selected_subtitle_content ();
+       _view_button->Enable (c.size() == 1);
 }
 
 void
@@ -223,3 +233,21 @@ SubtitlePanel::content_selection_changed ()
        film_content_changed (SubtitleContentProperty::SUBTITLE_Y_OFFSET);
        film_content_changed (SubtitleContentProperty::SUBTITLE_SCALE);
 }
+
+void
+SubtitlePanel::view_clicked ()
+{
+       if (_view) {
+               _view->Destroy ();
+               _view = 0;
+       }
+
+       SubtitleContentList c = _editor->selected_subtitle_content ();
+       assert (c.size() == 1);
+       shared_ptr<SubRipContent> sr = dynamic_pointer_cast<SubRipContent> (c.front ());
+       if (sr) {
+               _view = new SubtitleView (this, sr);
+       }
+
+       _view->Show ();
+}
index 20d7c40c2eb1d3eb1add4350a1b1482b3cbfc2a1..1ee775025ae80710638f869c2488fb9366a1b182 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2013 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
@@ -21,6 +21,7 @@
 
 class wxCheckBox;
 class wxSpinCtrl;
+class SubtitleView;
 
 class SubtitlePanel : public FilmEditorPanel
 {
@@ -37,6 +38,7 @@ private:
        void y_offset_changed ();
        void scale_changed ();
        void stream_changed ();
+       void view_clicked ();
 
        void setup_sensitivity ();
        
@@ -45,4 +47,6 @@ private:
        wxSpinCtrl* _y_offset;
        wxSpinCtrl* _scale;
        wxChoice* _stream;
+       wxButton* _view_button;
+       SubtitleView* _view;
 };
diff --git a/src/wx/subtitle_view.cc b/src/wx/subtitle_view.cc
new file mode 100644 (file)
index 0000000..adbc4a6
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "lib/subrip_decoder.h"
+#include "lib/content_subtitle.h"
+#include "subtitle_view.h"
+
+using std::list;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+SubtitleView::SubtitleView (wxWindow* parent, shared_ptr<SubRipContent> content)
+       : wxDialog (parent, wxID_ANY, _("Subtitles"))
+{
+       _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL);
+
+       {
+               wxListItem ip;
+               ip.SetId (0);
+               ip.SetText (_("Start"));
+               ip.SetWidth (100);
+               _list->InsertColumn (0, ip);
+       }
+
+       {
+               wxListItem ip;
+               ip.SetId (1);
+               ip.SetText (_("End"));
+               ip.SetWidth (100);
+               _list->InsertColumn (1, ip);
+       }               
+
+       {
+               wxListItem ip;
+               ip.SetId (2);
+               ip.SetText (_("Subtitle"));
+               ip.SetWidth (640);
+               _list->InsertColumn (2, ip);
+       }
+
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       sizer->Add (_list, 1, wxEXPAND);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
+       if (buttons) {
+               sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
+       list<shared_ptr<ContentTextSubtitle> > subs = decoder->get_text_subtitles (ContentTime(), ContentTime::max ());
+       int n = 0;
+       for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+               for (list<dcp::SubtitleString>::const_iterator j = (*i)->subs.begin(); j != (*i)->subs.end(); ++j) {
+                       wxListItem list_item;
+                       list_item.SetId (n);
+                       _list->InsertItem (list_item);
+                       _list->SetItem (n, 2, j->text ());
+                       ++n;
+               }
+       }
+
+       SetSizerAndFit (sizer);
+}
+
diff --git a/src/wx/subtitle_view.h b/src/wx/subtitle_view.h
new file mode 100644 (file)
index 0000000..94a25b7
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <wx/wx.h>
+#include <wx/listctrl.h>
+
+class SubRipContent;
+
+class SubtitleView : public wxDialog
+{
+public:
+       SubtitleView (wxWindow *, boost::shared_ptr<SubRipContent>);
+
+private:       
+       wxListCtrl* _list;
+};
index ee5b5604bef4189e95329a360525be0e0bfbaf7b..1ab4b590b2b130b4f7d0c80cd21c766c6706ff41 100644 (file)
@@ -83,12 +83,12 @@ Timecode::Timecode (wxWindow* parent)
 }
 
 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);
+       int64_t f = rint (t.seconds() * fps);
        
        int const h = f / (3600 * fps);
        f -= h * 3600 * fps;
@@ -105,18 +105,18 @@ Timecode::set (Time t, int fps)
        _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02" wxLongLongFmtSpec "d", h, m, s, f));
 }
 
-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;
 }
index 880b44a31be8754ca54634e542d9e739e328b745..b13e8c3c084b9c35514e805f59e752927fe46ae1 100644 (file)
@@ -26,8 +26,8 @@ class Timecode : public wxPanel
 public:
        Timecode (wxWindow *);
 
-       void set (Time, int);
-       Time get (int) const;
+       void set (DCPTime, int);
+       DCPTime get (int) const;
 
        void set_editable (bool);
 
index 1858afee7cebc0a4b55de50be3b4fb7dc030dc8b..185abefeb8b5e6c1041c937d02f491c9de00ea3f 100644 (file)
@@ -64,9 +64,9 @@ public:
 protected:
        virtual void do_paint (wxGraphicsContext *) = 0;
        
-       int time_x (Time t) const
+       int time_x (DCPTime t) const
        {
-               return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
+               return _timeline.tracks_position().x + t.seconds() * _timeline.pixels_per_second ();
        }
        
        Timeline& _timeline;
@@ -101,7 +101,7 @@ public:
                return dcpomatic::Rect<int> (
                        time_x (content->position ()) - 8,
                        y_pos (_track.get()) - 8,
-                       content->length_after_trim () * _timeline.pixels_per_time_unit() + 16,
+                       content->length_after_trim().seconds() * _timeline.pixels_per_second() + 16,
                        _timeline.track_height() + 16
                        );
        }
@@ -142,8 +142,8 @@ private:
                        return;
                }
 
-               Time const position = cont->position ();
-               Time const len = cont->length_after_trim ();
+               DCPTime const position = cont->position ();
+               DCPTime const len = cont->length_after_trim ();
 
                wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
 
@@ -172,7 +172,7 @@ private:
                wxDouble name_leading;
                gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
                
-               gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
+               gc->Clip (wxRegion (time_x (position), y_pos (_track.get()), len.seconds() * _timeline.pixels_per_second(), _timeline.track_height()));
                gc->DrawText (name, time_x (position) + 12, y_pos (_track.get() + 1) - name_height - 4);
                gc->ResetClip ();
        }
@@ -191,7 +191,7 @@ private:
                }
 
                if (!frequent) {
-                       _timeline.setup_pixels_per_time_unit ();
+                       _timeline.setup_pixels_per_second ();
                        _timeline.Refresh ();
                }
        }
@@ -246,6 +246,25 @@ private:
        }
 };
 
+class SubtitleContentView : public ContentView
+{
+public:
+       SubtitleContentView (Timeline& tl, shared_ptr<Content> c)
+               : ContentView (tl, c)
+       {}
+
+private:
+       wxString type () const
+       {
+               return _("subtitles");
+       }
+
+       wxColour colour () const
+       {
+               return wxColour (163, 255, 154, 255);
+       }
+};
+
 class TimeAxisView : public View
 {
 public:
@@ -271,18 +290,18 @@ private:
        {
                gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
                
-               int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ()));
+               double mark_interval = rint (128 / _timeline.pixels_per_second ());
                if (mark_interval > 5) {
-                       mark_interval -= mark_interval % 5;
+                       mark_interval -= int (rint (mark_interval)) % 5;
                }
                if (mark_interval > 10) {
-                       mark_interval -= mark_interval % 10;
+                       mark_interval -= int (rint (mark_interval)) % 10;
                }
                if (mark_interval > 60) {
-                       mark_interval -= mark_interval % 60;
+                       mark_interval -= int (rint (mark_interval)) % 60;
                }
                if (mark_interval > 3600) {
-                       mark_interval -= mark_interval % 3600;
+                       mark_interval -= int (rint (mark_interval)) % 3600;
                }
                
                if (mark_interval < 1) {
@@ -294,14 +313,15 @@ private:
                path.AddLineToPoint (_timeline.width(), _y);
                gc->StrokePath (path);
 
-               Time t = 0;
-               while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
+               /* Time in seconds */
+               DCPTime t;
+               while ((t.seconds() * _timeline.pixels_per_second()) < _timeline.width()) {
                        wxGraphicsPath path = gc->CreatePath ();
                        path.MoveToPoint (time_x (t), _y - 4);
                        path.AddLineToPoint (time_x (t), _y + 4);
                        gc->StrokePath (path);
 
-                       int tc = t / TIME_HZ;
+                       double tc = t.seconds ();
                        int const h = tc / 3600;
                        tc -= h * 3600;
                        int const m = tc / 60;
@@ -315,12 +335,12 @@ private:
                        wxDouble str_leading;
                        gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
                        
-                       int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit();
+                       int const tx = _timeline.x_offset() + t.seconds() * _timeline.pixels_per_second();
                        if ((tx + str_width) < _timeline.width()) {
                                gc->DrawText (str, time_x (t), _y + 16);
                        }
                        
-                       t += mark_interval * TIME_HZ;
+                       t += DCPTime::from_seconds (mark_interval);
                }
        }
 
@@ -335,7 +355,7 @@ Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
        , _film (film)
        , _time_axis_view (new TimeAxisView (*this, 32))
        , _tracks (0)
-       , _pixels_per_time_unit (0)
+       , _pixels_per_second (0)
        , _left_down (false)
        , _down_view_position (0)
        , _first_move (false)
@@ -401,10 +421,13 @@ Timeline::playlist_changed ()
                if (dynamic_pointer_cast<AudioContent> (*i)) {
                        _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i)));
                }
+               if (dynamic_pointer_cast<SubtitleContent> (*i)) {
+                       _views.push_back (shared_ptr<View> (new SubtitleContentView (*this, *i)));
+               }
        }
 
        assign_tracks ();
-       setup_pixels_per_time_unit ();
+       setup_pixels_per_second ();
        Refresh ();
 }
 
@@ -416,11 +439,11 @@ Timeline::assign_tracks ()
                if (!cv) {
                        continue;
                }
-       
+
                shared_ptr<Content> content = cv->content();
 
                int t = 0;
-               while (1) {
+               while (true) {
                        ViewList::iterator j = _views.begin();
                        while (j != _views.end()) {
                                shared_ptr<ContentView> test = dynamic_pointer_cast<ContentView> (*j);
@@ -466,14 +489,14 @@ Timeline::tracks () const
 }
 
 void
-Timeline::setup_pixels_per_time_unit ()
+Timeline::setup_pixels_per_second ()
 {
        shared_ptr<const Film> film = _film.lock ();
-       if (!film || film->length() == 0) {
+       if (!film || film->length() == DCPTime ()) {
                return;
        }
 
-       _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length ();
+       _pixels_per_second = static_cast<double>(width() - x_offset() * 2) / film->length().seconds ();
 }
 
 shared_ptr<View>
@@ -592,13 +615,13 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                return;
        }
        
-       Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
+       DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / _pixels_per_second);
        
        if (_snap) {
                
                bool first = true;
-               Time nearest_distance = TIME_MAX;
-               Time nearest_new_position = TIME_MAX;
+               DCPTime nearest_distance = DCPTime::max ();
+               DCPTime nearest_new_position = DCPTime::max ();
                
                /* Find the nearest content edge; this is inefficient */
                for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
@@ -609,7 +632,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                        
                        {
                                /* Snap starts to ends */
-                               Time const d = abs (cv->content()->end() - new_position);
+                               DCPTime const d = DCPTime (cv->content()->end() - new_position).abs ();
                                if (first || d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->end();
@@ -618,7 +641,10 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                        
                        {
                                /* Snap ends to starts */
-                               Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
+                               DCPTime const d = DCPTime (
+                                       cv->content()->position() - (new_position + _down_view->content()->length_after_trim())
+                                       ).abs ();
+                               
                                if (d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
@@ -630,14 +656,14 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                
                if (!first) {
                        /* Snap if it's close; `close' means within a proportion of the time on the timeline */
-                       if (nearest_distance < (width() / pixels_per_time_unit()) / 32) {
+                       if (nearest_distance < DCPTime::from_seconds ((width() / pixels_per_second()) / 32)) {
                                new_position = nearest_new_position;
                        }
                }
        }
        
-       if (new_position < 0) {
-               new_position = 0;
+       if (new_position < DCPTime ()) {
+               new_position = DCPTime ();
        }
        
        _down_view->content()->set_position (new_position);
@@ -662,7 +688,7 @@ Timeline::film () const
 void
 Timeline::resized ()
 {
-       setup_pixels_per_time_unit ();
+       setup_pixels_per_second ();
 }
 
 void
index ef1d10797c6e75d114c9d55d7b5d97ff40777279..35153dd175d4c13aa4f510656c3810a2a1bd38fc 100644 (file)
@@ -52,8 +52,8 @@ public:
                return 48;
        }
 
-       double pixels_per_time_unit () const {
-               return _pixels_per_time_unit;
+       double pixels_per_second () const {
+               return _pixels_per_second;
        }
 
        Position<int> tracks_position () const {
@@ -62,7 +62,7 @@ public:
 
        int tracks () const;
 
-       void setup_pixels_per_time_unit ();
+       void setup_pixels_per_second ();
 
        void set_snap (bool s) {
                _snap = s;
@@ -96,11 +96,11 @@ private:
        ViewList _views;
        boost::shared_ptr<TimeAxisView> _time_axis_view;
        int _tracks;
-       double _pixels_per_time_unit;
+       double _pixels_per_second;
        bool _left_down;
        wxPoint _down_point;
        boost::shared_ptr<ContentView> _down_view;
-       Time _down_view_position;
+       DCPTime _down_view_position;
        bool _first_move;
        ContentMenu _menu;
        bool _snap;
index 5cefe318af333d35ff68da2c02482325d57721c2..f33e052a1ad41dad2f2adea9b0b7aa39f7823ceb 100644 (file)
@@ -88,47 +88,43 @@ TimingPanel::film_content_changed (int property)
                if (content) {
                        _position->set (content->position (), film_video_frame_rate);
                } else {
-                       _position->set (0, 24);
+                       _position->set (DCPTime () , 24);
                }
        } else if (
                property == ContentProperty::LENGTH ||
                property == VideoContentProperty::VIDEO_FRAME_RATE ||
-               property == VideoContentProperty::VIDEO_FRAME_TYPE ||
-               property == SndfileContentProperty::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);
                } else {
-                       _full_length->set (0, 24);
-                       _play_length->set (0, 24);
+                       _full_length->set (DCPTime (), 24);
+                       _play_length->set (DCPTime (), 24);
                }
        } 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);
                } else {
-                       _trim_start->set (0, 24);
-                       _play_length->set (0, 24);
+                       _trim_start->set (DCPTime (), 24);
+                       _play_length->set (DCPTime (), 24);
                }
        } 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);
                } else {
-                       _trim_end->set (0, 24);
-                       _play_length->set (0, 24);
+                       _trim_end->set (DCPTime (), 24);
+                       _play_length->set (DCPTime (), 24);
                }
        }
 
        if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
                if (content) {
                        shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (content);
-                       shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (content);
                        if (vc) {
                                _video_frame_rate->SetValue (std_to_wx (lexical_cast<string> (vc->video_frame_rate ())));
-                       } else if (sc) {
-                               _video_frame_rate->SetValue (std_to_wx (lexical_cast<string> (sc->video_frame_rate ())));
                        } else {
                                _video_frame_rate->SetValue ("24");
                        }
@@ -161,7 +157,8 @@ TimingPanel::full_length_changed ()
        if (c.size() == 1) {
                shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (c.front ());
                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 (_editor->film()->video_frame_rate()), FrameRateChange (1, 1)));
                }
        }
 }
@@ -209,10 +206,6 @@ TimingPanel::set_video_frame_rate ()
                if (ic) {
                        ic->set_video_frame_rate (lexical_cast<float> (wx_to_std (_video_frame_rate->GetValue ())));
                }
-               shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (c.front ());
-               if (sc) {
-                       sc->set_video_frame_rate (lexical_cast<float> (wx_to_std (_video_frame_rate->GetValue ())));
-               }
                _set_video_frame_rate->Enable (false);
        }
 }
index ac4c29222b231e9912ca1e7306bbd91f253c8ac2..399e71aac26edba4d752847d0faa4240bb2ead69 100644 (file)
@@ -297,8 +297,8 @@ VideoPanel::setup_description ()
        }
 
        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,
@@ -307,8 +307,8 @@ VideoPanel::setup_description ()
                ++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 = _editor->film()->frame_size ();
+       dcp::Size const scaled = vcs->scale().size (vcs, container_size, container_size);
 
        if (scaled != vcs->video_size_after_crop ()) {
                d << wxString::Format (
@@ -330,7 +330,7 @@ VideoPanel::setup_description ()
 
        d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
        ++lines;
-       FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+       FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
        d << std_to_wx (frc.description) << "\n";
        ++lines;
 
index a04df2d41751a718f72f3f30584cf2109c94a1ee..cd78f064920081a7bbee364035af9f3f2e0c753b 100644 (file)
@@ -38,6 +38,7 @@ sources = """
           server_dialog.cc
           servers_list_dialog.cc
           subtitle_panel.cc
+          subtitle_view.cc
           table_dialog.cc
           timecode.cc
           timeline.cc
index 96278b82bac0d1197a26ae68da601c9084a912a3..048f87908e8c72770e973f0450a36767c1b747f3 100644 (file)
@@ -123,7 +123,7 @@ int const ThreadedStaticText::_update_event_id = 10000;
  *  @param initial Initial text for the wxStaticText while the computation is being run.
  *  @param fn Function which works out what the wxStaticText content should be and returns it.
  */
-ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, function<string ()> fn)
+ThreadedStaticText::ThreadedStaticText (wxWindow* parent, wxString initial, boost::function<string ()> fn)
        : wxStaticText (parent, wxID_ANY, initial)
 {
        Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&ThreadedStaticText::thread_finished, this, _1), _update_event_id);
@@ -139,7 +139,7 @@ ThreadedStaticText::~ThreadedStaticText ()
 
 /** Run our thread and post the result to the GUI thread via AddPendingEvent */
 void
-ThreadedStaticText::run (function<string ()> fn)
+ThreadedStaticText::run (boost::function<string ()> fn)
 try
 {
        wxCommandEvent ev (wxEVT_COMMAND_TEXT_UPDATED, _update_event_id);
index 77b2aeaf6f9671a1685bf841d357685145a291a8..b94b120564a13af559571323bb102494eba0cc2b 100644 (file)
@@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE (audio_analysis_test)
 
        AudioAnalysis b ("build/test/audio_analysis_test");
        for (int i = 0; i < channels; ++i) {
-               BOOST_CHECK (b.points(i) == points);
+               BOOST_CHECK_EQUAL (b.points(i), points);
                for (int j = 0; j < points; ++j) {
                        AudioPoint p = b.get_point (i, j);
                        BOOST_CHECK_CLOSE (p[AudioPoint::PEAK], random_float (), 1);
index 77243ea6d0e46d04b56dbeb34c33b5120977efc5..2e02fa999e9b1e5d617bf2c0bc5b9f064dec60b3 100644 (file)
 */
 
 #include <boost/test/unit_test.hpp>
-#include <libdcp/sound_frame.h>
-#include <libdcp/cpl.h>
-#include <libdcp/reel.h>
-#include <libdcp/sound_asset.h>
+#include <dcp/sound_frame.h>
+#include <dcp/cpl.h>
+#include <dcp/reel.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/reel_sound_asset.h>
 #include "lib/sndfile_content.h"
 #include "lib/dcp_content_type.h"
 #include "lib/ratio.h"
@@ -53,10 +54,10 @@ void test_audio_delay (int delay_in_ms)
        boost::filesystem::path path = "build/test";
        path /= film_name;
        path /= film->dcp_name ();
-       libdcp::DCP check (path.string ());
+       dcp::DCP check (path.string ());
        check.read ();
 
-       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
        BOOST_CHECK (sound_asset);
 
        /* Sample index in the DCP */
@@ -66,11 +67,11 @@ void test_audio_delay (int delay_in_ms)
        /* Delay in frames */
        int const delay_in_frames = delay_in_ms * 48000 / 1000;
 
-       while (n < sound_asset->intrinsic_duration()) {
-               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+       while (n < sound_asset->mxf()->intrinsic_duration()) {
+               shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
                uint8_t const * d = sound_frame->data ();
                
-               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
 
                        /* Mono input so it will appear on centre */
                        int const sample = d[i + 7] | (d[i + 8] << 8);
index a2a74104b513820e367b43d67d2609beb099c324..1fc20dcaf6e0433dcfcaebdcf3afc4a0ddcc955c 100644 (file)
@@ -35,10 +35,10 @@ BOOST_AUTO_TEST_CASE (audio_mapping_test)
 
        for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < MAX_AUDIO_CHANNELS; ++j) {
-                       BOOST_CHECK_EQUAL (four.get (i, static_cast<libdcp::Channel> (j)), i == j ? 1 : 0);
+                       BOOST_CHECK_EQUAL (four.get (i, static_cast<dcp::Channel> (j)), i == j ? 1 : 0);
                }
        }
 
-       four.set (0, libdcp::RIGHT, 1);
-       BOOST_CHECK_EQUAL (four.get (0, libdcp::RIGHT), 1);
+       four.set (0, dcp::RIGHT, 1);
+       BOOST_CHECK_EQUAL (four.get (0, dcp::RIGHT), 1);
 }
diff --git a/test/audio_merger_test.cc b/test/audio_merger_test.cc
deleted file mode 100644 (file)
index 31d055a..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/test/unit_test.hpp>
-#include <boost/bind.hpp>
-#include <boost/function.hpp>
-#include <boost/signals2.hpp>
-#include "lib/audio_merger.h"
-#include "lib/audio_buffers.h"
-
-using boost::shared_ptr;
-using boost::bind;
-
-static shared_ptr<const AudioBuffers> last_audio;
-
-static int
-pass_through (int x)
-{
-       return x;
-}
-
-BOOST_AUTO_TEST_CASE (audio_merger_test1)
-{
-       AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
-
-       /* Push 64 samples, 0 -> 63 at time 0 */
-       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
-       for (int i = 0; i < 64; ++i) {
-               buffers->data()[0][i] = i;
-       }
-       merger.push (buffers, 0);
-
-       /* Push 64 samples, 0 -> 63 at time 22 */
-       merger.push (buffers, 22);
-
-       TimedAudioBuffers<int> tb = merger.pull (22);
-       BOOST_CHECK (tb.audio != shared_ptr<const AudioBuffers> ());
-       BOOST_CHECK_EQUAL (tb.audio->frames(), 22);
-       BOOST_CHECK_EQUAL (tb.time, 0);
-
-       /* And they should be a staircase */
-       for (int i = 0; i < 22; ++i) {
-               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i);
-       }
-
-       tb = merger.flush ();
-
-       /* That flush should give us 64 samples at 22 */
-       BOOST_CHECK_EQUAL (tb.audio->frames(), 64);
-       BOOST_CHECK_EQUAL (tb.time, 22);
-
-       /* Check the sample values */
-       for (int i = 0; i < 64; ++i) {
-               int correct = i;
-               if (i < (64 - 22)) {
-                       correct += i + 22;
-               }
-               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], correct);
-       }
-}
-
-BOOST_AUTO_TEST_CASE (audio_merger_test2)
-{
-       AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
-
-       /* Push 64 samples, 0 -> 63 at time 9 */
-       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
-       for (int i = 0; i < 64; ++i) {
-               buffers->data()[0][i] = i;
-       }
-       merger.push (buffers, 9);
-
-       TimedAudioBuffers<int> tb = merger.pull (9);
-       BOOST_CHECK_EQUAL (tb.audio->frames(), 9);
-       BOOST_CHECK_EQUAL (tb.time, 0);
-       
-       for (int i = 0; i < 9; ++i) {
-               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], 0);
-       }
-       
-       tb = merger.flush ();
-
-       /* That flush should give us 64 samples at 9 */
-       BOOST_CHECK_EQUAL (tb.audio->frames(), 64);
-       BOOST_CHECK_EQUAL (tb.time, 9);
-       
-       /* Check the sample values */
-       for (int i = 0; i < 64; ++i) {
-               BOOST_CHECK_EQUAL (tb.audio->data()[0][i], i);
-       }
-}
index c2170d891744f3ce14848a9a372acdd0cdd5eb2a..a7e44bdfbd34f3360f5460bcc612b8562de70004 100644 (file)
@@ -46,10 +46,10 @@ BOOST_AUTO_TEST_CASE (black_fill_test)
        film->examine_and_add_content (contentB);
        wait_for_jobs ();
 
-       contentA->set_video_length (3);
-       contentA->set_position (film->video_frames_to_time (2));
-       contentB->set_video_length (1);
-       contentB->set_position (film->video_frames_to_time (7));
+       contentA->set_video_length (ContentTime::from_frames (3, 24));
+       contentA->set_position (DCPTime::from_frames (2, film->video_frame_rate ()));
+       contentB->set_video_length (ContentTime::from_frames (1, 24));
+       contentB->set_position (DCPTime::from_frames (7, film->video_frame_rate ()));
 
        film->make_dcp ();
 
index 1ad156ae38f98495ce3a23e58e71ce3da6c18369..54bbd520f8dd9c8d37ca2d1b30efd00cf2bca2f4 100644 (file)
@@ -36,12 +36,12 @@ do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription description
        BOOST_CHECK (remotely_encoded);
        
        BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size());
-       BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0);
+       BOOST_CHECK_EQUAL (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()), 0);
 }
 
 BOOST_AUTO_TEST_CASE (client_server_test)
 {
-       shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true));
+       shared_ptr<Image> image (new Image (PIX_FMT_RGB24, dcp::Size (1998, 1080), true));
        uint8_t* p = image->data()[0];
        
        for (int y = 0; y < 1080; ++y) {
@@ -54,7 +54,7 @@ BOOST_AUTO_TEST_CASE (client_server_test)
                p += image->stride()[0];
        }
 
-       shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, libdcp::Size (100, 200), true));
+       shared_ptr<Image> sub_image (new Image (PIX_FMT_RGBA, dcp::Size (100, 200), true));
        p = sub_image->data()[0];
        for (int y = 0; y < 200; ++y) {
                uint8_t* q = p;
index 3e90d542a8b74586ad5d1b5d8ecd2f1a089df1a2..0cf3a616b20992373e6a9ca777331b0aa1149723 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <boost/test/unit_test.hpp>
-#include <libdcp/colour_matrix.h>
+#include <dcp/colour_matrix.h>
 #include "lib/colour_conversion.h"
 
 using std::cout;
@@ -26,8 +26,8 @@ using std::cout;
 /* Basic test of identifier() for ColourConversion (i.e. a hash of the numbers) */
 BOOST_AUTO_TEST_CASE (colour_conversion_test)
 {
-       ColourConversion A (2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6);
-       ColourConversion B (2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6);
+       ColourConversion A (2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6);
+       ColourConversion B (2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6);
 
        BOOST_CHECK_EQUAL (A.identifier(), "246ff9b7dc32c0488948a32a713924b3");
        BOOST_CHECK_EQUAL (B.identifier(), "a8d1da30f96a121d8db06a03409758b3");
index 2e83d45c9101dbef0c064e3c0e26ef5446bcab85..48422259c7d272c7df160b1b6daa6d5b6bbd5139 100644 (file)
 
 */
 
+/** @file  test/ffmpeg_audio_test.cc
+ *  @brief A simple test of reading audio from an FFmpeg file.
+ */
+
 #include <boost/test/unit_test.hpp>
-#include <libdcp/cpl.h>
-#include <libdcp/dcp.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/sound_frame.h>
-#include <libdcp/reel.h>
+#include <dcp/cpl.h>
+#include <dcp/dcp.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_frame.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/reel.h>
 #include "lib/sndfile_content.h"
 #include "lib/film.h"
 #include "lib/dcp_content_type.h"
@@ -55,56 +60,56 @@ BOOST_AUTO_TEST_CASE (ffmpeg_audio_test)
        boost::filesystem::path path = "build/test";
        path /= "ffmpeg_audio_test";
        path /= film->dcp_name ();
-       libdcp::DCP check (path.string ());
+       dcp::DCP check (path.string ());
        check.read ();
 
-       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
        BOOST_CHECK (sound_asset);
-       BOOST_CHECK (sound_asset->channels () == 6);
+       BOOST_CHECK_EQUAL (sound_asset->mxf()->channels (), 6);
 
        /* Sample index in the DCP */
        int n = 0;
        /* DCP sound asset frame */
        int frame = 0;
 
-       while (n < sound_asset->intrinsic_duration()) {
-               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+       while (n < sound_asset->mxf()->intrinsic_duration()) {
+               shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
                uint8_t const * d = sound_frame->data ();
                
-               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
 
-                       if (sound_asset->channels() > 0) {
+                       if (sound_asset->mxf()->channels() > 0) {
                                /* L should be silent */
                                int const sample = d[i + 0] | (d[i + 1] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
 
-                       if (sound_asset->channels() > 1) {
+                       if (sound_asset->mxf()->channels() > 1) {
                                /* R should be silent */
                                int const sample = d[i + 2] | (d[i + 3] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
                        
-                       if (sound_asset->channels() > 2) {
+                       if (sound_asset->mxf()->channels() > 2) {
                                /* Mono input so it will appear on centre */
                                int const sample = d[i + 7] | (d[i + 8] << 8);
                                BOOST_CHECK_EQUAL (sample, n);
                        }
 
-                       if (sound_asset->channels() > 3) {
+                       if (sound_asset->mxf()->channels() > 3) {
                                /* Lfe should be silent */
                                int const sample = d[i + 9] | (d[i + 10] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
 
-                       if (sound_asset->channels() > 4) {
+                       if (sound_asset->mxf()->channels() > 4) {
                                /* Ls should be silent */
                                int const sample = d[i + 11] | (d[i + 12] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
 
 
-                       if (sound_asset->channels() > 5) {
+                       if (sound_asset->mxf()->channels() > 5) {
                                /* Rs should be silent */
                                int const sample = d[i + 13] | (d[i + 14] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
index a3b9bb4f6506793186b1aff886f68f67c07d6662..93a913870f81a108e1dfe46b135e16a912eeb5ab 100644 (file)
@@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test)
        shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
 
-       BOOST_CHECK_EQUAL (examiner->first_video().get(), 600);
+       BOOST_CHECK_EQUAL (examiner->first_video().get(), ContentTime (600));
        BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
-       BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), 600);
+       BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), ContentTime (600));
 }
index 6caf0d07a2a9d2cf9277afa69f5320b774ce24ae..dc391ed46c5b94f4633b40cfd320f39ba0ae4d6c 100644 (file)
@@ -34,48 +34,43 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
 
        {
                /* Sound == video so no offset required */
-               content->_first_video = 0;
-               content->_audio_stream->first_audio = 0;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
+               content->_first_video = ContentTime ();
+               content->_audio_stream->first_audio = ContentTime ();
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
        }
 
        {
                /* Common offset should be removed */
-               content->_first_video = 600;
-               content->_audio_stream->first_audio = 600;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, -600);
+               content->_first_video = ContentTime::from_seconds (600);
+               content->_audio_stream->first_audio = ContentTime::from_seconds (600);
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime::from_seconds (-600));
        }
 
        {
                /* Video is on a frame boundary */
-               content->_first_video = 1.0 / 24.0;
-               content->_audio_stream->first_audio = 0;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, 0);
+               content->_first_video = ContentTime::from_frames (1, 24);
+               content->_audio_stream->first_audio = ContentTime ();
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
        }
 
        {
                /* Video is off a frame boundary */
                double const frame = 1.0 / 24.0;
-               content->_first_video = frame + 0.0215;
-               content->_audio_stream->first_audio = 0;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
-               BOOST_CHECK_CLOSE (decoder._pts_offset, (frame - 0.0215), 0.00001);
+               content->_first_video = ContentTime::from_seconds (frame + 0.0215);
+               content->_audio_stream->first_audio = ContentTime ();
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215), 0.00001);
        }
 
        {
                /* Video is off a frame boundary and both have a common offset */
                double const frame = 1.0 / 24.0;
-               content->_first_video = frame + 0.0215 + 4.1;
-               content->_audio_stream->first_audio = 4.1;
-               FFmpegDecoder decoder (film, content, true, true);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
-               BOOST_CHECK_EQUAL (decoder._pts_offset, (frame - 0.0215) - 4.1);
+               content->_first_video = ContentTime::from_seconds (frame + 0.0215 + 4.1);
+               content->_audio_stream->first_audio = ContentTime::from_seconds (4.1);
+               FFmpegDecoder decoder (content, film->log());
+               BOOST_CHECK_EQUAL (decoder._pts_offset.seconds(), (frame - 0.0215) - 4.1);
        }
 }
index fdfdcf4529739f126ffab9da192aafeec44f8503..2135b3738f50a669b39b2678a51bd404eb755a2f 100644 (file)
@@ -47,91 +47,102 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 60;
        int best = film->playlist()->best_dcp_frame_rate ();
-       FrameRateConversion frc = FrameRateConversion (60, best);
+       FrameRateChange frc = FrameRateChange (60, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (50, best);
+       frc = FrameRateChange (50, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (48, best);
+       frc = FrameRateChange (48, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 30;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (30, best);
+       frc = FrameRateChange (30, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 29.97;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (29.97, best);
+       frc = FrameRateChange (29.97, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 30 / 29.97, 0.1);
        
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (25, best);
+       frc = FrameRateChange (25, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 24;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (24, best);
+       frc = FrameRateChange (24, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 14.5;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (14.5, best);
+       frc = FrameRateChange (14.5, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 15 / 14.5, 0.1);
 
        content->_video_frame_rate = 12.6;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12.6, best);
+       frc = FrameRateChange (12.6, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 25 / 25.2, 0.1);
 
        content->_video_frame_rate = 12.4;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12.4, best);
+       frc = FrameRateChange (12.4, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 25 / 24.8, 0.1);
 
        content->_video_frame_rate = 12;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12, best);
+       frc = FrameRateChange (12, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        /* Now add some more rates and see if it will use them
           in preference to skip/repeat.
@@ -144,34 +155,38 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 60;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (60, best);
+       frc = FrameRateChange (60, best);
        BOOST_CHECK_EQUAL (best, 60);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (50, best);
+       frc = FrameRateChange (50, best);
        BOOST_CHECK_EQUAL (best, 50);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (48, best);
+       frc = FrameRateChange (48, best);
        BOOST_CHECK_EQUAL (best, 48);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        /* Check some out-there conversions (not the best) */
        
-       frc = FrameRateConversion (14.99, 24);
+       frc = FrameRateChange (14.99, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 24 / (2 * 14.99), 0.1);
 
        /* Check some conversions with limited DCP targets */
 
@@ -181,14 +196,15 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (25, best);
+       frc = FrameRateChange (25, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 24.0 / 25, 0.1);
 }
 
-/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+/* Test Playlist::best_dcp_frame_rate and FrameRateChange
    with two pieces of content.
 */
 BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
@@ -266,7 +282,7 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
        content->_video_frame_rate = 14.99;
        film->set_video_frame_rate (25);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
-       /* The FrameRateConversion within output_audio_frame_rate should choose to double-up
+       /* The FrameRateChange within output_audio_frame_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
        BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
index 51ad49ebf63479694cd2c4099686a3e630babe7d..f876c8f65fc08c1e9d12b5758c982a7abe93f18e 100644 (file)
@@ -28,7 +28,7 @@ using boost::shared_ptr;
 
 BOOST_AUTO_TEST_CASE (aligned_image_test)
 {
-       Image* s = new Image (PIX_FMT_RGB24, libdcp::Size (50, 50), true);
+       Image* s = new Image (PIX_FMT_RGB24, dcp::Size (50, 50), true);
        BOOST_CHECK_EQUAL (s->components(), 1);
        /* 160 is 150 aligned to the nearest 32 bytes */
        BOOST_CHECK_EQUAL (s->stride()[0], 160);
@@ -50,12 +50,12 @@ BOOST_AUTO_TEST_CASE (aligned_image_test)
        BOOST_CHECK (t->data() != s->data());
        BOOST_CHECK (t->data()[0] != s->data()[0]);
        BOOST_CHECK (t->line_size() != s->line_size());
-       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK_EQUAL (t->line_size()[0], s->line_size()[0]);
        BOOST_CHECK (t->stride() != s->stride());
-       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+       BOOST_CHECK_EQUAL (t->stride()[0], s->stride()[0]);
 
        /* assignment operator */
-       Image* u = new Image (PIX_FMT_YUV422P, libdcp::Size (150, 150), false);
+       Image* u = new Image (PIX_FMT_YUV422P, dcp::Size (150, 150), false);
        *u = *s;
        BOOST_CHECK_EQUAL (u->components(), 1);
        BOOST_CHECK_EQUAL (u->stride()[0], 160);
@@ -67,9 +67,9 @@ BOOST_AUTO_TEST_CASE (aligned_image_test)
        BOOST_CHECK (u->data() != s->data());
        BOOST_CHECK (u->data()[0] != s->data()[0]);
        BOOST_CHECK (u->line_size() != s->line_size());
-       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK_EQUAL (u->line_size()[0], s->line_size()[0]);
        BOOST_CHECK (u->stride() != s->stride());
-       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+       BOOST_CHECK_EQUAL (u->stride()[0], s->stride()[0]);
 
        delete s;
        delete t;
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE (aligned_image_test)
 
 BOOST_AUTO_TEST_CASE (compact_image_test)
 {
-       Image* s = new Image (PIX_FMT_RGB24, libdcp::Size (50, 50), false);
+       Image* s = new Image (PIX_FMT_RGB24, dcp::Size (50, 50), false);
        BOOST_CHECK_EQUAL (s->components(), 1);
        BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3);
        BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3);
@@ -99,12 +99,12 @@ BOOST_AUTO_TEST_CASE (compact_image_test)
        BOOST_CHECK (t->data() != s->data());
        BOOST_CHECK (t->data()[0] != s->data()[0]);
        BOOST_CHECK (t->line_size() != s->line_size());
-       BOOST_CHECK (t->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK_EQUAL (t->line_size()[0], s->line_size()[0]);
        BOOST_CHECK (t->stride() != s->stride());
-       BOOST_CHECK (t->stride()[0] == s->stride()[0]);
+       BOOST_CHECK_EQUAL (t->stride()[0], s->stride()[0]);
 
        /* assignment operator */
-       Image* u = new Image (PIX_FMT_YUV422P, libdcp::Size (150, 150), true);
+       Image* u = new Image (PIX_FMT_YUV422P, dcp::Size (150, 150), true);
        *u = *s;
        BOOST_CHECK_EQUAL (u->components(), 1);
        BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3);
@@ -116,9 +116,9 @@ BOOST_AUTO_TEST_CASE (compact_image_test)
        BOOST_CHECK (u->data() != s->data());
        BOOST_CHECK (u->data()[0] != s->data()[0]);
        BOOST_CHECK (u->line_size() != s->line_size());
-       BOOST_CHECK (u->line_size()[0] == s->line_size()[0]);
+       BOOST_CHECK_EQUAL (u->line_size()[0], s->line_size()[0]);
        BOOST_CHECK (u->stride() != s->stride());
-       BOOST_CHECK (u->stride()[0] == s->stride()[0]);
+       BOOST_CHECK_EQUAL (u->stride()[0], s->stride()[0]);
 
        delete s;
        delete t;
@@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE (compact_image_test)
 BOOST_AUTO_TEST_CASE (crop_image_test)
 {
        /* This was to check out a bug with valgrind, and is probably not very useful */
-       shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (16, 16), true));
+       shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, dcp::Size (16, 16), true));
        image->make_black ();
        Crop crop;
        crop.top = 3;
@@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE (crop_image_test)
 BOOST_AUTO_TEST_CASE (crop_image_test2)
 {
        /* Here's a 1998 x 1080 image which is black */
-       shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, libdcp::Size (1998, 1080), true));
+       shared_ptr<Image> image (new Image (PIX_FMT_YUV420P, dcp::Size (1998, 1080), true));
        image->make_black ();
 
        /* Crop it by 1 pixel */
@@ -170,7 +170,7 @@ boost::shared_ptr<Image>
 read_file (string file)
 {
        Magick::Image magick_image (file.c_str ());
-       libdcp::Size size (magick_image.columns(), magick_image.rows());
+       dcp::Size size (magick_image.columns(), magick_image.rows());
 
        boost::shared_ptr<Image> image (new Image (PIX_FMT_RGB24, size, true));
 
@@ -214,7 +214,7 @@ write_file (shared_ptr<Image> image, string file)
 
 static
 void
-crop_scale_window_single (AVPixelFormat in_format, libdcp::Size in_size, Crop crop, libdcp::Size inter_size, libdcp::Size out_size)
+crop_scale_window_single (AVPixelFormat in_format, dcp::Size in_size, Crop crop, dcp::Size inter_size, dcp::Size out_size)
 {
        /* Set up our test image */
        shared_ptr<Image> test (new Image (in_format, in_size, true));
@@ -262,12 +262,12 @@ crop_scale_window_single (AVPixelFormat in_format, libdcp::Size in_size, Crop cr
 /** Test Image::crop_scale_window against separate calls to crop/scale/copy */
 BOOST_AUTO_TEST_CASE (crop_scale_window_test)
 {
-       crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (), libdcp::Size (640, 480), libdcp::Size (640, 480));
-       crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (2, 4, 6, 8), libdcp::Size (640, 480), libdcp::Size (640, 480));
-       crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (2, 4, 6, 8), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
-       crop_scale_window_single (AV_PIX_FMT_YUV422P, libdcp::Size (640, 480), Crop (1, 4, 6, 8), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
-       crop_scale_window_single (AV_PIX_FMT_YUV420P, libdcp::Size (640, 480), Crop (16, 16, 0, 0), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
-       crop_scale_window_single (AV_PIX_FMT_YUV420P, libdcp::Size (640, 480), Crop (16, 3, 3, 0), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
-       crop_scale_window_single (AV_PIX_FMT_RGB24, libdcp::Size (1000, 800), Crop (0, 0, 0, 0), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
-       crop_scale_window_single (AV_PIX_FMT_RGB24, libdcp::Size (1000, 800), Crop (55, 0, 1, 9), libdcp::Size (1920, 1080), libdcp::Size (1998, 1080));
+       crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (), dcp::Size (640, 480), dcp::Size (640, 480));
+       crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (2, 4, 6, 8), dcp::Size (640, 480), dcp::Size (640, 480));
+       crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (2, 4, 6, 8), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+       crop_scale_window_single (AV_PIX_FMT_YUV422P, dcp::Size (640, 480), Crop (1, 4, 6, 8), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+       crop_scale_window_single (AV_PIX_FMT_YUV420P, dcp::Size (640, 480), Crop (16, 16, 0, 0), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+       crop_scale_window_single (AV_PIX_FMT_YUV420P, dcp::Size (640, 480), Crop (16, 3, 3, 0), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+       crop_scale_window_single (AV_PIX_FMT_RGB24, dcp::Size (1000, 800), Crop (0, 0, 0, 0), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
+       crop_scale_window_single (AV_PIX_FMT_RGB24, dcp::Size (1000, 800), Crop (55, 0, 1, 9), dcp::Size (1920, 1080), dcp::Size (1998, 1080));
 }
index 7c0f92142b0f2f9faf4e68c58d66fc89b79b7e38..88c1bd0f46d7f60c293b7a20d911e46ea1b8aa3f 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <boost/test/unit_test.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
 extern "C" {
 #include <libavutil/pixfmt.h>
 }
@@ -32,8 +32,8 @@ using std::list;
 */
 BOOST_AUTO_TEST_CASE (make_black_test)
 {
-       libdcp::Size in_size (512, 512);
-       libdcp::Size out_size (1024, 1024);
+       dcp::Size in_size (512, 512);
+       dcp::Size out_size (1024, 1024);
 
        list<AVPixelFormat> pix_fmts;
        pix_fmts.push_back (AV_PIX_FMT_RGB24);
diff --git a/test/play_test.cc b/test/play_test.cc
deleted file mode 100644 (file)
index 51e2272..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/test/unit_test.hpp>
-#include "lib/player.h"
-#include "lib/ratio.h"
-#include "lib/dcp_content_type.h"
-#include "test.h"
-
-/* This test needs stuff in Player that is only included in debug mode */
-#ifdef DCPOMATIC_DEBUG
-
-using std::cout;
-using boost::optional;
-using boost::shared_ptr;
-
-struct Video
-{
-       boost::shared_ptr<Content> content;
-       boost::shared_ptr<const Image> image;
-       Time time;
-};
-
-class PlayerWrapper
-{
-public:
-       PlayerWrapper (shared_ptr<Player> p)
-               : _player (p)
-       {
-               _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _5));
-       }
-
-       void process_video (shared_ptr<PlayerImage> i, bool, Time t)
-       {
-               Video v;
-               v.content = _player->_last_video;
-               v.image = i->image ();
-               v.time = t;
-               _queue.push_front (v);
-       }
-
-       optional<Video> get_video ()
-       {
-               while (_queue.empty() && !_player->pass ()) {}
-               if (_queue.empty ()) {
-                       return optional<Video> ();
-               }
-               
-               Video v = _queue.back ();
-               _queue.pop_back ();
-               return v;
-       }
-
-       void seek (Time t, bool ac)
-       {
-               _player->seek (t, ac);
-               _queue.clear ();
-       }
-
-private:
-       shared_ptr<Player> _player;
-       std::list<Video> _queue;
-};
-
-BOOST_AUTO_TEST_CASE (play_test)
-{
-       shared_ptr<Film> film = new_test_film ("play_test");
-       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
-       film->set_container (Ratio::from_id ("185"));
-       film->set_name ("play_test");
-
-       shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/red_24.mp4"));
-       film->examine_and_add_content (A);
-       wait_for_jobs ();
-
-       BOOST_CHECK_EQUAL (A->video_length_after_3d_combine(), 16);
-
-       shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/red_30.mp4"));
-       film->examine_and_add_content (B);
-       wait_for_jobs ();
-
-       BOOST_CHECK_EQUAL (B->video_length_after_3d_combine(), 16);
-       
-       /* Film should have been set to 25fps */
-       BOOST_CHECK_EQUAL (film->video_frame_rate(), 25);
-
-       BOOST_CHECK_EQUAL (A->position(), 0);
-       /* A is 16 frames long at 25 fps */
-       BOOST_CHECK_EQUAL (B->position(), 16 * TIME_HZ / 25);
-
-       shared_ptr<Player> player = film->make_player ();
-       PlayerWrapper wrap (player);
-       /* Seek and audio don't get on at the moment */
-       player->disable_audio ();
-
-       for (int i = 0; i < 32; ++i) {
-               optional<Video> v = wrap.get_video ();
-               BOOST_CHECK (v);
-               if (i < 16) {
-                       BOOST_CHECK (v.get().content == A);
-               } else {
-                       BOOST_CHECK (v.get().content == B);
-               }
-       }
-
-       player->seek (10 * TIME_HZ / 25, true);
-       optional<Video> v = wrap.get_video ();
-       BOOST_CHECK (v);
-       BOOST_CHECK_EQUAL (v.get().time, 10 * TIME_HZ / 25);
-}
-
-#endif
index f3cbb504f0b874b9d5ef97b65d681b2e91ff33f3..f3784cad292d4ac5b2076d00e0a2174ae8075107 100644 (file)
@@ -19,7 +19,7 @@
 
 #include <iostream>
 #include <boost/test/unit_test.hpp>
-#include <libdcp/util.h>
+#include <dcp/util.h>
 #include "lib/ratio.h"
 #include "lib/util.h"
 
@@ -28,7 +28,7 @@ using std::ostream;
 namespace libdcp {
        
 ostream&
-operator<< (ostream& s, libdcp::Size const & t)
+operator<< (ostream& s, dcp::Size const & t)
 {
        s << t.width << "x" << t.height;
        return s;
@@ -42,38 +42,38 @@ BOOST_AUTO_TEST_CASE (ratio_test)
 
        Ratio const * r = Ratio::from_id ("119");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1290, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1290, 1080));
 
        r = Ratio::from_id ("133");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1440, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1440, 1080));
 
        r = Ratio::from_id ("137");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1480, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1480, 1080));
 
        r = Ratio::from_id ("138");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1485, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1485, 1080));
 
        r = Ratio::from_id ("166");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1800, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1800, 1080));
 
        r = Ratio::from_id ("178");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1920, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1920, 1080));
 
        r = Ratio::from_id ("185");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (1998, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (1998, 1080));
 
        r = Ratio::from_id ("239");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 858));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 858));
 
        r = Ratio::from_id ("full-frame");
        BOOST_CHECK (r);
-       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), libdcp::Size (2048, 1080)), libdcp::Size (2048, 1080));
+       BOOST_CHECK_EQUAL (fit_ratio_within (r->ratio(), dcp::Size (2048, 1080)), dcp::Size (2048, 1080));
 }
 
index d8caf17fa919c81a3657f5a459ba878671f95fc9..c4e4f155daaf96736b0cd073b3f33616ed679da4 100644 (file)
@@ -18,7 +18,7 @@
 */
 
 #include <boost/test/unit_test.hpp>
-#include <libdcp/stereo_picture_asset.h>
+#include <dcp/stereo_picture_mxf.h>
 #include "lib/film.h"
 #include "lib/dcp_content_type.h"
 #include "lib/image_content.h"
@@ -30,7 +30,7 @@ using std::string;
 using boost::shared_ptr;
 
 static void
-note (libdcp::NoteType, string n)
+note (dcp::NoteType, string n)
 {
        cout << n << "\n";
 }
@@ -62,10 +62,10 @@ BOOST_AUTO_TEST_CASE (recover_test)
        film->make_dcp ();
        wait_for_jobs ();
 
-       shared_ptr<libdcp::StereoPictureAsset> A (new libdcp::StereoPictureAsset ("build/test/recover_test", "original.mxf"));
-       shared_ptr<libdcp::StereoPictureAsset> B (new libdcp::StereoPictureAsset ("build/test/recover_test/video", "185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf"));
+       shared_ptr<dcp::StereoPictureMXF> A (new dcp::StereoPictureMXF ("build/test/recover_test/original.mxf"));
+       shared_ptr<dcp::StereoPictureMXF> B (new dcp::StereoPictureMXF ("build/test/recover_test/video/185_2K_58a090f8d70a2b410c534120d35e5256_24_bicubic_200000000_P_S_3D.mxf"));
 
-       libdcp::EqualityOptions eq;
+       dcp::EqualityOptions eq;
        eq.mxf_names_can_differ = true;
        BOOST_CHECK (A->equals (B, eq, boost::bind (&note, _1, _2)));
 }
diff --git a/test/repeat_frame_test.cc b/test/repeat_frame_test.cc
new file mode 100644 (file)
index 0000000..6cedf91
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+/* Test the repeat of frames by the player when putting a 24fps
+   source into a 48fps DCP.
+*/
+BOOST_AUTO_TEST_CASE (repeat_frame_test)
+{
+       shared_ptr<Film> film = new_test_film ("repeat_frame_test");
+       film->set_name ("repeat_frame_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/red_24.mp4"));
+       c->set_scale (VideoContentScale (Ratio::from_id ("185")));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       film->set_video_frame_rate (48);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       check_dcp ("test/data/repeat_frame_test", film->dir (film->dcp_name ()));
+}
+
index 9247159a7065b1a46ecf06d49f74f8cfdbab6695..3be251b3a75129c0daa83f8182ab19f7df51dc42 100644 (file)
@@ -34,13 +34,13 @@ resampler_test_one (int from, int to)
 
        /* 3 hours */
        int64_t const N = int64_t (from) * 60 * 60 * 3;
-       
+               
+       /* XXX: no longer checks anything */
        for (int64_t i = 0; i < N; i += 1000) {
                shared_ptr<AudioBuffers> a (new AudioBuffers (1, 1000));
                a->make_silent ();
-               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> r = resamp.run (a, i);
-               BOOST_CHECK_EQUAL (r.second, total_out);
-               total_out += r.first->frames ();
+               shared_ptr<const AudioBuffers> r = resamp.run (a);
+               total_out += r->frames ();
        }
 }      
                
index f0cf3fe4a5bf89a3b473abac7af75d9071b89076..bf8da8aac59ce4288e5ec1c7cb43b3e11779eab0 100644 (file)
@@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE (scaling_test)
 
        wait_for_jobs ();
        
-       imc->set_video_length (1);
+       imc->set_video_length (ContentTime::from_frames (1, 24));
 
        scaling_test_for (film, imc, "133", "185");
        scaling_test_for (film, imc, "185", "185");
diff --git a/test/seek_zero_test.cc b/test/seek_zero_test.cc
new file mode 100644 (file)
index 0000000..150e12d
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file  test/seek_zero_test.cc
+ *  @brief Test seek to zero with a raw FFmpegDecoder (without the player
+ *  confusing things as it might in ffmpeg_seek_test).
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/content_video.h"
+#include "test.h"
+
+using std::cout;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::optional;
+
+BOOST_AUTO_TEST_CASE (seek_zero_test)
+{
+       shared_ptr<Film> film = new_test_film ("seek_zero_test");
+       film->set_name ("seek_zero_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+       content->set_scale (VideoContentScale (Ratio::from_id ("185")));
+       film->examine_and_add_content (content);
+       wait_for_jobs ();
+
+       FFmpegDecoder decoder (content, film->log());
+       optional<ContentVideo> a = decoder.get_video (0, true);
+       optional<ContentVideo> b = decoder.get_video (0, true);
+       BOOST_CHECK_EQUAL (a->frame, 0);
+       BOOST_CHECK_EQUAL (b->frame, 0);
+}
index 82cbad080d9b8eafebf06f0effea0d4efaa12dc9..e4472ad4fef042f52f4822e56f5b82b2290a2009 100644 (file)
 */
 
 #include <boost/test/unit_test.hpp>
-#include <libdcp/cpl.h>
-#include <libdcp/dcp.h>
-#include <libdcp/sound_asset.h>
-#include <libdcp/sound_frame.h>
-#include <libdcp/reel.h>
+#include <dcp/cpl.h>
+#include <dcp/dcp.h>
+#include <dcp/sound_mxf.h>
+#include <dcp/sound_frame.h>
+#include <dcp/reel.h>
+#include <dcp/reel_sound_asset.h>
 #include "lib/sndfile_content.h"
 #include "lib/film.h"
 #include "lib/dcp_content_type.h"
@@ -52,56 +53,56 @@ static void test_silence_padding (int channels)
        boost::filesystem::path path = "build/test";
        path /= film_name;
        path /= film->dcp_name ();
-       libdcp::DCP check (path.string ());
+       dcp::DCP check (path.string ());
        check.read ();
 
-       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       shared_ptr<const dcp::ReelSoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
        BOOST_CHECK (sound_asset);
-       BOOST_CHECK (sound_asset->channels () == channels);
+       BOOST_CHECK_EQUAL (sound_asset->mxf()->channels (), channels);
 
        /* Sample index in the DCP */
        int n = 0;
        /* DCP sound asset frame */
        int frame = 0;
 
-       while (n < sound_asset->intrinsic_duration()) {
-               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+       while (n < sound_asset->mxf()->intrinsic_duration()) {
+               shared_ptr<const dcp::SoundFrame> sound_frame = sound_asset->mxf()->get_frame (frame++);
                uint8_t const * d = sound_frame->data ();
                
-               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->mxf()->channels())) {
 
-                       if (sound_asset->channels() > 0) {
+                       if (sound_asset->mxf()->channels() > 0) {
                                /* L should be silent */
                                int const sample = d[i + 0] | (d[i + 1] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
 
-                       if (sound_asset->channels() > 1) {
+                       if (sound_asset->mxf()->channels() > 1) {
                                /* R should be silent */
                                int const sample = d[i + 2] | (d[i + 3] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
                        
-                       if (sound_asset->channels() > 2) {
+                       if (sound_asset->mxf()->channels() > 2) {
                                /* Mono input so it will appear on centre */
                                int const sample = d[i + 7] | (d[i + 8] << 8);
                                BOOST_CHECK_EQUAL (sample, n);
                        }
 
-                       if (sound_asset->channels() > 3) {
+                       if (sound_asset->mxf()->channels() > 3) {
                                /* Lfe should be silent */
                                int const sample = d[i + 9] | (d[i + 10] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
 
-                       if (sound_asset->channels() > 4) {
+                       if (sound_asset->mxf()->channels() > 4) {
                                /* Ls should be silent */
                                int const sample = d[i + 11] | (d[i + 12] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
                        }
 
 
-                       if (sound_asset->channels() > 5) {
+                       if (sound_asset->mxf()->channels() > 5) {
                                /* Rs should be silent */
                                int const sample = d[i + 13] | (d[i + 14] << 8);
                                BOOST_CHECK_EQUAL (sample, 0);
diff --git a/test/skip_frame_test.cc b/test/skip_frame_test.cc
new file mode 100644 (file)
index 0000000..61176a7
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+/* Test the skip of frames by the player when putting a 48fps
+   source into a 24fps DCP.
+*/
+BOOST_AUTO_TEST_CASE (skip_frame_test)
+{
+       shared_ptr<Film> film = new_test_film ("skip_frame_test");
+       film->set_name ("skip_frame_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+       c->set_scale (VideoContentScale (Ratio::from_id ("185")));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+       film->write_metadata ();
+
+       film->set_video_frame_rate (24);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       check_dcp ("test/data/skip_frame_test", film->dir (film->dcp_name ()));
+}
+
index fed3ecabeb99283bcad12a4e0f542c5bef664b2c..1cd7e4a4270f60144ebd706cc15468c9193710e6 100644 (file)
@@ -73,11 +73,11 @@ BOOST_AUTO_TEST_CASE (stream_test)
        BOOST_CHECK_EQUAL (a.name, "hello there world");
        BOOST_CHECK_EQUAL (a.mapping.content_channels(), 2);
 
-       BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::LEFT), 1);
-       BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::RIGHT), 0);
-       BOOST_CHECK_EQUAL (a.mapping.get (0, libdcp::CENTRE), 1);
-       BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::LEFT), 0);
-       BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::RIGHT), 1);
-       BOOST_CHECK_EQUAL (a.mapping.get (1, libdcp::CENTRE), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::LEFT), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::RIGHT), 0);
+       BOOST_CHECK_EQUAL (a.mapping.get (0, dcp::CENTRE), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::LEFT), 0);
+       BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::RIGHT), 1);
+       BOOST_CHECK_EQUAL (a.mapping.get (1, dcp::CENTRE), 1);
 }
 
diff --git a/test/subrip_test.cc b/test/subrip_test.cc
new file mode 100644 (file)
index 0000000..f38ffb5
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+    Copyright (C) 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/test/unit_test.hpp>
+#include <dcp/subtitle_content.h>
+#include "lib/subrip.h"
+#include "lib/subrip_content.h"
+#include "lib/subrip_decoder.h"
+#include "lib/render_subtitles.h"
+#include "test.h"
+
+using std::list;
+using std::vector;
+using std::string;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+/** Test SubRip::convert_time */
+BOOST_AUTO_TEST_CASE (subrip_time_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("00:03:10,500"), ContentTime::from_seconds ((3 * 60) + 10 + 0.5));
+       BOOST_CHECK_EQUAL (SubRip::convert_time ("04:19:51,782"), ContentTime::from_seconds ((4 * 3600) + (19 * 60) + 51 + 0.782));
+}
+
+/** Test SubRip::convert_coordinate */
+BOOST_AUTO_TEST_CASE (subrip_coordinate_test)
+{
+       BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("foo:42"), 42);
+       BOOST_CHECK_EQUAL (SubRip::convert_coordinate ("X1:999"), 999);
+}
+
+/** Test SubRip::convert_content */
+BOOST_AUTO_TEST_CASE (subrip_content_test)
+{
+       list<string> c;
+       list<SubRipSubtitlePiece> p;
+       
+       c.push_back ("Hello world");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       c.clear ();
+
+       c.push_back ("<b>Hello world</b>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().bold, true);
+       c.clear ();
+
+       c.push_back ("<i>Hello world</i>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().italic, true);
+       c.clear ();
+
+       c.push_back ("<u>Hello world</u>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().underline, true);
+       c.clear ();
+
+       c.push_back ("{b}Hello world{/b}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().bold, true);
+       c.clear ();
+
+       c.push_back ("{i}Hello world{/i}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().italic, true);
+       c.clear ();
+
+       c.push_back ("{u}Hello world{/u}");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 1);
+       BOOST_CHECK_EQUAL (p.front().text, "Hello world");
+       BOOST_CHECK_EQUAL (p.front().underline, true);
+       c.clear ();
+
+       c.push_back ("<b>This is <i>nesting</i> of subtitles</b>");
+       p = SubRip::convert_content (c);
+       BOOST_CHECK_EQUAL (p.size(), 3);
+       list<SubRipSubtitlePiece>::iterator i = p.begin ();     
+       BOOST_CHECK_EQUAL (i->text, "This is ");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, false);
+       ++i;
+       BOOST_CHECK_EQUAL (i->text, "nesting");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, true);
+       ++i;
+       BOOST_CHECK_EQUAL (i->text, " of subtitles");
+       BOOST_CHECK_EQUAL (i->bold, true);
+       BOOST_CHECK_EQUAL (i->italic, false);
+       ++i;
+       c.clear ();
+}
+
+/** Test parsing of full SubRip file content */
+BOOST_AUTO_TEST_CASE (subrip_parse_test)
+{
+       shared_ptr<SubRipContent> content (new SubRipContent (shared_ptr<Film> (), "test/data/subrip.srt"));
+       content->examine (shared_ptr<Job> ());
+       BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
+
+       SubRip s (content);
+
+       vector<SubRipSubtitle>::const_iterator i = s._subtitles.begin();
+
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 49.200));
+       BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 52.351));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "This is a subtitle, and it goes over two lines.");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 52.440));
+       BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 54.351));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "We have emboldened this");
+       BOOST_CHECK_EQUAL (i->pieces.front().bold, true);
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 54.440));
+       BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 56.590));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "And italicised this.");
+       BOOST_CHECK_EQUAL (i->pieces.front().italic, true);
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((1 * 60) + 56.680));
+       BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((1 * 60) + 58.955));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Shall I compare thee to a summers' day?");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((2 * 60) + 0.840));
+       BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((2 * 60) + 3.400));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Is this a dagger I see before me?");
+
+       ++i;
+       BOOST_CHECK (i != s._subtitles.end ());
+       BOOST_CHECK_EQUAL (i->from, ContentTime::from_seconds ((3 * 60) + 54.560));
+       BOOST_CHECK_EQUAL (i->to, ContentTime::from_seconds ((3 * 60) + 56.471));
+       BOOST_CHECK_EQUAL (i->pieces.size(), 1);
+       BOOST_CHECK_EQUAL (i->pieces.front().text, "Hello world.");
+
+       ++i;
+       BOOST_CHECK (i == s._subtitles.end ());
+}
+
+/** Test rendering of a SubRip subtitle */
+BOOST_AUTO_TEST_CASE (subrip_render_test)
+{
+       shared_ptr<SubRipContent> content (new SubRipContent (shared_ptr<Film> (), "test/data/subrip.srt"));
+       content->examine (shared_ptr<Job> ());
+       BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
+
+       shared_ptr<Film> film = new_test_film ("subrip_render_test");
+
+       shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
+       list<shared_ptr<ContentTextSubtitle> > cts = decoder->get_text_subtitles (ContentTime::from_seconds (109), ContentTime::from_seconds (110));
+       BOOST_CHECK_EQUAL (cts.size(), 1);
+
+       PositionImage image = render_subtitles (cts.front()->subs, dcp::Size (1998, 1080));
+       write_image (image.image, "build/test/subrip_render_test.png");
+       check_file ("build/test/subrip_render_test.png", "test/data/subrip_render_test.png");
+}
index be2cf15389949c9ead1d23f5804b65c1c50ab99e..241032099b8922ebf7935c971978dcba6ac898f0 100644 (file)
@@ -19,8 +19,9 @@
 
 #include <vector>
 #include <list>
+#include <Magick++.h>
 #include <libxml++/libxml++.h>
-#include <libdcp/dcp.h>
+#include <dcp/dcp.h>
 #include "lib/config.h"
 #include "lib/util.h"
 #include "lib/ui_signaller.h"
@@ -29,6 +30,7 @@
 #include "lib/job.h"
 #include "lib/cross.h"
 #include "lib/server_finder.h"
+#include "lib/image.h"
 #define BOOST_TEST_DYN_LINK
 #define BOOST_TEST_MODULE dcpomatic_test
 #include <boost/test/unit_test.hpp>
@@ -53,15 +55,16 @@ public:
 
 struct TestConfig
 {
-       TestConfig()
+       TestConfig ()
        {
-               dcpomatic_setup();
+               dcpomatic_setup ();
 
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
                Config::instance()->set_default_dci_metadata (DCIMetadata ());
                Config::instance()->set_default_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
+               Config::instance()->set_default_audio_delay (0);
 
                ServerFinder::instance()->disable ();
 
@@ -98,8 +101,8 @@ void
 check_file (boost::filesystem::path ref, boost::filesystem::path check)
 {
        uintmax_t N = boost::filesystem::file_size (ref);
-       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
-       FILE* ref_file = fopen_boost (ref, "rb");
+       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
+       FILE* ref_file = fopen (ref.c_str(), "rb");
        BOOST_CHECK (ref_file);
        FILE* check_file = fopen_boost (check, "rb");
        BOOST_CHECK (check_file);
@@ -127,26 +130,26 @@ check_file (boost::filesystem::path ref, boost::filesystem::path check)
 }
 
 static void
-note (libdcp::NoteType t, string n)
+note (dcp::NoteType t, string n)
 {
-       if (t == libdcp::ERROR) {
+       if (t == dcp::ERROR) {
                cerr << n << "\n";
        }
 }
 
 void
-check_dcp (string ref, string check)
+check_dcp (boost::filesystem::path ref, boost::filesystem::path check)
 {
-       libdcp::DCP ref_dcp (ref);
+       dcp::DCP ref_dcp (ref);
        ref_dcp.read ();
-       libdcp::DCP check_dcp (check);
+       dcp::DCP check_dcp (check);
        check_dcp.read ();
 
-       libdcp::EqualityOptions options;
+       dcp::EqualityOptions options;
        options.max_mean_pixel_error = 5;
        options.max_std_dev_pixel_error = 5;
        options.max_audio_sample_error = 255;
-       options.cpl_names_can_differ = true;
+       options.cpl_annotation_texts_can_differ = true;
        options.mxf_names_can_differ = true;
        
        BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
@@ -230,3 +233,12 @@ wait_for_jobs ()
 
        ui_signaller->ui_idle ();
 }
+
+void
+write_image (shared_ptr<const Image> image, boost::filesystem::path file)
+{
+       using namespace MagickCore;
+
+       Magick::Image m (image->size().width, image->size().height, "ARGB", CharPixel, (void *) image->data()[0]);
+       m.write (file.string ());
+}
index dd007e8c9a15033e0db8112b4f3856bb5582c3e4..a42b41577917aa946c39f4aa6467a632ea449cba 100644 (file)
 #include <boost/filesystem.hpp>
 
 class Film;
+class Image;
 
 extern void wait_for_jobs ();
 extern boost::shared_ptr<Film> new_test_film (std::string);
-extern void check_dcp (std::string, std::string);
+extern void check_dcp (boost::filesystem::path, boost::filesystem::path);
+extern void check_file (boost::filesystem::path ref, boost::filesystem::path check);
 extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list<std::string>);
 extern void check_file (boost::filesystem::path, boost::filesystem::path);
 extern boost::filesystem::path test_film_dir (std::string);
+extern void write_image (boost::shared_ptr<const Image> image, boost::filesystem::path file);
index 750023d9f1a689bb4414706b66c352a4b6cda2d8..25b8140044afadbab3dd0f19d9e5cfcaeaf35a66 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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
@@ -54,6 +54,21 @@ BOOST_AUTO_TEST_CASE (md5_digest_test)
        BOOST_CHECK_THROW (md5_digest (p, shared_ptr<Job> ()), std::runtime_error);
 }
 
+/* Straightforward test of DCPTime::round_up */
+BOOST_AUTO_TEST_CASE (dcptime_round_up_test)
+{
+       BOOST_CHECK_EQUAL (DCPTime (0).round_up (DCPTime::HZ / 2), DCPTime (0));
+       BOOST_CHECK_EQUAL (DCPTime (1).round_up (DCPTime::HZ / 2), DCPTime (2));
+       BOOST_CHECK_EQUAL (DCPTime (2).round_up (DCPTime::HZ / 2), DCPTime (2));
+       BOOST_CHECK_EQUAL (DCPTime (3).round_up (DCPTime::HZ / 2), DCPTime (4));
+       
+       BOOST_CHECK_EQUAL (DCPTime (0).round_up (DCPTime::HZ / 42), DCPTime (0));
+       BOOST_CHECK_EQUAL (DCPTime (1).round_up (DCPTime::HZ / 42), DCPTime (42));
+       BOOST_CHECK_EQUAL (DCPTime (42).round_up (DCPTime::HZ / 42), DCPTime (42));
+       BOOST_CHECK_EQUAL (DCPTime (43).round_up (DCPTime::HZ / 42), DCPTime (84));
+}
+
+
 BOOST_AUTO_TEST_CASE (divide_with_round_test)
 {
        BOOST_CHECK_EQUAL (divide_with_round (0, 4), 0);
index 676f471049cfcf835e5da2bde766ef3ee053f687..ba5aabb7c333243fb8009ad22b08c8bb25b57096 100644 (file)
@@ -10,7 +10,7 @@ def configure(conf):
                               """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
 
 def build(bld):
-    obj = bld(features = 'cxx cxxprogram')
+    obj = bld(features='cxx cxxprogram')
     obj.name   = 'unit-tests'
     obj.uselib = 'BOOST_TEST BOOST_THREAD DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
     obj.use    = 'libdcpomatic'
@@ -19,7 +19,6 @@ def build(bld):
                  audio_analysis_test.cc
                  audio_delay_test.cc
                  audio_mapping_test.cc
-                 audio_merger_test.cc
                  black_fill_test.cc
                  client_server_test.cc
                  colour_conversion_test.cc
@@ -34,13 +33,16 @@ def build(bld):
                  job_test.cc
                  make_black_test.cc
                  pixel_formats_test.cc
-                 play_test.cc
                  ratio_test.cc
+                 repeat_frame_test.cc
                  recover_test.cc
                  resampler_test.cc
                  scaling_test.cc
+                 seek_zero_test.cc
                  silence_padding_test.cc
+                 skip_frame_test.cc
                  stream_test.cc
+                 subrip_test.cc
                  test.cc
                  threed_test.cc
                  util_test.cc
@@ -48,3 +50,14 @@ def build(bld):
 
     obj.target = 'unit-tests'
     obj.install_path = ''
+
+    obj = bld(features='cxx cxxprogram')
+    obj.name   = 'long-unit-tests'
+    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+    obj.use    = 'libdcpomatic'
+    obj.source = """
+                 test.cc
+                 """
+
+    obj.target = 'long-unit-tests'
+    obj.install_path = ''
diff --git a/wscript b/wscript
index fe834f34378b3fe6c88dc76dc39a2a53b11ba489..88e928f12dec710dfb2a72ec54199cbcb0be439d 100644 (file)
--- a/wscript
+++ b/wscript
@@ -3,7 +3,7 @@ import os
 import sys
 
 APPNAME = 'dcpomatic'
-VERSION = '1.66.9devel'
+VERSION = '2.0.0devel'
 
 def options(opt):
     opt.load('compiler_cxx')
@@ -52,9 +52,9 @@ def dynamic_openjpeg(conf):
     conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.1', mandatory=True)
 
 def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
-    conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
+    conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
     conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
-    conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+    conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp', 'kumu-libdcp']
     conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
 
     if static_boost:
@@ -78,7 +78,7 @@ def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
         conf.env.LIB_DCP.append('ssh')
 
 def dynamic_dcp(conf):
-    conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+    conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
     conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
 
 def dynamic_ssh(conf):
@@ -299,6 +299,8 @@ def configure(conf):
     conf.check_cfg(package='glib-2.0', args='--cflags --libs', uselib_store='GLIB', mandatory=True)
     conf.check_cfg(package= '', path=conf.options.magickpp_config, args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
     conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
+    conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
+    conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
 
     conf.check_cc(fragment="""
                            #include <glib.h>