Merge master.
authorCarl Hetherington <cth@carlh.net>
Wed, 10 Apr 2013 18:43:01 +0000 (19:43 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 10 Apr 2013 18:43:01 +0000 (19:43 +0100)
97 files changed:
.gitignore
doc/design/content.tex [new file with mode: 0644]
run/dvdomatic
src/lib/ab_transcode_job.cc
src/lib/ab_transcode_job.h
src/lib/ab_transcoder.cc
src/lib/ab_transcoder.h
src/lib/analyse_audio_job.cc
src/lib/audio_content.cc [new file with mode: 0644]
src/lib/audio_content.h [new file with mode: 0644]
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_mapping.cc [new file with mode: 0644]
src/lib/audio_mapping.h [new file with mode: 0644]
src/lib/audio_source.cc
src/lib/config.cc
src/lib/config.h
src/lib/content.cc [new file with mode: 0644]
src/lib/content.h [new file with mode: 0644]
src/lib/dci_metadata.cc
src/lib/dci_metadata.h
src/lib/dcp_video_frame.cc
src/lib/decoder.cc
src/lib/decoder.h
src/lib/decoder_factory.cc [deleted file]
src/lib/decoder_factory.h [deleted file]
src/lib/encoder.cc
src/lib/encoder.h
src/lib/examine_content_job.cc
src/lib/examine_content_job.h
src/lib/exceptions.h
src/lib/ffmpeg_content.cc [new file with mode: 0644]
src/lib/ffmpeg_content.h [new file with mode: 0644]
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/film.cc
src/lib/film.h
src/lib/filter_graph.cc
src/lib/filter_graph.h
src/lib/format.cc
src/lib/format.h
src/lib/imagemagick_content.cc [new file with mode: 0644]
src/lib/imagemagick_content.h [new file with mode: 0644]
src/lib/imagemagick_decoder.cc
src/lib/imagemagick_decoder.h
src/lib/job.cc
src/lib/job.h
src/lib/matcher.cc
src/lib/options.h [deleted file]
src/lib/player.cc [new file with mode: 0644]
src/lib/player.h [new file with mode: 0644]
src/lib/playlist.cc [new file with mode: 0644]
src/lib/playlist.h [new file with mode: 0644]
src/lib/scp_dcp_job.h
src/lib/server.cc
src/lib/server.h
src/lib/sndfile_content.cc [new file with mode: 0644]
src/lib/sndfile_content.h [new file with mode: 0644]
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/stream.cc [deleted file]
src/lib/stream.h [deleted file]
src/lib/transcode_job.cc
src/lib/transcode_job.h
src/lib/transcoder.cc
src/lib/transcoder.h
src/lib/types.cc [new file with mode: 0644]
src/lib/types.h [new file with mode: 0644]
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc [new file with mode: 0644]
src/lib/video_content.h [new file with mode: 0644]
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/lib/video_source.cc
src/lib/video_source.h
src/lib/writer.cc
src/lib/wscript
src/tools/dvdomatic.cc
src/tools/makedcp.cc
src/tools/servomatictest.cc
src/wx/audio_dialog.cc
src/wx/audio_dialog.h
src/wx/audio_mapping_view.cc [new file with mode: 0644]
src/wx/audio_mapping_view.h [new file with mode: 0644]
src/wx/audio_plot.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/imagemagick_content_dialog.cc [new file with mode: 0644]
src/wx/imagemagick_content_dialog.h [new file with mode: 0644]
src/wx/job_manager_view.cc
src/wx/properties_dialog.cc
src/wx/wscript
test/test.cc
wscript

index cc3351558baabbdbcfe257501c77ad3c333056cf..70738a18f475189d9df36642a39ba509060e226c 100644 (file)
@@ -15,6 +15,9 @@ sync
 doc/manual/html
 doc/manual/pdf
 doc/manual/extensions.ent
+doc/design/*.pdf
+doc/design/*.log
+doc/design/*.aux
 .be/id-cache
 *.pyc
 GPATH
diff --git a/doc/design/content.tex b/doc/design/content.tex
new file mode 100644 (file)
index 0000000..0f5f170
--- /dev/null
@@ -0,0 +1,195 @@
+\documentclass{article}
+\begin{document}
+
+\section{Status quo}
+
+As at 0.78 there is an unfortunate mish-mash of code to handle the
+input `content' the goes into a DCP.
+
+The Film has a `content' file name.  This is guessed to be either a
+movie (for FFmpeg) or a still-image (for ImageMagick) based on its
+extension.  We also have `external audio', which is a set of WAV files
+for libsndfile, and a flag to enable that.
+
+The `content' file is badly named and limiting.  We can't have
+multiple content files, and it's not really the `content' as such (it
+used to be, but increasingly it's only a part of the content, on equal
+footing with `external' audio).
+
+The choice of sources for sound is expressed clumsily by the
+AudioStream class hierarchy.
+
+
+\section{Targets}
+
+We want to be able to implement the following:
+
+\begin{itemize}
+\item Immediately:
+\begin{itemize}
+\item Multiple still images, each with their own duration, made into a `slide-show'
+\item Lack of bugs in adding WAV-file audio to still images.
+\item External subtitle files (either XML or SRT) to be converted to XML subtitles in the DCP.
+\end{itemize}
+
+\item In the future:
+\begin{itemize}
+\item Playlist-style multiple video / audio (perhaps).
+\end{itemize}
+\end{itemize}
+
+
+\section{Content hierarchy}
+
+One idea is to have a hierarchy of Content classes (\texttt{Content},
+\texttt{\{Video/Audio\}Content}, \texttt{FFmpegContent}, \texttt{ImageMagickContent},
+\texttt{SndfileContent}).
+
+Then the Film has a list of these, and decides what to put into the
+DCP based on some rules.  These rules would probably be fixed (for
+now), with the possibility to expand later into some kind of playlist.
+
+
+\section{Immediate questions}
+
+\subsection{What Film attributes are video-content specific, and which are general?}
+
+Questionable attributes:
+
+\begin{itemize}
+\item Trust content header
+\item Crop
+\item Filters
+
+Post-processing (held as part of the filters description) is done in
+the encoder, by which time all knowledge of the source is lost.
+
+\item Scaler
+\item Trim start/end
+
+Messily tied in with the encoding side.  We want to implement this
+using start/end point specifications in the DCP reel, otherwise
+modifying the trim points requires a complete re-encode.
+
+\item Audio gain
+\item Audio delay
+\item With subtitles
+\item Subtitle offset/scale
+\item Colour LUT
+\end{itemize}
+
+Attributes that I think must remain in Film:
+\begin{itemize}
+\item DCP content type
+\item Format
+\item A/B
+\item J2K bandwidth
+\end{itemize}
+
+Part of the consideration here is that per-content attributes need to
+be represented in the GUI differently to how things are represented
+now.
+
+Bear in mind also that, as it stands, the only options for video are:
+
+\begin{enumerate}
+\item An FFmpeg video
+\item A set of stills
+\end{enumerate}
+
+and so the need for multiple scalers, crop and filters is
+questionable.  Also, there is one set of audio (either from WAVs or
+from the FFMpeg file), so per-content audio gain/delay is also
+questionable.  Trust content header is only applicable for FFmpeg
+content, really.  Similarly trim, with-subtitles, subtitle details,
+colour LUT; basically none of it is really important right now.
+
+Hence it may be sensible to keep everything in Film and move it later
+along YAGNI lines.
+
+
+\subsection{Who answers questions like: ``what is the length of video?''?}
+
+If we have FFmpeg video, the question is easy to answer.  For a set of
+stills, it is less easy.  Who knows that we are sticking them all
+together end-to-end, with different durations for each?
+
+If we have one-content-object equalling one file, the content objects
+will presumably know how long their file should be displayed for.
+There would appear to be two options following this:
+
+\begin{enumerate}
+\item There is one \texttt{ImageMagickDecoder} which is fed all the
+  files, and outputs them in order.  The magic knowledge is then
+  within this class, really.
+\item There are multiple \texttt{ImageMagickDecoder} classes, one per
+  \texttt{..Content}, and some controlling (`playlist') class to manage
+  them.  The `playlist' is then itself a
+  \texttt{\{Video/Audio\}Source}, and has the magic knowledge.
+\end{enumerate}
+
+
+\section{Playlist approach}
+
+Let's try the playlist approach.  We define a hierarchy of content classes:
+
+\begin{verbatim}
+
+class Content
+{
+public:
+  boost::filesystem::path file () const;
+};
+
+class VideoContent : virtual public Content
+{
+public:
+  VideoContentFrame video_length () const;
+  float video_frame_rate () const;
+  libdcp::Size size () const;
+
+};
+
+class AudioContent : virtual public Content
+{
+
+};
+
+class FFmpegContent : public VideoContent, public AudioContent
+{
+public:
+  .. stream stuff ..
+};
+
+class ImageMagickContent : public VideoContent
+{
+
+};
+
+class SndfileContent : public AudioContent
+{
+public:
+  .. channel allocation for this file ..
+};
+\end{verbatim}
+
+Then Film has a \texttt{Playlist} which has a
+\texttt{vector<shared\_ptr<Content> >}.  It can answer questions
+about audio/video length, frame rate, audio channels and so on.
+
+\texttt{Playlist} can also be a source of video and audio, so clients can do:
+
+\begin{verbatim}
+shared_ptr<Playlist> p = film->playlist ();
+p->Video.connect (foo);
+p->Audio.connect (foo);
+while (!p->pass ()) {
+  /* carry on */
+}
+\end{verbatim}
+
+Playlist could be created on-demand for all the difference it would
+make.  And perhaps it should, since it will hold Decoders which are
+probably run-once.
+
+\end{document}
index 147c001cdf3c8fc8e4be4fe0ff8f8f2b0d65e35f..dbc63d44a0d1d5db9eb9ada88cc95fd6e16f5687 100755 (executable)
@@ -3,7 +3,7 @@
 export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
 if [ "$1" == "--debug" ]; then
     shift
-    gdb --args build/src/tools/dvdomatic "$*"
+    gdb --args build/src/tools/dvdomatic $*
 elif [ "$1" == "--valgrind" ]; then
     shift
     valgrind --tool="memcheck" build/src/tools/dvdomatic $*
@@ -11,5 +11,5 @@ elif [ "$1" == "--i18n" ]; then
     shift
     LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 build/src/tools/dvdomatic "$*"
 else
-    build/src/tools/dvdomatic "$*"
+    build/src/tools/dvdomatic $*
 fi
index 4ffdd9af6b815cbe6c572d1e72a336e82ce0445b..2bdff47de684827ab0e0ec15664737cb624609ed 100644 (file)
@@ -32,11 +32,9 @@ using std::string;
 using boost::shared_ptr;
 
 /** @param f Film to compare.
- *  @param o Decode options.
  */
-ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f, DecodeOptions o)
+ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f)
        : Job (f)
-       , _decode_opt (o)
 {
        _film_b.reset (new Film (*_film));
        _film_b->set_scaler (Config::instance()->reference_scaler ());
@@ -54,7 +52,7 @@ ABTranscodeJob::run ()
 {
        try {
                /* _film_b is the one with reference filters */
-               ABTranscoder w (_film_b, _film, _decode_opt, this, shared_ptr<Encoder> (new Encoder (_film)));
+               ABTranscoder w (_film_b, _film, shared_from_this ());
                w.go ();
                set_progress (1);
                set_state (FINISHED_OK);
index 8e3cbe2d8f8990586218b8925f3a9b5ed1514bee..cd82d4247ffdf7dfc49095993466792b618c1f0f 100644 (file)
@@ -23,7 +23,6 @@
 
 #include <boost/shared_ptr.hpp>
 #include "job.h"
-#include "options.h"
 
 class Film;
 
@@ -38,16 +37,13 @@ class ABTranscodeJob : public Job
 {
 public:
        ABTranscodeJob (
-               boost::shared_ptr<Film> f,
-               DecodeOptions o
+               boost::shared_ptr<Film> f
                );
 
        std::string name () const;
        void run ();
 
 private:
-       DecodeOptions _decode_opt;
-       
        /** Copy of our Film using the reference filters and scaler */
        boost::shared_ptr<Film> _film_b;
 };
index 3a1cd83d77473ccfed80da473c39244e50ff80c6..81aa5a4bae2192d5622689f32f57ee9f7724d2ae 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include "ab_transcoder.h"
 #include "film.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
 #include "encoder.h"
 #include "job.h"
-#include "options.h"
 #include "image.h"
-#include "decoder_factory.h"
+#include "player.h"
 #include "matcher.h"
 #include "delay_line.h"
 #include "gain.h"
@@ -49,31 +46,23 @@ using boost::dynamic_pointer_cast;
  *  @param e Encoder to use.
  */
 
-ABTranscoder::ABTranscoder (
-       shared_ptr<Film> a, shared_ptr<Film> b, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
+ABTranscoder::ABTranscoder (shared_ptr<Film> a, shared_ptr<Film> b, shared_ptr<Job> j)
        : _film_a (a)
        , _film_b (b)
+       , _player_a (_film_a->player ())
+       , _player_b (_film_b->player ())
        , _job (j)
-       , _encoder (e)
+       , _encoder (new Encoder (_film_a))
        , _combiner (new Combiner (a->log()))
 {
-       _da = decoder_factory (_film_a, o);
-       _db = decoder_factory (_film_b, o);
-
-       if (_film_a->audio_stream()) {
-               shared_ptr<AudioStream> st = _film_a->audio_stream();
-               _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate()));
-               _delay_line.reset (new DelayLine (_film_a->log(), st->channels(), _film_a->audio_delay() * st->sample_rate() / 1000));
+       if (_film_a->has_audio ()) {
+               _matcher.reset (new Matcher (_film_a->log(), _film_a->audio_frame_rate(), _film_a->video_frame_rate()));
+               _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_channels(), _film_a->audio_delay() * _film_a->audio_frame_rate() / 1000));
                _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain()));
        }
 
-       /* Set up the decoder to use the film's set streams */
-       _da.video->set_subtitle_stream (_film_a->subtitle_stream ());
-       _db.video->set_subtitle_stream (_film_a->subtitle_stream ());
-       _da.audio->set_audio_stream (_film_a->audio_stream ());
-
-       _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
-       _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
+       _player_a->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3));
+       _player_b->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3));
 
        if (_matcher) {
                _combiner->connect_video (_matcher);
@@ -83,7 +72,7 @@ ABTranscoder::ABTranscoder (
        }
        
        if (_matcher && _delay_line) {
-               _da.audio->connect_audio (_delay_line);
+               _player_a->connect_audio (_delay_line);
                _delay_line->connect_audio (_matcher);
                _matcher->connect_audio (_gain);
                _gain->connect_audio (_encoder);
@@ -95,23 +84,17 @@ ABTranscoder::go ()
 {
        _encoder->process_begin ();
 
-       bool done[3] = { false, false, false };
+       bool done[2] = { false, false };
        
        while (1) {
-               done[0] = _da.video->pass ();
-               done[1] = _db.video->pass ();
-               
-               if (!done[2] && _da.audio && dynamic_pointer_cast<Decoder> (_da.audio) != dynamic_pointer_cast<Decoder> (_da.video)) {
-                       done[2] = _da.audio->pass ();
-               } else {
-                       done[2] = true;
-               }
+               done[0] = _player_a->pass ();
+               done[1] = _player_b->pass ();
 
                if (_job) {
-                       _da.video->set_progress (_job);
+                       _player_a->set_progress (_job);
                }
 
-               if (done[0] && done[1] && done[2]) {
+               if (done[0] && done[1]) {
                        break;
                }
        }
index 58a08af04ca7fbe67eddd78937af60c0fd53b51d..b1b01d724f7b4818400122c7347aafe8d9f0bcad 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include "util.h"
-#include "decoder_factory.h"
 
 class Job;
 class Encoder;
-class VideoDecoder;
-class AudioDecoder;
 class Image;
 class Log;
-class Subtitle;
 class Film;
 class Matcher;
 class DelayLine;
 class Gain;
 class Combiner;
+class Player;
 
 /** @class ABTranscoder
  *  @brief A transcoder which uses one Film for the left half of the screen, and a different one
@@ -50,9 +47,7 @@ public:
        ABTranscoder (
                boost::shared_ptr<Film> a,
                boost::shared_ptr<Film> b,
-               DecodeOptions o,
-               Job* j,
-               boost::shared_ptr<Encoder> e
+               boost::shared_ptr<Job> j
                );
        
        void go ();
@@ -60,10 +55,10 @@ public:
 private:
        boost::shared_ptr<Film> _film_a;
        boost::shared_ptr<Film> _film_b;
-       Job* _job;
+       boost::shared_ptr<Player> _player_a;
+       boost::shared_ptr<Player> _player_b;
+       boost::shared_ptr<Job> _job;
        boost::shared_ptr<Encoder> _encoder;
-       Decoders _da;
-       Decoders _db;
        boost::shared_ptr<Combiner> _combiner;
        boost::shared_ptr<Matcher> _matcher;
        boost::shared_ptr<DelayLine> _delay_line;
index 43eecbcbd774f66d4b84e70e714171aa86304aa2..50096d7c125f183d694d4534febbadb4f20f4739 100644 (file)
@@ -21,9 +21,7 @@
 #include "analyse_audio_job.h"
 #include "compose.hpp"
 #include "film.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "audio_decoder.h"
+#include "player.h"
 
 #include "i18n.h"
 
@@ -52,29 +50,18 @@ AnalyseAudioJob::name () const
 void
 AnalyseAudioJob::run ()
 {
-       if (!_film->audio_stream () || !_film->length()) {
-               set_progress (1);
-               set_state (FINISHED_ERROR);
-               return;
-       }
-               
-       DecodeOptions options;
-       options.decode_video = false;
-
-       Decoders decoders = decoder_factory (_film, options);
-       assert (decoders.audio);
+       shared_ptr<Player> player = _film->player ();
+       player->disable_video ();
        
-       decoders.audio->set_audio_stream (_film->audio_stream ());
-       decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+       player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
 
-       int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->source_frame_rate());
-       _samples_per_point = max (int64_t (1), total_audio_frames / _num_points);
+       _samples_per_point = max (int64_t (1), _film->audio_length() / _num_points);
 
-       _current.resize (_film->audio_stream()->channels ());
-       _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels()));
+       _current.resize (MAX_AUDIO_CHANNELS);
+       _analysis.reset (new AudioAnalysis (MAX_AUDIO_CHANNELS));
                         
-       while (!decoders.audio->pass()) {
-               set_progress (float (_done) / total_audio_frames);
+       while (!player->pass()) {
+               set_progress (float (_done) / _film->audio_length ());
        }
 
        _analysis->write (_film->audio_analysis_path ());
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
new file mode 100644 (file)
index 0000000..9968f47
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+    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 <libcxml/cxml.h>
+#include "audio_content.h"
+
+using boost::shared_ptr;
+
+int const AudioContentProperty::AUDIO_CHANNELS = 200;
+int const AudioContentProperty::AUDIO_LENGTH = 201;
+int const AudioContentProperty::AUDIO_FRAME_RATE = 202;
+
+AudioContent::AudioContent (boost::filesystem::path f)
+       : Content (f)
+{
+
+}
+
+AudioContent::AudioContent (shared_ptr<const cxml::Node> node)
+       : Content (node)
+{
+
+}
+
+AudioContent::AudioContent (AudioContent const & o)
+       : Content (o)
+{
+
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
new file mode 100644 (file)
index 0000000..d5dbf26
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_CONTENT_H
+#define DVDOMATIC_AUDIO_CONTENT_H
+
+#include "content.h"
+#include "util.h"
+
+namespace cxml {
+       class Node;
+}
+
+class AudioContentProperty
+{
+public:
+       static int const AUDIO_CHANNELS;
+       static int const AUDIO_LENGTH;
+       static int const AUDIO_FRAME_RATE;
+};
+
+class AudioContent : public virtual Content
+{
+public:
+       AudioContent (boost::filesystem::path);
+       AudioContent (boost::shared_ptr<const cxml::Node>);
+       AudioContent (AudioContent const &);
+
+        virtual int audio_channels () const = 0;
+        virtual ContentAudioFrame audio_length () const = 0;
+        virtual int audio_frame_rate () const = 0;
+        virtual int64_t audio_channel_layout () const = 0;
+       
+};
+
+#endif
index a54c14843927449b4a62f00665ea83020a53bf70..df13a984a4b36416fb1120b6512aca4537484f6c 100644 (file)
 */
 
 #include "audio_decoder.h"
-#include "stream.h"
 
 using boost::optional;
 using boost::shared_ptr;
 
-AudioDecoder::AudioDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> f)
+       : Decoder (f)
 {
 
 }
-
-void
-AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s)
-{
-       _audio_stream = s;
-}
index 9bef8e0e7cf579f9903a3abed1dd7b684d739860..24e2796aeac07badddd987d97074a7c8faf037c5 100644 (file)
 #define DVDOMATIC_AUDIO_DECODER_H
 
 #include "audio_source.h"
-#include "stream.h"
 #include "decoder.h"
 
+class AudioContent;
+
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
 class AudioDecoder : public AudioSource, public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<Film>, DecodeOptions);
-
-       virtual void set_audio_stream (boost::shared_ptr<AudioStream>);
-
-       /** @return Audio stream that we are using */
-       boost::shared_ptr<AudioStream> audio_stream () const {
-               return _audio_stream;
-       }
-
-       /** @return All available audio streams */
-       std::vector<boost::shared_ptr<AudioStream> > audio_streams () const {
-               return _audio_streams;
-       }
-
-protected:
-       /** Audio stream that we are using */
-       boost::shared_ptr<AudioStream> _audio_stream;
-       /** All available audio streams */
-       std::vector<boost::shared_ptr<AudioStream> > _audio_streams;
+       AudioDecoder (boost::shared_ptr<const Film>);
 };
 
 #endif
diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc
new file mode 100644 (file)
index 0000000..b85ea73
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+    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/lexical_cast.hpp>
+#include <libcxml/cxml.h>
+#include "audio_mapping.h"
+
+using std::list;
+using std::cout;
+using std::make_pair;
+using std::pair;
+using std::string;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using boost::dynamic_pointer_cast;
+
+void
+AudioMapping::add (Channel c, libdcp::Channel d)
+{
+       _content_to_dcp.push_back (make_pair (c, d));
+}
+
+/* XXX: this is grotty */
+int
+AudioMapping::dcp_channels () const
+{
+       for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (((int) i->second) > 2) {
+                       return 6;
+               }
+       }
+
+       return 2;
+}
+
+list<AudioMapping::Channel>
+AudioMapping::dcp_to_content (libdcp::Channel d) const
+{
+       list<AudioMapping::Channel> c;
+       for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (i->second == d) {
+                       c.push_back (i->first);
+               }
+       }
+
+       return c;
+}
+
+list<AudioMapping::Channel>
+AudioMapping::content_channels () const
+{
+       list<AudioMapping::Channel> c;
+       for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (find (c.begin(), c.end(), i->first) == c.end ()) {
+                       c.push_back (i->first);
+               }
+       }
+
+       return c;
+}
+
+list<libdcp::Channel>
+AudioMapping::content_to_dcp (Channel c) const
+{
+       list<libdcp::Channel> d;
+       for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               if (i->first == c) {
+                       d.push_back (i->second);
+               }
+       }
+
+       return d;
+}
+
+void
+AudioMapping::as_xml (xmlpp::Node* node) const
+{
+       for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) {
+               xmlpp::Node* t = node->add_child ("Map");
+               shared_ptr<const AudioContent> c = i->first.content.lock ();
+               t->add_child ("Content")->add_child_text (c->file().string ());
+               t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first.index));
+               t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second));
+       }
+}
+
+void
+AudioMapping::set_from_xml (ContentList const & content, shared_ptr<const cxml::Node> node)
+{
+       list<shared_ptr<cxml::Node> > const c = node->node_children ("Map");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               string const c = (*i)->string_child ("Content");
+               ContentList::const_iterator j = content.begin ();
+               while (j != content.end() && (*j)->file().string() != c) {
+                       ++j;
+               }
+
+               if (j == content.end ()) {
+                       continue;
+               }
+
+               shared_ptr<const AudioContent> ac = dynamic_pointer_cast<AudioContent> (*j);
+               assert (ac);
+
+               add (AudioMapping::Channel (ac, (*i)->number_child<int> ("ContentIndex")), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP")));
+       }
+}
+
+bool
+operator== (AudioMapping::Channel const & a, AudioMapping::Channel const & b)
+{
+       shared_ptr<const AudioContent> sa = a.content.lock ();
+       shared_ptr<const AudioContent> sb = b.content.lock ();
+       return sa == sb && a.index == b.index;
+}
diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h
new file mode 100644 (file)
index 0000000..4f2cdb7
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_MAPPING_H
+#define DVDOMATIC_AUDIO_MAPPING_H
+
+#include <list>
+#include <string>
+#include <libdcp/types.h>
+#include <boost/shared_ptr.hpp>
+#include "audio_content.h"
+
+class AudioMapping
+{
+public:
+       void as_xml (xmlpp::Node *) const;
+       void set_from_xml (ContentList const &, boost::shared_ptr<const cxml::Node>);
+       
+       struct Channel {
+               Channel (boost::weak_ptr<const AudioContent> c, int i)
+                       : content (c)
+                       , index (i)
+               {}
+               
+               boost::weak_ptr<const AudioContent> content;
+               int index;
+       };
+
+       void add (Channel, libdcp::Channel);
+
+       int dcp_channels () const;
+       std::list<Channel> dcp_to_content (libdcp::Channel) const;
+       std::list<std::pair<Channel, libdcp::Channel> > content_to_dcp () const {
+               return _content_to_dcp;
+       }
+
+       std::list<Channel> content_channels () const;
+       std::list<libdcp::Channel> content_to_dcp (Channel) const;
+
+private:
+       std::list<std::pair<Channel, libdcp::Channel> > _content_to_dcp;
+};
+
+extern bool operator== (AudioMapping::Channel const &, AudioMapping::Channel const &);
+
+#endif
index 53b0dda1500a705ac314d9a5c5bd314c896140ba..99b59759d07eac7250126429515f7c00863f9677 100644 (file)
 #include "audio_sink.h"
 
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::bind;
 
+static void
+process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<AudioBuffers> audio)
+{
+       shared_ptr<AudioSink> p = sink.lock ();
+       if (p) {
+               p->process_audio (audio);
+       }
+}
+
 void
 AudioSource::connect_audio (shared_ptr<AudioSink> s)
 {
-       Audio.connect (bind (&AudioSink::process_audio, s, _1));
+       Audio.connect (bind (process_audio_proxy, weak_ptr<AudioSink> (s), _1));
 }
index 5dce3748d72c4c7bd75df294858e945301e42637..2defa053921ccc079cc9e0bd14c79a2f3ebe3a99 100644 (file)
@@ -22,6 +22,7 @@
 #include <fstream>
 #include <glib.h>
 #include <boost/filesystem.hpp>
+#include <libcxml/cxml.h>
 #include "config.h"
 #include "server.h"
 #include "scaler.h"
@@ -34,7 +35,9 @@ using std::vector;
 using std::ifstream;
 using std::string;
 using std::ofstream;
+using std::list;
 using boost::shared_ptr;
+using boost::optional;
 
 Config* Config::_instance = 0;
 
@@ -52,8 +55,51 @@ Config::Config ()
        _allowed_dcp_frame_rates.push_back (48);
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
+
+       if (!boost::filesystem::exists (file (false))) {
+               read_old_metadata ();
+               return;
+       }
+
+       cxml::File f (file (false), "Config");
+       optional<string> c;
+
+       _num_local_encoding_threads = f.number_child<int> ("NumLocalEncodingThreads");
+       _default_directory = f.string_child ("DefaultDirectory");
+       _server_port = f.number_child<int> ("ServerPort");
+       c = f.optional_string_child ("ReferenceScaler");
+       if (c) {
+               _reference_scaler = Scaler::from_id (c.get ());
+       }
+
+       list<shared_ptr<cxml::Node> > filters = f.node_children ("ReferenceFilter");
+       for (list<shared_ptr<cxml::Node> >::iterator i = filters.begin(); i != filters.end(); ++i) {
+               _reference_filters.push_back (Filter::from_id ((*i)->content ()));
+       }
        
-       ifstream f (file().c_str ());
+       list<shared_ptr<cxml::Node> > servers = f.node_children ("Server");
+       for (list<shared_ptr<cxml::Node> >::iterator i = servers.begin(); i != servers.end(); ++i) {
+               _servers.push_back (new ServerDescription (*i));
+       }
+
+       _tms_ip = f.string_child ("TMSIP");
+       _tms_path = f.string_child ("TMSPath");
+       _tms_user = f.string_child ("TMSUser");
+       _tms_password = f.string_child ("TMSPassword");
+
+       c = f.optional_string_child ("SoundProcessor");
+       if (c) {
+               _sound_processor = SoundProcessor::from_id (c.get ());
+       }
+
+       _language = f.optional_string_child ("Language");
+       _default_dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+}
+
+void
+Config::read_old_metadata ()
+{
+       ifstream f (file(true).c_str ());
        string line;
        while (getline (f, line)) {
                if (line.empty ()) {
@@ -98,17 +144,21 @@ Config::Config ()
                        _language = v;
                }
 
-               _default_dci_metadata.read (k, v);
+               _default_dci_metadata.read_old_metadata (k, v);
        }
 }
 
 /** @return Filename to write configuration to */
 string
-Config::file () const
+Config::file (bool old) const
 {
        boost::filesystem::path p;
        p /= g_get_user_config_dir ();
-       p /= N_(".dvdomatic");
+       if (old) {
+               p /= ".dvdomatic";
+       } else {
+               p /= ".dvdomatic.xml";
+       }
        return p.string ();
 }
 
@@ -127,35 +177,38 @@ Config::instance ()
 void
 Config::write () const
 {
-       ofstream f (file().c_str ());
-       f << N_("num_local_encoding_threads ") << _num_local_encoding_threads << N_("\n")
-         << N_("default_directory ") << _default_directory << N_("\n")
-         << N_("server_port ") << _server_port << N_("\n");
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("Config");
 
+       root->add_child("NumLocalEncodingThreads")->add_child_text (boost::lexical_cast<string> (_num_local_encoding_threads));
+       root->add_child("DefaultDirectory")->add_child_text (_default_directory);
+       root->add_child("ServerPort")->add_child_text (boost::lexical_cast<string> (_server_port));
        if (_reference_scaler) {
-               f << "reference_scaler " << _reference_scaler->id () << "\n";
+               root->add_child("ReferenceScaler")->add_child_text (_reference_scaler->id ());
        }
 
        for (vector<Filter const *>::const_iterator i = _reference_filters.begin(); i != _reference_filters.end(); ++i) {
-               f << N_("reference_filter ") << (*i)->id () << N_("\n");
+               root->add_child("ReferenceFilter")->add_child_text ((*i)->id ());
        }
        
        for (vector<ServerDescription*>::const_iterator i = _servers.begin(); i != _servers.end(); ++i) {
-               f << N_("server ") << (*i)->as_metadata () << N_("\n");
+               (*i)->as_xml (root->add_child ("Server"));
        }
 
-       f << N_("tms_ip ") << _tms_ip << N_("\n");
-       f << N_("tms_path ") << _tms_path << N_("\n");
-       f << N_("tms_user ") << _tms_user << N_("\n");
-       f << N_("tms_password ") << _tms_password << N_("\n");
+       root->add_child("TMSIP")->add_child_text (_tms_ip);
+       root->add_child("TMSPath")->add_child_text (_tms_path);
+       root->add_child("TMSUser")->add_child_text (_tms_user);
+       root->add_child("TMSPassword")->add_child_text (_tms_password);
        if (_sound_processor) {
-               f << "sound_processor " << _sound_processor->id () << "\n";
+               root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
        }
        if (_language) {
-               f << "language " << _language.get() << "\n";
+               root->add_child("Language")->add_child_text (_language.get());
        }
 
-       _default_dci_metadata.write (f);
+       _default_dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+
+       doc.write_to_file_formatted (file (false));
 }
 
 string
index 011ca716faef1839a1227daaa14bd74ad50cab7b..13d36d236da762fe7f9d9e999165b3c43f952fdb 100644 (file)
@@ -177,7 +177,8 @@ public:
 
 private:
        Config ();
-       std::string file () const;
+       std::string file (bool) const;
+       void read_old_metadata ();
 
        /** number of threads to use for J2K encoding on the local machine */
        int _num_local_encoding_threads;
diff --git a/src/lib/content.cc b/src/lib/content.cc
new file mode 100644 (file)
index 0000000..9c3bcd3
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+    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/thread/mutex.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
+#include "content.h"
+#include "util.h"
+
+using std::string;
+using boost::shared_ptr;
+
+Content::Content (boost::filesystem::path f)
+       : _file (f)
+{
+
+}
+
+Content::Content (shared_ptr<const cxml::Node> node)
+{
+       _file = node->string_child ("File");
+       _digest = node->string_child ("Digest");
+}
+
+Content::Content (Content const & o)
+       : boost::enable_shared_from_this<Content> (o)
+       , _file (o._file)
+       , _digest (o._digest)
+{
+
+}
+
+void
+Content::as_xml (xmlpp::Node* node) const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       node->add_child("File")->add_child_text (_file.string());
+       node->add_child("Digest")->add_child_text (_digest);
+}
+
+void
+Content::examine (shared_ptr<Film>, shared_ptr<Job>, bool)
+{
+       string const d = md5_digest (_file);
+       boost::mutex::scoped_lock lm (_mutex);
+       _digest = d;
+}
+
+void
+Content::signal_changed (int p)
+{
+       Changed (shared_from_this (), p);
+}
diff --git a/src/lib/content.h b/src/lib/content.h
new file mode 100644 (file)
index 0000000..fc2672c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_CONTENT_H
+#define DVDOMATIC_CONTENT_H
+
+#include <boost/filesystem.hpp>
+#include <boost/signals2.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+       class Node;
+}
+
+class Job;
+class Film;
+
+class Content : public boost::enable_shared_from_this<Content>
+{
+public:
+       Content (boost::filesystem::path);
+       Content (boost::shared_ptr<const cxml::Node>);
+       Content (Content const &);
+       
+       virtual void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+       virtual std::string summary () const = 0;
+       virtual std::string information () const = 0;
+       virtual void as_xml (xmlpp::Node *) const;
+       virtual boost::shared_ptr<Content> clone () const = 0;
+       
+       boost::filesystem::path file () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _file;
+       }
+
+       boost::signals2::signal<void (boost::weak_ptr<Content>, int)> Changed;
+
+protected:
+       void signal_changed (int);
+       
+       mutable boost::mutex _mutex;
+
+private:
+       boost::filesystem::path _file;
+       std::string _digest;
+};
+
+#endif
index 758886db46596f059917fe8136aa7865547de678..f25b3ddb00508157065b682c20a626b3de965f4d 100644 (file)
 */
 
 #include <iostream>
+#include <libcxml/cxml.h>
 #include "dci_metadata.h"
 
 #include "i18n.h"
 
-using namespace std;
+using std::string;
+using boost::shared_ptr;
+
+DCIMetadata::DCIMetadata (shared_ptr<const cxml::Node> node)
+{
+       audio_language = node->string_child ("AudioLanguage");
+       subtitle_language = node->string_child ("SubtitleLanguage");
+       territory = node->string_child ("Territory");
+       rating = node->string_child ("Rating");
+       studio = node->string_child ("Studio");
+       facility = node->string_child ("Facility");
+       package_type = node->string_child ("PackageType");
+}
 
 void
-DCIMetadata::write (ostream& f) const
+DCIMetadata::as_xml (xmlpp::Node* root) const
 {
-       f << N_("audio_language ") << audio_language << N_("\n");
-       f << N_("subtitle_language ") << subtitle_language << N_("\n");
-       f << N_("territory ") << territory << N_("\n");
-       f << N_("rating ") << rating << N_("\n");
-       f << N_("studio ") << studio << N_("\n");
-       f << N_("facility ") << facility << N_("\n");
-       f << N_("package_type ") << package_type << N_("\n");
+       root->add_child("AudioLanguage")->add_child_text (audio_language);
+       root->add_child("SubtitleLanguage")->add_child_text (subtitle_language);
+       root->add_child("Territory")->add_child_text (territory);
+       root->add_child("Rating")->add_child_text (rating);
+       root->add_child("Studio")->add_child_text (studio);
+       root->add_child("Facility")->add_child_text (facility);
+       root->add_child("PackageType")->add_child_text (package_type);
 }
 
 void
-DCIMetadata::read (string k, string v)
+DCIMetadata::read_old_metadata (string k, string v)
 {
        if (k == N_("audio_language")) {
                audio_language = v;
index eecdc765511a6ac2d6fa83301eb647cd1e78fab9..f61dae5a80bbe46e9926bd9c494fbdea6c617989 100644 (file)
 #define DVDOMATIC_DCI_METADATA_H
 
 #include <string>
+#include <libxml++/libxml++.h>
+
+namespace cxml {
+       class Node;
+}
 
 class DCIMetadata
 {
 public:
-       void read (std::string, std::string);
-       void write (std::ostream &) const;
+       DCIMetadata () {}
+       DCIMetadata (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       void read_old_metadata (std::string, std::string);
        
        std::string audio_language;
        std::string subtitle_language;
index d674393a98023e2e02459c1bc6b8ec6c966d2a42..e9499871ab240877958c8e1484269a8811f1590c 100644 (file)
@@ -47,7 +47,6 @@
 #include "dcp_video_frame.h"
 #include "lut.h"
 #include "config.h"
-#include "options.h"
 #include "exceptions.h"
 #include "server.h"
 #include "util.h"
index 52b22fa067955b48341b40947a728161f65c6152..082ad5076cd402647f43800596f45614319616f6 100644 (file)
  */
 
 #include <iostream>
-#include <stdint.h>
-#include <boost/lexical_cast.hpp>
 #include "film.h"
-#include "format.h"
-#include "options.h"
 #include "exceptions.h"
-#include "image.h"
 #include "util.h"
-#include "log.h"
 #include "decoder.h"
-#include "delay_line.h"
-#include "subtitle.h"
-#include "filter_graph.h"
 
 #include "i18n.h"
 
 using std::string;
-using std::stringstream;
-using std::min;
-using std::pair;
-using std::list;
 using boost::shared_ptr;
-using boost::optional;
 
 /** @param f Film.
  *  @param o Decode options.
  */
-Decoder::Decoder (boost::shared_ptr<Film> f, DecodeOptions o)
+Decoder::Decoder (shared_ptr<const Film> f)
        : _film (f)
-       , _opt (o)
 {
        _film_connection = f->Changed.connect (bind (&Decoder::film_changed, this, _1));
 }
 
-/** Seek to a position as a source timestamp in seconds.
+/** Seek to a position as a content timestamp in seconds.
  *  @return true on error.
  */
 bool
@@ -64,12 +49,3 @@ Decoder::seek (double)
 {
        throw DecodeError (N_("decoder does not support seek"));
 }
-
-/** Seek so that the next frame we will produce is the same as the last one.
- *  @return true on error.
- */
-bool
-Decoder::seek_to_last ()
-{
-       throw DecodeError (N_("decoder does not support seek"));
-}
index f2f5235168402c136e9c133ac8865c11396a99c7..0fffef25739ca0c6afb9b049b2de7978bbd55a56 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <boost/signals2.hpp>
 #include "util.h"
-#include "stream.h"
 #include "video_source.h"
 #include "audio_source.h"
 #include "film.h"
-#include "options.h"
 
 class Image;
 class Log;
@@ -53,24 +51,18 @@ class FilterGraph;
 class Decoder
 {
 public:
-       Decoder (boost::shared_ptr<Film>, DecodeOptions);
+       Decoder (boost::shared_ptr<const Film>);
        virtual ~Decoder () {}
 
        virtual bool pass () = 0;
        virtual bool seek (double);
-       virtual bool seek_to_last ();
-
-       boost::signals2::signal<void()> OutputChanged;
 
 protected:
-       /** our Film */
-       boost::shared_ptr<Film> _film;
-       /** our decode options */
-       DecodeOptions _opt;
+       boost::shared_ptr<const Film> _film;
 
 private:
        virtual void film_changed (Film::Property) {}
-       
+
        boost::signals2::scoped_connection _film_connection;
 };
 
diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc
deleted file mode 100644 (file)
index f7f9f40..0000000
+++ /dev/null
@@ -1,60 +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_factory.cc
- *  @brief A method to create an appropriate decoder for some content.
- */
-
-#include <boost/filesystem.hpp>
-#include "ffmpeg_decoder.h"
-#include "imagemagick_decoder.h"
-#include "film.h"
-#include "sndfile_decoder.h"
-#include "decoder_factory.h"
-
-using std::string;
-using std::pair;
-using std::make_pair;
-using boost::shared_ptr;
-using boost::dynamic_pointer_cast;
-
-Decoders
-decoder_factory (
-       shared_ptr<Film> f, DecodeOptions o
-       )
-{
-       if (f->content().empty()) {
-               return Decoders ();
-       }
-       
-       if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) {
-               /* A single image file, or a directory of them */
-               return Decoders (
-                       shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o)),
-                       shared_ptr<AudioDecoder> (new SndfileDecoder (f, o))
-                       );
-       }
-
-       shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o));
-       if (f->use_content_audio()) {
-               return Decoders (fd, fd);
-       }
-
-       return Decoders (fd, shared_ptr<AudioDecoder> (new SndfileDecoder (f, o)));
-}
diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h
deleted file mode 100644 (file)
index 8076b01..0000000
+++ /dev/null
@@ -1,49 +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.
-
-*/
-
-#ifndef DVDOMATIC_DECODER_FACTORY_H
-#define DVDOMATIC_DECODER_FACTORY_H
-
-/** @file  src/decoder_factory.h
- *  @brief A method to create appropriate decoders for some content.
- */
-
-#include "options.h"
-
-class Film;
-class VideoDecoder;
-class AudioDecoder;
-
-struct Decoders {
-       Decoders () {}
-       
-       Decoders (boost::shared_ptr<VideoDecoder> v, boost::shared_ptr<AudioDecoder> a)
-               : video (v)
-               , audio (a)
-       {}
-
-       boost::shared_ptr<VideoDecoder> video;
-       boost::shared_ptr<AudioDecoder> audio;
-};
-
-extern Decoders decoder_factory (
-       boost::shared_ptr<Film>, DecodeOptions
-       );
-
-#endif
index 7b338407eae7765c3bd7aac7bb5fd519b45675a7..46d11c55640e2fbf92a0789748f9f9a284c1d0b8 100644 (file)
@@ -27,7 +27,6 @@
 #include <libdcp/picture_asset.h>
 #include "encoder.h"
 #include "util.h"
-#include "options.h"
 #include "film.h"
 #include "log.h"
 #include "exceptions.h"
@@ -38,6 +37,8 @@
 #include "format.h"
 #include "cross.h"
 #include "writer.h"
+#include "player.h"
+#include "audio_mapping.h"
 
 #include "i18n.h"
 
@@ -48,7 +49,8 @@ using std::vector;
 using std::list;
 using std::cout;
 using std::make_pair;
-using namespace boost;
+using boost::shared_ptr;
+using boost::optional;
 
 int const Encoder::_history_size = 25;
 
@@ -77,22 +79,22 @@ Encoder::~Encoder ()
 void
 Encoder::process_begin ()
 {
-       if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) {
+       if (_film->has_audio() && _film->audio_frame_rate() != _film->target_audio_sample_rate()) {
 #ifdef HAVE_SWRESAMPLE
 
                stringstream s;
-               s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_stream()->sample_rate(), _film->target_audio_sample_rate());
+               s << String::compose (N_("Will resample audio from %1 to %2"), _film->audio_frame_rate(), _film->target_audio_sample_rate());
                _film->log()->log (s.str ());
 
                /* We will be using planar float data when we call the resampler */
                _swr_context = swr_alloc_set_opts (
                        0,
-                       _film->audio_stream()->channel_layout(),
+                       _film->audio_channel_layout(),
                        AV_SAMPLE_FMT_FLTP,
                        _film->target_audio_sample_rate(),
-                       _film->audio_stream()->channel_layout(),
+                       _film->audio_channel_layout(),
                        AV_SAMPLE_FMT_FLTP,
-                       _film->audio_stream()->sample_rate(),
+                       _film->audio_frame_rate(),
                        0, 0
                        );
                
@@ -126,9 +128,9 @@ void
 Encoder::process_end ()
 {
 #if HAVE_SWRESAMPLE    
-       if (_film->audio_stream() && _film->audio_stream()->channels() && _swr_context) {
+       if (_film->has_audio() && _film->audio_channels() && _swr_context) {
 
-               shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256));
+               shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_channels(), 256));
                        
                while (1) {
                        int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0);
@@ -142,7 +144,7 @@ Encoder::process_end ()
                        }
 
                        out->set_frames (frames);
-                       write_audio (out);
+                       _writer->write (out);
                }
 
                swr_free (&_swr_context);
@@ -193,7 +195,7 @@ Encoder::process_end ()
  *  or 0 if not known.
  */
 float
-Encoder::current_frames_per_second () const
+Encoder::current_encoding_rate () const
 {
        boost::mutex::scoped_lock lock (_history_mutex);
        if (int (_time_history.size()) < _history_size) {
@@ -231,9 +233,9 @@ Encoder::frame_done ()
 }
 
 void
-Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub)
+Encoder::process_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
 {
-       FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
+       FrameRateConversion frc (_film->video_frame_rate(), _film->dcp_frame_rate());
        
        if (frc.skip && (_video_frames_in % 2)) {
                ++_video_frames_in;
@@ -269,7 +271,7 @@ Encoder::process_video (shared_ptr<Image> image, bool same, boost::shared_ptr<Su
                /* Queue this new frame for encoding */
                pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
                TIMING ("adding to queue of %1", _queue.size ());
-               _queue.push_back (boost::shared_ptr<DCPVideoFrame> (
+               _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
                                                  image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film),
                                                  _film->subtitle_offset(), _film->subtitle_scale(),
@@ -301,9 +303,9 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data)
        if (_swr_context) {
 
                /* Compute the resampled frames count and add 32 for luck */
-               int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32;
+               int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _film->audio_frame_rate()) + 32;
 
-               shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames));
+               shared_ptr<AudioBuffers> resampled (new AudioBuffers (_film->audio_channels(), max_resampled_frames));
 
                /* Resample audio */
                int const resampled_frames = swr_convert (
@@ -321,7 +323,7 @@ Encoder::process_audio (shared_ptr<AudioBuffers> data)
        }
 #endif
 
-       write_audio (data);
+       _writer->write (data);
 }
 
 void
@@ -360,7 +362,7 @@ Encoder::encoder_thread (ServerDescription* server)
                }
 
                TIMING ("encoder thread %1 wakes with queue of %2", boost::this_thread::get_id(), _queue.size());
-               boost::shared_ptr<DCPVideoFrame> vf = _queue.front ();
+               shared_ptr<DCPVideoFrame> vf = _queue.front ();
                _film->log()->log (String::compose (N_("Encoder thread %1 pops frame %2 from queue"), boost::this_thread::get_id(), vf->frame()), Log::VERBOSE);
                _queue.pop_front ();
                
@@ -421,27 +423,3 @@ Encoder::encoder_thread (ServerDescription* server)
                _condition.notify_all ();
        }
 }
-
-void
-Encoder::write_audio (shared_ptr<const AudioBuffers> data)
-{
-       AudioMapping m (_film->audio_channels ());
-       if (m.dcp_channels() != _film->audio_channels()) {
-
-               /* Remap (currently just for mono -> 5.1) */
-
-               shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ()));
-               for (int i = 0; i < m.dcp_channels(); ++i) {
-                       optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i));
-                       if (!s) {
-                               b->make_silent (i);
-                       } else {
-                               memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float));
-                       }
-               }
-
-               data = b;
-       }
-
-       _writer->write (data);
-}
index 86880bc34942561ba12a1488feb00663c2c9d6d8..70e6eea9a8ae30997d3f2132d31405e1e80e5850 100644 (file)
@@ -81,15 +81,13 @@ public:
        /** Called when a processing run has finished */
        virtual void process_end ();
 
-       float current_frames_per_second () const;
+       float current_encoding_rate () const;
        int video_frames_out () const;
 
 private:
        
        void frame_done ();
        
-       void write_audio (boost::shared_ptr<const AudioBuffers> data);
-
        void encoder_thread (ServerDescription *);
        void terminate_threads ();
 
@@ -106,7 +104,7 @@ private:
        static int const _history_size;
 
        /** Number of video frames received so far */
-       SourceFrame _video_frames_in;
+       ContentVideoFrame _video_frames_in;
        /** Number of video frames written for the DCP so far */
        int _video_frames_out;
 
index 4b30c943136a522d3b2197794ca38ca5eb2b187d..aad7f265e8e9f1465fd58d7ed22b35cdb4cbccdc 100644 (file)
 
 */
 
-/** @file  src/examine_content_job.cc
- *  @brief A class to run through content at high speed to find its length.
- */
-
 #include <boost/filesystem.hpp>
 #include "examine_content_job.h"
-#include "options.h"
-#include "decoder_factory.h"
-#include "decoder.h"
-#include "transcoder.h"
 #include "log.h"
-#include "film.h"
-#include "video_decoder.h"
+#include "content.h"
 
 #include "i18n.h"
 
 using std::string;
-using std::vector;
-using std::pair;
 using boost::shared_ptr;
 
-ExamineContentJob::ExamineContentJob (shared_ptr<Film> f)
+ExamineContentJob::ExamineContentJob (shared_ptr<Film> f, shared_ptr<Content> c, bool q)
        : Job (f)
+       , _content (c)
+       , _quick (q)
 {
 
 }
@@ -51,60 +42,13 @@ ExamineContentJob::~ExamineContentJob ()
 string
 ExamineContentJob::name () const
 {
-       if (_film->name().empty ()) {
-               return _("Examine content");
-       }
-       
-       return String::compose (_("Examine content of %1"), _film->name());
+       return _("Examine content");
 }
 
 void
 ExamineContentJob::run ()
 {
-       descend (0.5);
-       _film->set_content_digest (md5_digest (_film->content_path ()));
-       ascend ();
-
-       descend (0.5);
-
-       /* Set the film's length to either
-          a) a length judged by running through the content or
-          b) the length from a decoder's header.
-       */
-       if (!_film->trust_content_header()) {
-               /* Decode the content to get an accurate length */
-               
-               /* We don't want to use any existing length here, as progress
-                  will be messed up.
-               */
-               _film->unset_length ();
-               _film->set_crop (Crop ());
-               
-               DecodeOptions o;
-               o.decode_audio = false;
-               
-               Decoders decoders = decoder_factory (_film, o);
-               
-               set_progress_unknown ();
-               while (!decoders.video->pass()) {
-                       /* keep going */
-               }
-               
-               _film->set_length (decoders.video->video_frame());
-               
-               _film->log()->log (String::compose (N_("Video length examined as %1 frames"), _film->length().get()));
-               
-       } else {
-
-               /* Get a quick decoder to get the content's length from its header */
-               
-               Decoders d = decoder_factory (_film, DecodeOptions());
-               _film->set_length (d.video->length());
-       
-               _film->log()->log (String::compose (N_("Video length obtained from header as %1 frames"), _film->length().get()));
-       }
-
-       ascend ();
+       _content->examine (_film, shared_from_this (), _quick);
        set_progress (1);
        set_state (FINISHED_OK);
 }
index 8ee4f0d608de5e8b591e68f23dd2634929065dbe..dc0d53ffff2e03650c611b067d18d61abe5a18d0 100644 (file)
 
 */
 
-/** @file  src/examine_content_job.h
- *  @brief A class to obtain the length and MD5 digest of a content file.
- */
-
+#include <boost/shared_ptr.hpp>
 #include "job.h"
 
-/** @class ExamineContentJob
- *  @brief A class to obtain the length and MD5 digest of a content file.
- */
+class Content;
+class Log;
+
 class ExamineContentJob : public Job
 {
 public:
-       ExamineContentJob (boost::shared_ptr<Film>);
+       ExamineContentJob (boost::shared_ptr<Film>, boost::shared_ptr<Content>, bool);
        ~ExamineContentJob ();
 
        std::string name () const;
        void run ();
+
+private:
+       boost::shared_ptr<Content> _content;
+       bool _quick;
 };
 
index e45a62353bf57fa7085701828ccfbc6eec1d2791..6920556e5c05a95a24eadfe398c226a4c0df3cb3 100644 (file)
@@ -112,6 +112,7 @@ class OpenFileError : public FileError
 {
 public:
        /** @param f File that we were trying to open */
+       /* XXX: should be boost::filesystem::path */
        OpenFileError (std::string f);
 };
 
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
new file mode 100644 (file)
index 0000000..d36abe2
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+    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 <libcxml/cxml.h>
+#include "ffmpeg_content.h"
+#include "ffmpeg_decoder.h"
+#include "compose.hpp"
+#include "job.h"
+#include "util.h"
+#include "log.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::vector;
+using std::list;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
+int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
+int const FFmpegContentProperty::AUDIO_STREAMS = 102;
+int const FFmpegContentProperty::AUDIO_STREAM = 103;
+
+FFmpegContent::FFmpegContent (boost::filesystem::path f)
+       : Content (f)
+       , VideoContent (f)
+       , AudioContent (f)
+{
+
+}
+
+FFmpegContent::FFmpegContent (shared_ptr<const cxml::Node> node)
+       : Content (node)
+       , VideoContent (node)
+       , AudioContent (node)
+{
+       list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               _subtitle_streams.push_back (FFmpegSubtitleStream (*i));
+               if ((*i)->optional_number_child<int> ("Selected")) {
+                       _subtitle_stream = _subtitle_streams.back ();
+               }
+       }
+
+       c = node->node_children ("AudioStream");
+       for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
+               _audio_streams.push_back (FFmpegAudioStream (*i));
+               if ((*i)->optional_number_child<int> ("Selected")) {
+                       _audio_stream = _audio_streams.back ();
+               }
+       }
+}
+
+FFmpegContent::FFmpegContent (FFmpegContent const & o)
+       : Content (o)
+       , VideoContent (o)
+       , AudioContent (o)
+       , _subtitle_streams (o._subtitle_streams)
+       , _subtitle_stream (o._subtitle_stream)
+       , _audio_streams (o._audio_streams)
+       , _audio_stream (o._audio_stream)
+{
+
+}
+
+void
+FFmpegContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("FFmpeg");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+
+       boost::mutex::scoped_lock lm (_mutex);
+
+       for (vector<FFmpegSubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
+               xmlpp::Node* t = node->add_child("SubtitleStream");
+               if (_subtitle_stream && *i == _subtitle_stream.get()) {
+                       t->add_child("Selected")->add_child_text("1");
+               }
+               i->as_xml (t);
+       }
+
+       for (vector<FFmpegAudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
+               xmlpp::Node* t = node->add_child("AudioStream");
+               if (_audio_stream && *i == _audio_stream.get()) {
+                       t->add_child("Selected")->add_child_text("1");
+               }
+               i->as_xml (t);
+       }
+}
+
+void
+FFmpegContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
+{
+       job->set_progress_unknown ();
+
+       Content::examine (film, job, quick);
+
+       shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false, true));
+
+       ContentVideoFrame video_length = 0;
+       if (quick) {
+               video_length = decoder->video_length ();
+                film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ()));
+        } else {
+                while (!decoder->pass ()) {
+                        /* keep going */
+                }
+
+                video_length = decoder->video_frame ();
+                film->log()->log (String::compose ("Video length examined as %1 frames", decoder->video_frame ()));
+        }
+
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+
+                _video_length = video_length;
+
+                _subtitle_streams = decoder->subtitle_streams ();
+                if (!_subtitle_streams.empty ()) {
+                        _subtitle_stream = _subtitle_streams.front ();
+                }
+                
+                _audio_streams = decoder->audio_streams ();
+                if (!_audio_streams.empty ()) {
+                        _audio_stream = _audio_streams.front ();
+                }
+        }
+
+        take_from_video_decoder (decoder);
+
+        signal_changed (VideoContentProperty::VIDEO_LENGTH);
+        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
+        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+        signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
+        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+        signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+}
+
+string
+FFmpegContent::summary () const
+{
+       return String::compose (_("Movie: %1"), file().filename().string());
+}
+
+string
+FFmpegContent::information () const
+{
+       if (video_length() == 0 || video_frame_rate() == 0) {
+               return "";
+       }
+       
+       stringstream s;
+       
+       s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
+       s << VideoContent::information ();
+
+       return s.str ();
+}
+
+void
+FFmpegContent::set_subtitle_stream (FFmpegSubtitleStream s)
+{
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+                _subtitle_stream = s;
+        }
+
+        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
+}
+
+void
+FFmpegContent::set_audio_stream (FFmpegAudioStream s)
+{
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+                _audio_stream = s;
+        }
+
+        signal_changed (FFmpegContentProperty::AUDIO_STREAM);
+}
+
+ContentAudioFrame
+FFmpegContent::audio_length () const
+{
+        if (!_audio_stream) {
+                return 0;
+        }
+        
+        return video_frames_to_audio_frames (_video_length, audio_frame_rate(), video_frame_rate());
+}
+
+int
+FFmpegContent::audio_channels () const
+{
+        if (!_audio_stream) {
+                return 0;
+        }
+
+        return _audio_stream->channels ();
+}
+
+int
+FFmpegContent::audio_frame_rate () const
+{
+        if (!_audio_stream) {
+                return 0;
+        }
+
+        return _audio_stream->frame_rate;
+}
+
+int64_t
+FFmpegContent::audio_channel_layout () const
+{
+        if (!_audio_stream) {
+                return 0;
+        }
+
+        return _audio_stream->channel_layout;
+}
+       
+bool
+operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+{
+        return a.id == b.id;
+}
+
+bool
+operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+{
+        return a.id == b.id;
+}
+
+FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
+{
+       name = node->string_child ("Name");
+       id = node->number_child<int> ("Id");
+       frame_rate = node->number_child<int> ("FrameRate");
+       channel_layout = node->number_child<int64_t> ("ChannelLayout");
+}
+
+void
+FFmpegAudioStream::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("Name")->add_child_text (name);
+       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+       root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
+       root->add_child("ChannelLayout")->add_child_text (lexical_cast<string> (channel_layout));
+}
+
+/** Construct a SubtitleStream from a value returned from to_string().
+ *  @param t String returned from to_string().
+ *  @param v State file version.
+ */
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+{
+       name = node->string_child ("Name");
+       id = node->number_child<int> ("Id");
+}
+
+void
+FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("Name")->add_child_text (name);
+       root->add_child("Id")->add_child_text (lexical_cast<string> (id));
+}
+
+shared_ptr<Content>
+FFmpegContent::clone () const
+{
+       return shared_ptr<Content> (new FFmpegContent (*this));
+}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
new file mode 100644 (file)
index 0000000..cc603e6
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_FFMPEG_CONTENT_H
+#define DVDOMATIC_FFMPEG_CONTENT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "video_content.h"
+#include "audio_content.h"
+
+class FFmpegAudioStream
+{
+public:
+        FFmpegAudioStream (std::string n, int i, int f, int64_t c)
+                : name (n)
+                , id (i)
+                , frame_rate (f)
+                , channel_layout (c)
+        {}
+
+       FFmpegAudioStream (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       
+        int channels () const {
+                return av_get_channel_layout_nb_channels (channel_layout);
+        }
+        
+        std::string name;
+        int id;
+        int frame_rate;
+        int64_t channel_layout;
+};
+
+extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
+
+class FFmpegSubtitleStream
+{
+public:
+        FFmpegSubtitleStream (std::string n, int i)
+                : name (n)
+                , id (i)
+        {}
+        
+       FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
+
+       void as_xml (xmlpp::Node *) const;
+       
+        std::string name;
+        int id;
+};
+
+extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
+
+class FFmpegContentProperty : public VideoContentProperty
+{
+public:
+        static int const SUBTITLE_STREAMS;
+        static int const SUBTITLE_STREAM;
+        static int const AUDIO_STREAMS;
+        static int const AUDIO_STREAM;
+};
+
+class FFmpegContent : public VideoContent, public AudioContent
+{
+public:
+       FFmpegContent (boost::filesystem::path);
+       FFmpegContent (boost::shared_ptr<const cxml::Node>);
+       FFmpegContent (FFmpegContent const &);
+
+       boost::shared_ptr<FFmpegContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<FFmpegContent> (Content::shared_from_this ());
+       }
+       
+       void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+       std::string summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       boost::shared_ptr<Content> clone () const;
+
+        /* AudioContent */
+        int audio_channels () const;
+        ContentAudioFrame audio_length () const;
+        int audio_frame_rate () const;
+        int64_t audio_channel_layout () const;
+       
+        std::vector<FFmpegSubtitleStream> subtitle_streams () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _subtitle_streams;
+        }
+
+        boost::optional<FFmpegSubtitleStream> subtitle_stream () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _subtitle_stream;
+        }
+
+        std::vector<FFmpegAudioStream> audio_streams () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _audio_streams;
+        }
+        
+        boost::optional<FFmpegAudioStream> audio_stream () const {
+                boost::mutex::scoped_lock lm (_mutex);
+                return _audio_stream;
+        }
+
+        void set_subtitle_stream (FFmpegSubtitleStream);
+        void set_audio_stream (FFmpegAudioStream);
+       
+private:
+       std::vector<FFmpegSubtitleStream> _subtitle_streams;
+       boost::optional<FFmpegSubtitleStream> _subtitle_stream;
+       std::vector<FFmpegAudioStream> _audio_streams;
+       boost::optional<FFmpegAudioStream> _audio_stream;
+};
+
+#endif
index ac25844e34687032ea6c47c1808a0672704b9f44..d0b1de748c0e2960dc4bc3ed9aeade1c8db2ab00 100644 (file)
@@ -41,7 +41,6 @@ extern "C" {
 #include "transcoder.h"
 #include "job.h"
 #include "filter.h"
-#include "options.h"
 #include "exceptions.h"
 #include "image.h"
 #include "util.h"
@@ -62,10 +61,13 @@ using boost::optional;
 using boost::dynamic_pointer_cast;
 using libdcp::Size;
 
-FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , VideoDecoder (f, o)
-       , AudioDecoder (f, o)
+boost::mutex FFmpegDecoder::_mutex;
+
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio, bool subtitles, bool video_sync)
+       : Decoder (f)
+       , VideoDecoder (f)
+       , AudioDecoder (f)
+       , _ffmpeg_content (c)
        , _format_context (0)
        , _video_stream (-1)
        , _frame (0)
@@ -75,23 +77,29 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o)
        , _audio_codec (0)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
+       , _decode_video (video)
+       , _decode_audio (audio)
+       , _decode_subtitles (subtitles)
+       , _video_sync (video_sync)
 {
        setup_general ();
        setup_video ();
        setup_audio ();
        setup_subtitle ();
 
-       if (!o.video_sync) {
+       if (!video_sync) {
                _first_video = 0;
        }
 }
 
 FFmpegDecoder::~FFmpegDecoder ()
 {
+       boost::mutex::scoped_lock lm (_mutex);
+       
        if (_audio_codec_context) {
                avcodec_close (_audio_codec_context);
        }
-       
+
        if (_video_codec_context) {
                avcodec_close (_video_codec_context);
        }
@@ -110,15 +118,15 @@ FFmpegDecoder::setup_general ()
 {
        av_register_all ();
 
-       if (avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0) < 0) {
-               throw OpenFileError (_film->content_path ());
+       if (avformat_open_input (&_format_context, _ffmpeg_content->file().string().c_str(), 0, 0) < 0) {
+               throw OpenFileError (_ffmpeg_content->file().string ());
        }
 
        if (avformat_find_stream_info (_format_context, 0) < 0) {
                throw DecodeError (_("could not find stream information"));
        }
 
-       /* Find video, audio and subtitle streams and choose the first of each */
+       /* Find video, audio and subtitle streams */
 
        for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
                AVStream* s = _format_context->streams[i];
@@ -135,17 +143,11 @@ FFmpegDecoder::setup_general ()
                        }
                        
                        _audio_streams.push_back (
-                               shared_ptr<AudioStream> (
-                                       new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
-                                       )
+                               FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout)
                                );
                        
                } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
-                       _subtitle_streams.push_back (
-                               shared_ptr<SubtitleStream> (
-                                       new SubtitleStream (stream_name (s), i)
-                                       )
-                               );
+                       _subtitle_streams.push_back (FFmpegSubtitleStream (stream_name (s), i));
                }
        }
 
@@ -162,6 +164,8 @@ FFmpegDecoder::setup_general ()
 void
 FFmpegDecoder::setup_video ()
 {
+       boost::mutex::scoped_lock lm (_mutex);
+       
        _video_codec_context = _format_context->streams[_video_stream]->codec;
        _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
 
@@ -177,14 +181,13 @@ FFmpegDecoder::setup_video ()
 void
 FFmpegDecoder::setup_audio ()
 {
-       if (!_audio_stream) {
+       boost::mutex::scoped_lock lm (_mutex);
+       
+       if (!_ffmpeg_content->audio_stream ()) {
                return;
        }
 
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-       assert (ffa);
-       
-       _audio_codec_context = _format_context->streams[ffa->id()]->codec;
+       _audio_codec_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
        _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
 
        if (_audio_codec == 0) {
@@ -199,11 +202,13 @@ FFmpegDecoder::setup_audio ()
 void
 FFmpegDecoder::setup_subtitle ()
 {
-       if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
+       boost::mutex::scoped_lock lm (_mutex);
+       
+       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->id >= int (_format_context->nb_streams)) {
                return;
        }
 
-       _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec;
+       _subtitle_codec_context = _format_context->streams[_ffmpeg_content->subtitle_stream()->id]->codec;
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 
        if (_subtitle_codec == 0) {
@@ -238,13 +243,13 @@ FFmpegDecoder::pass ()
 
                int frame_finished;
 
-               if (_opt.decode_video) {
+               if (_decode_video) {
                        while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
                                filter_and_emit_video (_frame);
                        }
                }
 
-               if (_audio_stream && _opt.decode_audio) {
+               if (_ffmpeg_content->audio_stream() && _decode_audio) {
                        decode_audio_packet ();
                }
 
@@ -253,9 +258,7 @@ FFmpegDecoder::pass ()
 
        avcodec_get_frame_defaults (_frame);
 
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-
-       if (_packet.stream_index == _video_stream && _opt.decode_video) {
+       if (_packet.stream_index == _video_stream && _decode_video) {
 
                int frame_finished;
                int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
@@ -265,16 +268,16 @@ FFmpegDecoder::pass ()
                                _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size));
                        }
 
-                       if (_opt.video_sync) {
+                       if (_video_sync) {
                                out_with_sync ();
                        } else {
                                filter_and_emit_video (_frame);
                        }
                }
 
-       } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) {
+       } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
                decode_audio_packet ();
-       } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles && _first_video) {
+       } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id && _decode_subtitles && _first_video) {
 
                int got_subtitle;
                AVSubtitle sub;
@@ -306,19 +309,16 @@ FFmpegDecoder::pass ()
 shared_ptr<AudioBuffers>
 FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
 {
-       assert (_film->audio_channels());
+       assert (_ffmpeg_content->audio_channels());
        assert (bytes_per_audio_sample());
 
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-       assert (ffa);
-       
        /* Deinterleave and convert to float */
 
-       assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0);
+       assert ((size % (bytes_per_audio_sample() * _ffmpeg_content->audio_channels())) == 0);
 
        int const total_samples = size / bytes_per_audio_sample();
-       int const frames = total_samples / _film->audio_channels();
-       shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames));
+       int const frames = total_samples / _ffmpeg_content->audio_channels();
+       shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
 
        switch (audio_sample_format()) {
        case AV_SAMPLE_FMT_S16:
@@ -330,7 +330,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -341,7 +341,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        case AV_SAMPLE_FMT_S16P:
        {
                int16_t** p = reinterpret_cast<int16_t **> (data);
-               for (int i = 0; i < _film->audio_channels(); ++i) {
+               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        for (int j = 0; j < frames; ++j) {
                                audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
@@ -358,7 +358,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -375,7 +375,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = *p++;
 
                        ++channel;
-                       if (channel == _film->audio_channels()) {
+                       if (channel == _ffmpeg_content->audio_channels()) {
                                channel = 0;
                                ++sample;
                        }
@@ -386,7 +386,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        case AV_SAMPLE_FMT_FLTP:
        {
                float** p = reinterpret_cast<float**> (data);
-               for (int i = 0; i < _film->audio_channels(); ++i) {
+               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
                        memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
@@ -400,7 +400,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
 }
 
 float
-FFmpegDecoder::frames_per_second () const
+FFmpegDecoder::video_frame_rate () const
 {
        AVStream* s = _format_context->streams[_video_stream];
 
@@ -488,21 +488,6 @@ FFmpegDecoder::bytes_per_audio_sample () const
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-void
-FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s)
-{
-       AudioDecoder::set_audio_stream (s);
-       setup_audio ();
-}
-
-void
-FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       VideoDecoder::set_subtitle_stream (s);
-       setup_subtitle ();
-       OutputChanged ();
-}
-
 void
 FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
 {
@@ -533,22 +518,12 @@ FFmpegDecoder::filter_and_emit_video (AVFrame* frame)
 bool
 FFmpegDecoder::seek (double p)
 {
-       return do_seek (p, false);
-}
-
-bool
-FFmpegDecoder::seek_to_last ()
-{
-       /* This AVSEEK_FLAG_BACKWARD in do_seek is a bit of a hack; without it, if we ask for a seek to the same place as last time
+       /* This use of AVSEEK_FLAG_BACKWARD is a bit of a hack; without it, if we ask for a seek to the same place as last time
           (used when we change decoder parameters and want to re-fetch the frame) we end up going forwards rather than
           staying in the same place.
        */
-       return do_seek (last_source_time(), true);
-}
-
-bool
-FFmpegDecoder::do_seek (double p, bool backwards)
-{
+       bool const backwards = (p == last_content_time());
+       
        int64_t const vt = p / av_q2d (_format_context->streams[_video_stream]->time_base);
 
        int const r = av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
@@ -561,63 +536,11 @@ FFmpegDecoder::do_seek (double p, bool backwards)
        return r < 0;
 }
 
-shared_ptr<FFmpegAudioStream>
-FFmpegAudioStream::create (string t, optional<int> v)
-{
-       if (!v) {
-               /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
-               return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-       }
-
-       stringstream s (t);
-       string type;
-       s >> type;
-       if (type != N_("ffmpeg")) {
-               return shared_ptr<FFmpegAudioStream> ();
-       }
-
-       return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v));
-}
-
-FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version)
-{
-       stringstream n (t);
-       
-       int name_index = 4;
-       if (!version) {
-               name_index = 2;
-               int channels;
-               n >> _id >> channels;
-               _channel_layout = av_get_default_channel_layout (channels);
-               _sample_rate = 0;
-       } else {
-               string type;
-               /* Current (marked version 1) */
-               n >> type >> _id >> _sample_rate >> _channel_layout;
-               assert (type == N_("ffmpeg"));
-       }
-
-       for (int i = 0; i < name_index; ++i) {
-               size_t const s = t.find (' ');
-               if (s != string::npos) {
-                       t = t.substr (s + 1);
-               }
-       }
-
-       _name = t;
-}
-
-string
-FFmpegAudioStream::to_string () const
-{
-       return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
-}
-
 void
 FFmpegDecoder::out_with_sync ()
 {
        /* Where we are in the output, in seconds */
-       double const out_pts_seconds = video_frame() / frames_per_second();
+       double const out_pts_seconds = video_frame() / video_frame_rate();
        
        /* Where we are in the source, in seconds */
        double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base)
@@ -634,17 +557,17 @@ FFmpegDecoder::out_with_sync ()
        
        /* Difference between where we are and where we should be */
        double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds;
-       double const one_frame = 1 / frames_per_second();
+       double const one_frame = 1 / video_frame_rate();
        
        /* Insert frames if required to get out_pts_seconds up to pts_seconds */
        if (delta > one_frame) {
                int const extra = rint (delta / one_frame);
                for (int i = 0; i < extra; ++i) {
-                       repeat_last_video ();
+                       repeat_last_video (frame_time ());
                        _film->log()->log (
                                String::compose (
                                        N_("Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)"),
-                                       out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second()
+                                       out_pts_seconds, video_frame(), source_pts_seconds, video_frame_rate()
                                        )
                                );
                }
@@ -669,7 +592,6 @@ FFmpegDecoder::film_changed (Film::Property p)
                boost::mutex::scoped_lock lm (_filter_graphs_mutex);
                _filter_graphs.clear ();
        }
-       OutputChanged ();
        break;
 
        default:
@@ -678,10 +600,10 @@ FFmpegDecoder::film_changed (Film::Property p)
 }
 
 /** @return Length (in video frames) according to our content's header */
-SourceFrame
-FFmpegDecoder::length () const
+ContentVideoFrame
+FFmpegDecoder::video_length () const
 {
-       return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second();
+       return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
 }
 
 double
@@ -693,9 +615,6 @@ FFmpegDecoder::frame_time () const
 void
 FFmpegDecoder::decode_audio_packet ()
 {
-       shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
-       assert (ffa);
-
        /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
           several times.
        */
@@ -716,9 +635,9 @@ FFmpegDecoder::decode_audio_packet ()
                           was before this packet.  Until then audio is thrown away.
                        */
                        
-                       if ((_first_video && _first_video.get() <= source_pts_seconds) || !_opt.decode_video) {
+                       if ((_first_video && _first_video.get() <= source_pts_seconds) || !_decode_video) {
                                
-                               if (!_first_audio && _opt.decode_video) {
+                               if (!_first_audio && _decode_video) {
                                        _first_audio = source_pts_seconds;
                                        
                                        /* This is our first audio frame, and if we've arrived here we must have had our
@@ -727,17 +646,17 @@ FFmpegDecoder::decode_audio_packet ()
                                        */
                                        
                                        /* frames of silence that we must push */
-                                       int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ());
+                                       int const s = rint ((_first_audio.get() - _first_video.get()) * _ffmpeg_content->audio_frame_rate ());
                                        
                                        _film->log()->log (
                                                String::compose (
                                                        N_("First video at %1, first audio at %2, pushing %3 audio frames of silence for %4 channels (%5 bytes per sample)"),
-                                                       _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample()
+                                                       _first_video.get(), _first_audio.get(), s, _ffmpeg_content->audio_channels(), bytes_per_audio_sample()
                                                        )
                                                );
                                        
                                        if (s) {
-                                               shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s));
+                                               shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), s));
                                                audio->make_silent ();
                                                Audio (audio);
                                        }
@@ -747,7 +666,7 @@ FFmpegDecoder::decode_audio_packet ()
                                        0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
                                        );
                                
-                               assert (_audio_codec_context->channels == _film->audio_channels());
+                               assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
                                Audio (deinterleave_audio (_frame->data, data_size));
                        }
                }
index 1bb14ce9cfca0ea6d0ba2392c06b0e06b30725be..f6a53874ae723871d4d6dba390d57dccab047597 100644 (file)
@@ -36,6 +36,7 @@ extern "C" {
 #include "video_decoder.h"
 #include "audio_decoder.h"
 #include "film.h"
+#include "ffmpeg_content.h"
 
 struct AVFilterGraph;
 struct AVCodecContext;
@@ -50,63 +51,40 @@ class Options;
 class Image;
 class Log;
 
-class FFmpegAudioStream : public AudioStream
-{
-public:
-       FFmpegAudioStream (std::string n, int i, int s, int64_t c)
-               : AudioStream (s, c)
-               , _name (n)
-               , _id (i)
-       {}
-
-       std::string to_string () const;
-
-       std::string name () const {
-               return _name;
-       }
-
-       int id () const {
-               return _id;
-       }
-
-       static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       FFmpegAudioStream (std::string t, boost::optional<int> v);
-       
-       std::string _name;
-       int _id;
-};
-
 /** @class FFmpegDecoder
  *  @brief A decoder using FFmpeg to decode content.
  */
 class FFmpegDecoder : public VideoDecoder, public AudioDecoder
 {
 public:
-       FFmpegDecoder (boost::shared_ptr<Film>, DecodeOptions);
+       FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio, bool subtitles, bool video_sync);
        ~FFmpegDecoder ();
 
-       float frames_per_second () const;
+       float video_frame_rate () const;
        libdcp::Size native_size () const;
-       SourceFrame length () const;
+       ContentVideoFrame video_length () const;
        int time_base_numerator () const;
        int time_base_denominator () const;
        int sample_aspect_ratio_numerator () const;
        int sample_aspect_ratio_denominator () const;
 
-       void set_audio_stream (boost::shared_ptr<AudioStream>);
-       void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
+       std::vector<FFmpegSubtitleStream> subtitle_streams () const {
+               return _subtitle_streams;
+       }
+       
+       std::vector<FFmpegAudioStream> audio_streams () const {
+               return _audio_streams;
+       }
 
        bool seek (double);
-       bool seek_to_last ();
+       bool pass ();
 
 private:
 
-       bool pass ();
-       bool do_seek (double p, bool);
+       /* No copy construction */
+       FFmpegDecoder (FFmpegDecoder const &);
+       FFmpegDecoder& operator= (FFmpegDecoder const &);
+
        PixelFormat pixel_format () const;
        AVSampleFormat audio_sample_format () const;
        int bytes_per_audio_sample () const;
@@ -129,6 +107,8 @@ private:
 
        std::string stream_name (AVStream* s) const;
 
+       boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
        AVFormatContext* _format_context;
        int _video_stream;
        
@@ -148,4 +128,18 @@ private:
 
        std::list<boost::shared_ptr<FilterGraph> > _filter_graphs;
        boost::mutex _filter_graphs_mutex;
+
+        std::vector<FFmpegSubtitleStream> _subtitle_streams;
+        std::vector<FFmpegAudioStream> _audio_streams;
+
+       bool _decode_video;
+       bool _decode_audio;
+       bool _decode_subtitles;
+       bool _video_sync;
+
+       /* It would appear (though not completely verified) that one must have
+          a mutex around calls to avcodec_open* and avcodec_close... and here
+          it is.
+       */
+       static boost::mutex _mutex;
 };
index bd11c1eb577376b8fbf50c04c7ce47d448919da9..35a07b399e3432716f61f64e7d892abf98f056de 100644 (file)
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/date_time.hpp>
+#include <libxml++/libxml++.h>
+#include <libcxml/cxml.h>
 #include "film.h"
 #include "format.h"
 #include "job.h"
 #include "filter.h"
-#include "transcoder.h"
 #include "util.h"
 #include "job_manager.h"
 #include "ab_transcode_job.h"
 #include "transcode_job.h"
 #include "scp_dcp_job.h"
 #include "log.h"
-#include "options.h"
 #include "exceptions.h"
 #include "examine_content_job.h"
 #include "scaler.h"
-#include "decoder_factory.h"
 #include "config.h"
 #include "version.h"
 #include "ui_signaller.h"
-#include "video_decoder.h"
-#include "audio_decoder.h"
-#include "sndfile_decoder.h"
 #include "analyse_audio_job.h"
+#include "playlist.h"
+#include "player.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_content.h"
+#include "sndfile_content.h"
+#include "dcp_content_type.h"
 
 #include "i18n.h"
 
@@ -67,6 +69,8 @@ using std::setfill;
 using std::min;
 using std::make_pair;
 using std::endl;
+using std::cout;
+using std::list;
 using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::to_upper_copy;
@@ -86,18 +90,17 @@ int const Film::state_version = 4;
  */
 
 Film::Film (string d, bool must_exist)
-       : _use_dci_name (true)
-       , _trust_content_header (true)
+       : _playlist (new Playlist)
+       , _use_dci_name (true)
+       , _trust_content_headers (true)
        , _dcp_content_type (0)
-       , _format (0)
+       , _format (Format::from_id ("185"))
        , _scaler (Scaler::from_id ("bicubic"))
        , _trim_start (0)
        , _trim_end (0)
-       , _dcp_ab (false)
-       , _use_content_audio (true)
+       , _ab (false)
        , _audio_gain (0)
        , _audio_delay (0)
-       , _still_duration (10)
        , _with_subtitles (false)
        , _subtitle_offset (0)
        , _subtitle_scale (1)
@@ -105,10 +108,11 @@ Film::Film (string d, bool must_exist)
        , _j2k_bandwidth (200000000)
        , _dci_metadata (Config::instance()->default_dci_metadata ())
        , _dcp_frame_rate (0)
-       , _source_frame_rate (0)
        , _dirty (false)
 {
        set_dci_date_today ();
+
+       _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
        
        /* Make state.directory a complete path without ..s (where possible)
           (Code swiped from Adam Bowen on stackoverflow)
@@ -138,8 +142,6 @@ Film::Film (string d, bool must_exist)
                }
        }
 
-       _sndfile_stream = SndfileStream::create ();
-       
        if (must_exist) {
                read_metadata ();
        }
@@ -151,11 +153,11 @@ Film::Film (Film const & o)
        : boost::enable_shared_from_this<Film> (o)
        /* note: the copied film shares the original's log */
        , _log               (o._log)
+       , _playlist          (new Playlist)
        , _directory         (o._directory)
        , _name              (o._name)
        , _use_dci_name      (o._use_dci_name)
-       , _content           (o._content)
-       , _trust_content_header (o._trust_content_header)
+       , _trust_content_headers (o._trust_content_headers)
        , _dcp_content_type  (o._dcp_content_type)
        , _format            (o._format)
        , _crop              (o._crop)
@@ -163,37 +165,26 @@ Film::Film (Film const & o)
        , _scaler            (o._scaler)
        , _trim_start        (o._trim_start)
        , _trim_end          (o._trim_end)
-       , _dcp_ab            (o._dcp_ab)
-       , _content_audio_stream (o._content_audio_stream)
-       , _external_audio    (o._external_audio)
-       , _use_content_audio (o._use_content_audio)
+       , _ab                (o._ab)
        , _audio_gain        (o._audio_gain)
        , _audio_delay       (o._audio_delay)
-       , _still_duration    (o._still_duration)
-       , _subtitle_stream   (o._subtitle_stream)
        , _with_subtitles    (o._with_subtitles)
        , _subtitle_offset   (o._subtitle_offset)
        , _subtitle_scale    (o._subtitle_scale)
        , _colour_lut        (o._colour_lut)
        , _j2k_bandwidth     (o._j2k_bandwidth)
        , _dci_metadata      (o._dci_metadata)
-       , _dci_date          (o._dci_date)
        , _dcp_frame_rate    (o._dcp_frame_rate)
-       , _size              (o._size)
-       , _length            (o._length)
-       , _content_digest    (o._content_digest)
-       , _content_audio_streams (o._content_audio_streams)
-       , _sndfile_stream    (o._sndfile_stream)
-       , _subtitle_streams  (o._subtitle_streams)
-       , _source_frame_rate (o._source_frame_rate)
+       , _dci_date          (o._dci_date)
        , _dirty             (o._dirty)
 {
+       for (ContentList::const_iterator i = o._content.begin(); i != o._content.end(); ++i) {
+               _content.push_back ((*i)->clone ());
+       }
        
-}
-
-Film::~Film ()
-{
-
+       _playlist->ContentChanged.connect (bind (&Film::content_changed, this, _1, _2));
+       
+       _playlist->setup (_content);
 }
 
 string
@@ -201,6 +192,10 @@ Film::video_state_identifier () const
 {
        assert (format ());
 
+       return "XXX";
+
+#if 0  
+
        pair<string, string> f = Filter::ffmpeg_strings (filters());
 
        stringstream s;
@@ -213,12 +208,13 @@ Film::video_state_identifier () const
          << "_" << j2k_bandwidth()
          << "_" << boost::lexical_cast<int> (colour_lut());
 
-       if (dcp_ab()) {
+       if (ab()) {
                pair<string, string> fa = Filter::ffmpeg_strings (Config::instance()->reference_filters());
                s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second;
        }
 
        return s.str ();
+#endif 
 }
          
 /** @return The path to the directory to write video frame info files to */
@@ -249,7 +245,7 @@ Film::audio_analysis_path () const
 {
        boost::filesystem::path p;
        p /= "analysis";
-       p /= content_digest();
+       p /= "XXX";//content_digest();
        return file (p.string ());
 }
 
@@ -271,12 +267,12 @@ Film::make_dcp ()
                log()->log (String::compose ("Starting to make DCP on %1", buffer));
        }
        
-       log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
-       if (length()) {
-               log()->log (String::compose ("Content length %1", length().get()));
-       }
-       log()->log (String::compose ("Content digest %1", content_digest()));
-       log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
+//     log()->log (String::compose ("Content is %1; type %2", content_path(), (content_type() == STILL ? _("still") : _("video"))));
+//     if (length()) {
+//             log()->log (String::compose ("Content length %1", length().get()));
+//     }
+//     log()->log (String::compose ("Content digest %1", content_digest()));
+//     log()->log (String::compose ("Content at %1 fps, DCP at %2 fps", source_frame_rate(), dcp_frame_rate()));
        log()->log (String::compose ("%1 threads", Config::instance()->num_local_encoding_threads()));
        log()->log (String::compose ("J2K bandwidth %1", j2k_bandwidth()));
 #ifdef DVDOMATIC_DEBUG
@@ -308,19 +304,16 @@ Film::make_dcp ()
                throw MissingSettingError (_("name"));
        }
 
-       DecodeOptions od;
-       od.decode_subtitles = with_subtitles ();
-
        shared_ptr<Job> r;
 
-       if (dcp_ab()) {
-               r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this(), od)));
+       if (ab()) {
+               r = JobManager::instance()->add (shared_ptr<Job> (new ABTranscodeJob (shared_from_this())));
        } else {
-               r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this(), od)));
+               r = JobManager::instance()->add (shared_ptr<Job> (new TranscodeJob (shared_from_this())));
        }
 }
 
-/** Start a job to analyse the audio of our content file */
+/** Start a job to analyse the audio in our Playlist */
 void
 Film::analyse_audio ()
 {
@@ -333,17 +326,12 @@ Film::analyse_audio ()
        JobManager::instance()->add (_analyse_audio_job);
 }
 
-/** Start a job to examine our content file */
+/** Start a job to examine a piece of content */
 void
-Film::examine_content ()
+Film::examine_content (shared_ptr<Content> c)
 {
-       if (_examine_content_job) {
-               return;
-       }
-
-       _examine_content_job.reset (new ExamineContentJob (shared_from_this()));
-       _examine_content_job->Finished.connect (bind (&Film::examine_content_finished, this));
-       JobManager::instance()->add (_examine_content_job);
+       shared_ptr<Job> j (new ExamineContentJob (shared_from_this(), c, trust_content_headers ()));
+       JobManager::instance()->add (j);
 }
 
 void
@@ -358,12 +346,6 @@ Film::analyse_audio_finished ()
        _analyse_audio_job.reset ();
 }
 
-void
-Film::examine_content_finished ()
-{
-       _examine_content_job.reset ();
-}
-
 /** Start a job to send our DCP to the configured TMS */
 void
 Film::send_dcp_to_tms ()
@@ -395,77 +377,55 @@ Film::encoded_frames () const
 void
 Film::write_metadata () const
 {
+       ContentList the_content = content ();
+       
        boost::mutex::scoped_lock lm (_state_mutex);
 
        boost::filesystem::create_directories (directory());
 
-       string const m = file ("metadata");
-       ofstream f (m.c_str ());
-       if (!f.good ()) {
-               throw CreateFileError (m);
-       }
-
-       f << "version " << state_version << endl;
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("Metadata");
 
-       /* User stuff */
-       f << "name " << _name << endl;
-       f << "use_dci_name " << _use_dci_name << endl;
-       f << "content " << _content << endl;
-       f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl;
+       root->add_child("Version")->add_child_text (boost::lexical_cast<string> (state_version));
+       root->add_child("Name")->add_child_text (_name);
+       root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
+       root->add_child("TrustContentHeaders")->add_child_text (_trust_content_headers ? "1" : "0");
        if (_dcp_content_type) {
-               f << "dcp_content_type " << _dcp_content_type->dci_name () << endl;
+               root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ());
        }
        if (_format) {
-               f << "format " << _format->as_metadata () << endl;
-       }
-       f << "left_crop " << _crop.left << endl;
-       f << "right_crop " << _crop.right << endl;
-       f << "top_crop " << _crop.top << endl;
-       f << "bottom_crop " << _crop.bottom << endl;
-       for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
-               f << "filter " << (*i)->id () << endl;
-       }
-       f << "scaler " << _scaler->id () << endl;
-       f << "trim_start " << _trim_start << endl;
-       f << "trim_end " << _trim_end << endl;
-       f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl;
-       if (_content_audio_stream) {
-               f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl;
+               root->add_child("Format")->add_child_text (_format->id ());
        }
-       for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) {
-               f << "external_audio " << *i << endl;
-       }
-       f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl;
-       f << "audio_gain " << _audio_gain << endl;
-       f << "audio_delay " << _audio_delay << endl;
-       f << "still_duration " << _still_duration << endl;
-       if (_subtitle_stream) {
-               f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl;
-       }
-       f << "with_subtitles " << _with_subtitles << endl;
-       f << "subtitle_offset " << _subtitle_offset << endl;
-       f << "subtitle_scale " << _subtitle_scale << endl;
-       f << "colour_lut " << _colour_lut << endl;
-       f << "j2k_bandwidth " << _j2k_bandwidth << endl;
-       _dci_metadata.write (f);
-       f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl;
-       f << "dcp_frame_rate " << _dcp_frame_rate << endl;
-       f << "width " << _size.width << endl;
-       f << "height " << _size.height << endl;
-       f << "length " << _length.get_value_or(0) << endl;
-       f << "content_digest " << _content_digest << endl;
-
-       for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
-               f << "content_audio_stream " << (*i)->to_string () << endl;
-       }
-
-       f << "external_audio_stream " << _sndfile_stream->to_string() << endl;
+       root->add_child("LeftCrop")->add_child_text (boost::lexical_cast<string> (_crop.left));
+       root->add_child("RightCrop")->add_child_text (boost::lexical_cast<string> (_crop.right));
+       root->add_child("TopCrop")->add_child_text (boost::lexical_cast<string> (_crop.top));
+       root->add_child("BottomCrop")->add_child_text (boost::lexical_cast<string> (_crop.bottom));
 
-       for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
-               f << "subtitle_stream " << (*i)->to_string () << endl;
+       for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
+               root->add_child("Filter")->add_child_text ((*i)->id ());
        }
-
-       f << "source_frame_rate " << _source_frame_rate << endl;
+       
+       root->add_child("Scaler")->add_child_text (_scaler->id ());
+       root->add_child("TrimStart")->add_child_text (boost::lexical_cast<string> (_trim_start));
+       root->add_child("TrimEnd")->add_child_text (boost::lexical_cast<string> (_trim_end));
+       root->add_child("AB")->add_child_text (_ab ? "1" : "0");
+       root->add_child("AudioGain")->add_child_text (boost::lexical_cast<string> (_audio_gain));
+       root->add_child("AudioDelay")->add_child_text (boost::lexical_cast<string> (_audio_delay));
+       root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0");
+       root->add_child("SubtitleOffset")->add_child_text (boost::lexical_cast<string> (_subtitle_offset));
+       root->add_child("SubtitleScale")->add_child_text (boost::lexical_cast<string> (_subtitle_scale));
+       root->add_child("ColourLUT")->add_child_text (boost::lexical_cast<string> (_colour_lut));
+       root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth));
+       _dci_metadata.as_xml (root->add_child ("DCIMetadata"));
+       root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate));
+       root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date));
+       _audio_mapping.as_xml (root->add_child("AudioMapping"));
+
+       for (ContentList::iterator i = the_content.begin(); i != the_content.end(); ++i) {
+               (*i)->as_xml (root->add_child ("Content"));
+       }
+
+       doc.write_to_file_formatted (file ("metadata.xml"));
        
        _dirty = false;
 }
@@ -476,160 +436,80 @@ Film::read_metadata ()
 {
        boost::mutex::scoped_lock lm (_state_mutex);
 
-       _external_audio.clear ();
-       _content_audio_streams.clear ();
-       _subtitle_streams.clear ();
-
-       boost::optional<int> version;
-
-       /* Backward compatibility things */
-       boost::optional<int> audio_sample_rate;
-       boost::optional<int> audio_stream_index;
-       boost::optional<int> subtitle_stream_index;
-
-       ifstream f (file ("metadata").c_str());
-       if (!f.good()) {
-               throw OpenFileError (file ("metadata"));
+       if (boost::filesystem::exists (file ("metadata")) && !boost::filesystem::exists (file ("metadata.xml"))) {
+               throw StringError (_("This film was created with an older version of DVD-o-matic, and unfortunately it cannot be loaded into this version.  You will need to create a new Film, re-add your content and set it up again.  Sorry!"));
        }
-       
-       multimap<string, string> kv = read_key_value (f);
 
-       /* We need version before anything else */
-       multimap<string, string>::iterator v = kv.find ("version");
-       if (v != kv.end ()) {
-               version = atoi (v->second.c_str());
-       }
+       cxml::File f (file ("metadata.xml"), "Metadata");
        
-       for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) {
-               string const k = i->first;
-               string const v = i->second;
+       _name = f.string_child ("Name");
+       _use_dci_name = f.bool_child ("UseDCIName");
+       _trust_content_headers = f.bool_child ("TrustContentHeaders");
 
-               if (k == "audio_sample_rate") {
-                       audio_sample_rate = atoi (v.c_str());
-               }
-
-               /* User-specified stuff */
-               if (k == "name") {
-                       _name = v;
-               } else if (k == "use_dci_name") {
-                       _use_dci_name = (v == "1");
-               } else if (k == "content") {
-                       _content = v;
-               } else if (k == "trust_content_header") {
-                       _trust_content_header = (v == "1");
-               } else if (k == "dcp_content_type") {
-                       if (version < 3) {
-                               _dcp_content_type = DCPContentType::from_pretty_name (v);
-                       } else {
-                               _dcp_content_type = DCPContentType::from_dci_name (v);
-                       }
-               } else if (k == "format") {
-                       _format = Format::from_metadata (v);
-               } else if (k == "left_crop") {
-                       _crop.left = atoi (v.c_str ());
-               } else if (k == "right_crop") {
-                       _crop.right = atoi (v.c_str ());
-               } else if (k == "top_crop") {
-                       _crop.top = atoi (v.c_str ());
-               } else if (k == "bottom_crop") {
-                       _crop.bottom = atoi (v.c_str ());
-               } else if (k == "filter") {
-                       _filters.push_back (Filter::from_id (v));
-               } else if (k == "scaler") {
-                       _scaler = Scaler::from_id (v);
-               } else if ( ((!version || version < 2) && k == "dcp_trim_start") || k == "trim_start") {
-                       _trim_start = atoi (v.c_str ());
-               } else if ( ((!version || version < 2) && k == "dcp_trim_end") || k == "trim_end") {
-                       _trim_end = atoi (v.c_str ());
-               } else if (k == "dcp_ab") {
-                       _dcp_ab = (v == "1");
-               } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) {
-                       if (!version) {
-                               audio_stream_index = atoi (v.c_str ());
-                       } else {
-                               _content_audio_stream = audio_stream_factory (v, version);
-                       }
-               } else if (k == "external_audio") {
-                       _external_audio.push_back (v);
-               } else if (k == "use_content_audio") {
-                       _use_content_audio = (v == "1");
-               } else if (k == "audio_gain") {
-                       _audio_gain = atof (v.c_str ());
-               } else if (k == "audio_delay") {
-                       _audio_delay = atoi (v.c_str ());
-               } else if (k == "still_duration") {
-                       _still_duration = atoi (v.c_str ());
-               } else if (k == "selected_subtitle_stream") {
-                       if (!version) {
-                               subtitle_stream_index = atoi (v.c_str ());
-                       } else {
-                               _subtitle_stream = subtitle_stream_factory (v, version);
-                       }
-               } else if (k == "with_subtitles") {
-                       _with_subtitles = (v == "1");
-               } else if (k == "subtitle_offset") {
-                       _subtitle_offset = atoi (v.c_str ());
-               } else if (k == "subtitle_scale") {
-                       _subtitle_scale = atof (v.c_str ());
-               } else if (k == "colour_lut") {
-                       _colour_lut = atoi (v.c_str ());
-               } else if (k == "j2k_bandwidth") {
-                       _j2k_bandwidth = atoi (v.c_str ());
-               } else if (k == "dci_date") {
-                       _dci_date = boost::gregorian::from_undelimited_string (v);
-               } else if (k == "dcp_frame_rate") {
-                       _dcp_frame_rate = atoi (v.c_str ());
+       {
+               optional<string> c = f.optional_string_child ("DCPContentType");
+               if (c) {
+                       _dcp_content_type = DCPContentType::from_dci_name (c.get ());
                }
+       }
 
-               _dci_metadata.read (k, v);
-               
-               /* Cached stuff */
-               if (k == "width") {
-                       _size.width = atoi (v.c_str ());
-               } else if (k == "height") {
-                       _size.height = atoi (v.c_str ());
-               } else if (k == "length") {
-                       int const vv = atoi (v.c_str ());
-                       if (vv) {
-                               _length = vv;
-                       }
-               } else if (k == "content_digest") {
-                       _content_digest = v;
-               } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) {
-                       _content_audio_streams.push_back (audio_stream_factory (v, version));
-               } else if (k == "external_audio_stream") {
-                       _sndfile_stream = audio_stream_factory (v, version);
-               } else if (k == "subtitle_stream") {
-                       _subtitle_streams.push_back (subtitle_stream_factory (v, version));
-               } else if (k == "source_frame_rate") {
-                       _source_frame_rate = atof (v.c_str ());
-               } else if (version < 4 && k == "frames_per_second") {
-                       _source_frame_rate = atof (v.c_str ());
-                       /* Fill in what would have been used for DCP frame rate by the older version */
-                       _dcp_frame_rate = best_dcp_frame_rate (_source_frame_rate);
+       {
+               optional<string> c = f.optional_string_child ("Format");
+               if (c) {
+                       _format = Format::from_id (c.get ());
                }
        }
 
-       if (!version) {
-               if (audio_sample_rate) {
-                       /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */
-                       for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) {
-                               (*i)->set_sample_rate (audio_sample_rate.get());
-                       }
-               }
+       _crop.left = f.number_child<int> ("LeftCrop");
+       _crop.right = f.number_child<int> ("RightCrop");
+       _crop.top = f.number_child<int> ("TopCrop");
+       _crop.bottom = f.number_child<int> ("BottomCrop");
 
-               /* also the selected stream was specified as an index */
-               if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) {
-                       _content_audio_stream = _content_audio_streams[audio_stream_index.get()];
+       {
+               list<shared_ptr<cxml::Node> > c = f.node_children ("Filter");
+               for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+                       _filters.push_back (Filter::from_id ((*i)->content ()));
                }
+       }
 
-               /* similarly the subtitle */
-               if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) {
-                       _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()];
+       _scaler = Scaler::from_id (f.string_child ("Scaler"));
+       _trim_start = f.number_child<int> ("TrimStart");
+       _trim_end = f.number_child<int> ("TrimEnd");
+       _ab = f.bool_child ("AB");
+       _audio_gain = f.number_child<float> ("AudioGain");
+       _audio_delay = f.number_child<int> ("AudioDelay");
+       _with_subtitles = f.bool_child ("WithSubtitles");
+       _subtitle_offset = f.number_child<float> ("SubtitleOffset");
+       _subtitle_scale = f.number_child<float> ("SubtitleScale");
+       _colour_lut = f.number_child<int> ("ColourLUT");
+       _j2k_bandwidth = f.number_child<int> ("J2KBandwidth");
+       _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata"));
+       _dcp_frame_rate = f.number_child<int> ("DCPFrameRate");
+       _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate"));
+
+       list<shared_ptr<cxml::Node> > c = f.node_children ("Content");
+       for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
+
+               string const type = (*i)->string_child ("Type");
+               boost::shared_ptr<Content> c;
+               
+               if (type == "FFmpeg") {
+                       c.reset (new FFmpegContent (*i));
+               } else if (type == "ImageMagick") {
+                       c.reset (new ImageMagickContent (*i));
+               } else if (type == "Sndfile") {
+                       c.reset (new SndfileContent (*i));
                }
+
+               _content.push_back (c);
        }
-               
+
+       /* This must come after we've loaded the content, as we're looking things up in _content */
+       _audio_mapping.set_from_xml (_content, f.node_child ("AudioMapping"));
+
        _dirty = false;
+
+       _playlist->setup (_content);
 }
 
 libdcp::Size
@@ -676,47 +556,18 @@ Film::file (string f) const
        return p.string ();
 }
 
-/** @return full path of the content (actual video) file
- *  of the Film.
- */
-string
-Film::content_path () const
-{
-       boost::mutex::scoped_lock lm (_state_mutex);
-       if (boost::filesystem::path(_content).has_root_directory ()) {
-               return _content;
-       }
-
-       return file (_content);
-}
-
-ContentType
-Film::content_type () const
-{
-       if (boost::filesystem::is_directory (_content)) {
-               /* Directory of images, we assume */
-               return VIDEO;
-       }
-
-       if (still_image_file (_content)) {
-               return STILL;
-       }
-
-       return VIDEO;
-}
-
 /** @return The sampling rate that we will resample the audio to */
 int
 Film::target_audio_sample_rate () const
 {
-       if (!audio_stream()) {
+       if (!has_audio ()) {
                return 0;
        }
        
        /* Resample to a DCI-approved sample rate */
-       double t = dcp_audio_sample_rate (audio_stream()->sample_rate());
+       double t = dcp_audio_sample_rate (audio_frame_rate());
 
-       FrameRateConversion frc (source_frame_rate(), dcp_frame_rate());
+       FrameRateConversion frc (video_frame_rate(), dcp_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
@@ -725,18 +576,12 @@ Film::target_audio_sample_rate () const
        */
 
        if (frc.change_speed) {
-               t *= source_frame_rate() * frc.factor() / dcp_frame_rate();
+               t *= video_frame_rate() * frc.factor() / dcp_frame_rate();
        }
 
        return rint (t);
 }
 
-int
-Film::still_duration_in_frames () const
-{
-       return still_duration() * source_frame_rate();
-}
-
 /** @return a DCI-compliant name for a DCP of this film */
 string
 Film::dci_name (bool if_created_now) const
@@ -783,7 +628,7 @@ Film::dci_name (bool if_created_now) const
                }
        }
 
-       switch (audio_channels()) {
+       switch (audio_channels ()) {
        case 1:
                d << "_10";
                break;
@@ -862,110 +707,21 @@ Film::set_use_dci_name (bool u)
 }
 
 void
-Film::set_content (string c)
-{
-       string check = directory ();
-
-       boost::filesystem::path slash ("/");
-       string platform_slash = slash.make_preferred().string ();
-
-       if (!ends_with (check, platform_slash)) {
-               check += platform_slash;
-       }
-       
-       if (boost::filesystem::path(c).has_root_directory () && starts_with (c, check)) {
-               c = c.substr (_directory.length() + 1);
-       }
-
-       string old_content;
-       
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (c == _content) {
-                       return;
-               }
-
-               old_content = _content;
-               _content = c;
-       }
-
-       /* Reset streams here in case the new content doesn't have one or the other */
-       _content_audio_stream = shared_ptr<AudioStream> ();
-       _subtitle_stream = shared_ptr<SubtitleStream> ();
-
-       /* Start off using content audio */
-       set_use_content_audio (true);
-
-       /* Create a temporary decoder so that we can get information
-          about the content.
-       */
-
-       try {
-               Decoders d = decoder_factory (shared_from_this(), DecodeOptions());
-               
-               set_size (d.video->native_size ());
-               set_source_frame_rate (d.video->frames_per_second ());
-               set_dcp_frame_rate (best_dcp_frame_rate (source_frame_rate ()));
-               set_subtitle_streams (d.video->subtitle_streams ());
-               if (d.audio) {
-                       set_content_audio_streams (d.audio->audio_streams ());
-               }
-
-               {
-                       boost::mutex::scoped_lock lm (_state_mutex);
-                       _content = c;
-               }
-               
-               signal_changed (CONTENT);
-               
-               /* Start off with the first audio and subtitle streams */
-               if (d.audio && !d.audio->audio_streams().empty()) {
-                       set_content_audio_stream (d.audio->audio_streams().front());
-               }
-               
-               if (!d.video->subtitle_streams().empty()) {
-                       set_subtitle_stream (d.video->subtitle_streams().front());
-               }
-               
-               examine_content ();
-
-       } catch (...) {
-
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content = old_content;
-               throw;
-
-       }
-
-       /* Default format */
-       switch (content_type()) {
-       case STILL:
-               set_format (Format::from_id ("var-185"));
-               break;
-       case VIDEO:
-               set_format (Format::from_id ("185"));
-               break;
-       }
-
-       /* Still image DCPs must use external audio */
-       if (content_type() == STILL) {
-               set_use_content_audio (false);
-       }
-}
-
-void
-Film::set_trust_content_header (bool t)
+Film::set_trust_content_headers (bool t)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _trust_content_header = t;
+               _trust_content_headers = t;
        }
        
-       signal_changed (TRUST_CONTENT_HEADER);
+       signal_changed (TRUST_CONTENT_HEADERS);
 
-       if (!_trust_content_header && !content().empty()) {
+       if (!_trust_content_headers && !content().empty()) {
                /* We just said that we don't trust the content's header */
-               examine_content ();
+               ContentList c = content ();
+               for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
+                       examine_content (*i);
+               }
        }
 }
               
@@ -1097,50 +853,13 @@ Film::set_trim_end (int t)
 }
 
 void
-Film::set_dcp_ab (bool a)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _dcp_ab = a;
-       }
-       signal_changed (DCP_AB);
-}
-
-void
-Film::set_content_audio_stream (shared_ptr<AudioStream> s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_audio_stream = s;
-       }
-       signal_changed (CONTENT_AUDIO_STREAM);
-}
-
-void
-Film::set_external_audio (vector<string> a)
+Film::set_ab (bool a)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _external_audio = a;
+               _ab = a;
        }
-
-       shared_ptr<SndfileDecoder> decoder (new SndfileDecoder (shared_from_this(), DecodeOptions()));
-       if (decoder->audio_stream()) {
-               _sndfile_stream = decoder->audio_stream ();
-       }
-       
-       signal_changed (EXTERNAL_AUDIO);
-}
-
-void
-Film::set_use_content_audio (bool e)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _use_content_audio = e;
-       }
-
-       signal_changed (USE_CONTENT_AUDIO);
+       signal_changed (AB);
 }
 
 void
@@ -1163,26 +882,6 @@ Film::set_audio_delay (int d)
        signal_changed (AUDIO_DELAY);
 }
 
-void
-Film::set_still_duration (int d)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _still_duration = d;
-       }
-       signal_changed (STILL_DURATION);
-}
-
-void
-Film::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_stream = s;
-       }
-       signal_changed (SUBTITLE_STREAM);
-}
-
 void
 Film::set_with_subtitles (bool w)
 {
@@ -1255,184 +954,312 @@ Film::set_dcp_frame_rate (int f)
 }
 
 void
-Film::set_size (libdcp::Size s)
+Film::signal_changed (Property p)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _size = s;
+               _dirty = true;
+       }
+
+       switch (p) {
+       case Film::CONTENT:
+               _playlist->setup (content ());
+               set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
+               set_audio_mapping (_playlist->default_audio_mapping ());
+               break;
+       default:
+               break;
+       }
+
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (Changed), p));
        }
-       signal_changed (SIZE);
 }
 
 void
-Film::set_length (SourceFrame l)
+Film::set_dci_date_today ()
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _length = l;
-       }
-       signal_changed (LENGTH);
+       _dci_date = boost::gregorian::day_clock::local_day ();
 }
 
-void
-Film::unset_length ()
+string
+Film::info_path (int f) const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _length = boost::none;
+       boost::filesystem::path p;
+       p /= info_dir ();
+
+       stringstream s;
+       s.width (8);
+       s << setfill('0') << f << ".md5";
+
+       p /= s.str();
+
+       /* info_dir() will already have added any initial bit of the path,
+          so don't call file() on this.
+       */
+       return p.string ();
+}
+
+string
+Film::j2c_path (int f, bool t) const
+{
+       boost::filesystem::path p;
+       p /= "j2c";
+       p /= video_state_identifier ();
+
+       stringstream s;
+       s.width (8);
+       s << setfill('0') << f << ".j2c";
+
+       if (t) {
+               s << ".tmp";
        }
-       signal_changed (LENGTH);
+
+       p /= s.str();
+       return file (p.string ());
 }
 
-void
-Film::set_content_digest (string d)
+/** Make an educated guess as to whether we have a complete DCP
+ *  or not.
+ *  @return true if we do.
+ */
+
+bool
+Film::have_dcp () const
 {
-       {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               _content_digest = d;
+       try {
+               libdcp::DCP dcp (dir (dcp_name()));
+               dcp.read ();
+       } catch (...) {
+               return false;
        }
-       _dirty = true;
+
+       return true;
+}
+
+shared_ptr<Player>
+Film::player () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
 }
 
 void
-Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s)
+Film::add_content (shared_ptr<Content> c)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _content_audio_streams = s;
+               _content.push_back (c);
        }
-       signal_changed (CONTENT_AUDIO_STREAMS);
+
+       signal_changed (CONTENT);
+
+       examine_content (c);
 }
 
 void
-Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s)
+Film::remove_content (shared_ptr<Content> c)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _subtitle_streams = s;
+               ContentList::iterator i = find (_content.begin(), _content.end(), c);
+               if (i != _content.end ()) {
+                       _content.erase (i);
+               }
        }
-       signal_changed (SUBTITLE_STREAMS);
+
+       signal_changed (CONTENT);
 }
 
 void
-Film::set_source_frame_rate (float f)
+Film::move_content_earlier (shared_ptr<Content> c)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _source_frame_rate = f;
+               ContentList::iterator i = find (_content.begin(), _content.end(), c);
+               if (i == _content.begin () || i == _content.end()) {
+                       return;
+               }
+
+               ContentList::iterator j = i;
+               --j;
+
+               swap (*i, *j);
        }
-       signal_changed (SOURCE_FRAME_RATE);
+
+       signal_changed (CONTENT);
 }
-       
+
 void
-Film::signal_changed (Property p)
+Film::move_content_later (shared_ptr<Content> c)
 {
        {
                boost::mutex::scoped_lock lm (_state_mutex);
-               _dirty = true;
-       }
+               ContentList::iterator i = find (_content.begin(), _content.end(), c);
+               if (i == _content.end()) {
+                       return;
+               }
 
-       if (ui_signaller) {
-               ui_signaller->emit (boost::bind (boost::ref (Changed), p));
+               ContentList::iterator j = i;
+               ++j;
+               if (j == _content.end ()) {
+                       return;
+               }
+
+               swap (*i, *j);
        }
+
+       signal_changed (CONTENT);
+
+}
+
+ContentAudioFrame
+Film::audio_length () const
+{
+       return _playlist->audio_length ();
 }
 
 int
 Film::audio_channels () const
 {
-       shared_ptr<AudioStream> s = audio_stream ();
-       if (!s) {
-               return 0;
-       }
+       return _playlist->audio_channels ();
+}
 
-       return s->channels ();
+int
+Film::audio_frame_rate () const
+{
+       return _playlist->audio_frame_rate ();
 }
 
-void
-Film::set_dci_date_today ()
+int64_t
+Film::audio_channel_layout () const
 {
-       _dci_date = boost::gregorian::day_clock::local_day ();
+       return _playlist->audio_channel_layout ();
 }
 
-boost::shared_ptr<AudioStream>
-Film::audio_stream () const
+bool
+Film::has_audio () const
 {
-       if (use_content_audio()) {
-               return _content_audio_stream;
-       }
+       return _playlist->has_audio ();
+}
 
-       return _sndfile_stream;
+float
+Film::video_frame_rate () const
+{
+       return _playlist->video_frame_rate ();
 }
 
-string
-Film::info_path (int f) const
+libdcp::Size
+Film::video_size () const
 {
-       boost::filesystem::path p;
-       p /= info_dir ();
+       return _playlist->video_size ();
+}
 
-       stringstream s;
-       s.width (8);
-       s << setfill('0') << f << ".md5";
+ContentVideoFrame
+Film::video_length () const
+{
+       return _playlist->video_length ();
+}
 
-       p /= s.str();
+/** Unfortunately this is needed as the GUI has FFmpeg-specific controls */
+shared_ptr<FFmpegContent>
+Film::ffmpeg () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       
+       for (ContentList::const_iterator i = _content.begin (); i != _content.end(); ++i) {
+               shared_ptr<FFmpegContent> f = boost::dynamic_pointer_cast<FFmpegContent> (*i);
+               if (f) {
+                       return f;
+               }
+       }
 
-       /* info_dir() will already have added any initial bit of the path,
-          so don't call file() on this.
-       */
-       return p.string ();
+       return shared_ptr<FFmpegContent> ();
 }
 
-string
-Film::j2c_path (int f, bool t) const
+vector<FFmpegSubtitleStream>
+Film::ffmpeg_subtitle_streams () const
 {
-       boost::filesystem::path p;
-       p /= "j2c";
-       p /= video_state_identifier ();
+       shared_ptr<FFmpegContent> f = ffmpeg ();
+       if (f) {
+               return f->subtitle_streams ();
+       }
 
-       stringstream s;
-       s.width (8);
-       s << setfill('0') << f << ".j2c";
+       return vector<FFmpegSubtitleStream> ();
+}
 
-       if (t) {
-               s << ".tmp";
+boost::optional<FFmpegSubtitleStream>
+Film::ffmpeg_subtitle_stream () const
+{
+       shared_ptr<FFmpegContent> f = ffmpeg ();
+       if (f) {
+               return f->subtitle_stream ();
        }
 
-       p /= s.str();
-       return file (p.string ());
+       return boost::none;
 }
 
-/** Make an educated guess as to whether we have a complete DCP
- *  or not.
- *  @return true if we do.
- */
+vector<FFmpegAudioStream>
+Film::ffmpeg_audio_streams () const
+{
+       shared_ptr<FFmpegContent> f = ffmpeg ();
+       if (f) {
+               return f->audio_streams ();
+       }
 
-bool
-Film::have_dcp () const
+       return vector<FFmpegAudioStream> ();
+}
+
+boost::optional<FFmpegAudioStream>
+Film::ffmpeg_audio_stream () const
 {
-       try {
-               libdcp::DCP dcp (dir (dcp_name()));
-               dcp.read ();
-       } catch (...) {
-               return false;
+       shared_ptr<FFmpegContent> f = ffmpeg ();
+       if (f) {
+               return f->audio_stream ();
        }
 
-       return true;
+       return boost::none;
 }
 
-bool
-Film::has_audio () const
+void
+Film::set_ffmpeg_subtitle_stream (FFmpegSubtitleStream s)
 {
-       if (use_content_audio()) {
-               return audio_stream();
+       shared_ptr<FFmpegContent> f = ffmpeg ();
+       if (f) {
+               f->set_subtitle_stream (s);
        }
+}
 
-       vector<string> const e = external_audio ();
-       for (vector<string>::const_iterator i = e.begin(); i != e.end(); ++i) {
-               if (!i->empty ()) {
-                       return true;
-               }
+void
+Film::set_ffmpeg_audio_stream (FFmpegAudioStream s)
+{
+       shared_ptr<FFmpegContent> f = ffmpeg ();
+       if (f) {
+               f->set_audio_stream (s);
+       }
+}
+
+void
+Film::set_audio_mapping (AudioMapping m)
+{
+       {
+               boost::mutex::scoped_lock lm (_state_mutex);
+               _audio_mapping = m;
        }
 
-       return false;
+       signal_changed (AUDIO_MAPPING);
 }
 
+void
+Film::content_changed (boost::weak_ptr<Content> c, int p)
+{
+       if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
+               set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ()));
+       } else if (p == AudioContentProperty::AUDIO_CHANNELS) {
+               set_audio_mapping (_playlist->default_audio_mapping ());
+       }               
+
+       if (ui_signaller) {
+               ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p));
+       }
+}
index adc4b0eec65137c22a2c2b6318dd65b6bdb8b090..532d32bdcc5477c3ee863231743101cb714416c6 100644 (file)
@@ -18,8 +18,8 @@
 */
 
 /** @file  src/film.h
- *  @brief A representation of a piece of video (with sound), including naming,
- *  the source content file, and how it should be presented in a DCP.
+ *  @brief A representation of some audio and video content, and details of
+ *  how they should be presented in a DCP.
  */
 
 #ifndef DVDOMATIC_FILM_H
 #include <boost/thread.hpp>
 #include <boost/signals2.hpp>
 #include <boost/enable_shared_from_this.hpp>
-extern "C" {
-#include <libavcodec/avcodec.h>
-}
-#include "dcp_content_type.h"
 #include "util.h"
-#include "stream.h"
 #include "dci_metadata.h"
+#include "types.h"
+#include "ffmpeg_content.h"
+#include "audio_mapping.h"
 
+class DCPContentType;
 class Format;
 class Job;
 class Filter;
@@ -47,19 +46,19 @@ class Log;
 class ExamineContentJob;
 class AnalyseAudioJob;
 class ExternalAudioStream;
+class Content;
+class Player;
+class Playlist;
 
 /** @class Film
- *  @brief A representation of a video, maybe with sound.
- *
- *  A representation of a piece of video (maybe with sound), including naming,
- *  the source content file, and how it should be presented in a DCP.
+ *  @brief A representation of some audio and video content, and details of
+ *  how they should be presented in a DCP.
  */
 class Film : public boost::enable_shared_from_this<Film>
 {
 public:
        Film (std::string d, bool must_exist = true);
        Film (Film const &);
-       ~Film ();
 
        std::string info_dir () const;
        std::string j2c_path (int f, bool t) const;
@@ -68,10 +67,9 @@ public:
        std::string video_mxf_filename () const;
        std::string audio_analysis_path () const;
 
-       void examine_content ();
+       void examine_content (boost::shared_ptr<Content>);
        void analyse_audio ();
        void send_dcp_to_tms ();
-
        void make_dcp ();
 
        /** @return Logger.
@@ -86,13 +84,9 @@ public:
        std::string file (std::string f) const;
        std::string dir (std::string d) const;
 
-       std::string content_path () const;
-       ContentType content_type () const;
-       
        int target_audio_sample_rate () const;
        
        void write_metadata () const;
-       void read_metadata ();
 
        libdcp::Size cropped_size (libdcp::Size) const;
        std::string dci_name (bool if_created_now) const;
@@ -103,11 +97,29 @@ public:
                return _dirty;
        }
 
+       bool have_dcp () const;
+
+       boost::shared_ptr<Player> player () const;
+
+       /* Proxies for some Playlist methods */
+
+       ContentAudioFrame audio_length () const;
        int audio_channels () const;
+       int audio_frame_rate () const;
+       int64_t audio_channel_layout () const;
+       bool has_audio () const;
+       
+       float video_frame_rate () const;
+       libdcp::Size video_size () const;
+       ContentVideoFrame video_length () const;        
 
-       void set_dci_date_today ();
+       std::vector<FFmpegSubtitleStream> ffmpeg_subtitle_streams () const;
+       boost::optional<FFmpegSubtitleStream> ffmpeg_subtitle_stream () const;
+       std::vector<FFmpegAudioStream> ffmpeg_audio_streams () const;
+       boost::optional<FFmpegAudioStream> ffmpeg_audio_stream () const;
 
-       bool have_dcp () const;
+       void set_ffmpeg_subtitle_stream (FFmpegSubtitleStream);
+       void set_ffmpeg_audio_stream (FFmpegAudioStream);
 
        /** Identifiers for the parts of our state;
            used for signalling changes.
@@ -116,8 +128,9 @@ public:
                NONE,
                NAME,
                USE_DCI_NAME,
+               TRUST_CONTENT_HEADERS,
+               /** The content list has changed (i.e. content has been added, moved around or removed) */
                CONTENT,
-               TRUST_CONTENT_HEADER,
                DCP_CONTENT_TYPE,
                FORMAT,
                CROP,
@@ -125,26 +138,17 @@ public:
                SCALER,
                TRIM_START,
                TRIM_END,
-               DCP_AB,
-               CONTENT_AUDIO_STREAM,
-               EXTERNAL_AUDIO,
-               USE_CONTENT_AUDIO,
+               AB,
                AUDIO_GAIN,
                AUDIO_DELAY,
-               STILL_DURATION,
-               SUBTITLE_STREAM,
                WITH_SUBTITLES,
                SUBTITLE_OFFSET,
                SUBTITLE_SCALE,
                COLOUR_LUT,
                J2K_BANDWIDTH,
                DCI_METADATA,
-               SIZE,
-               LENGTH,
-               CONTENT_AUDIO_STREAMS,
-               SUBTITLE_STREAMS,
-               SOURCE_FRAME_RATE,
-               DCP_FRAME_RATE
+               DCP_FRAME_RATE,
+               AUDIO_MAPPING
        };
 
 
@@ -165,14 +169,14 @@ public:
                return _use_dci_name;
        }
 
-       std::string content () const {
+       bool trust_content_headers () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _content;
+               return _trust_content_headers;
        }
 
-       bool trust_content_header () const {
+       ContentList content () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _trust_content_header;
+               return _content;
        }
 
        DCPContentType const * dcp_content_type () const {
@@ -210,26 +214,11 @@ public:
                return _trim_end;
        }
 
-       bool dcp_ab () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _dcp_ab;
-       }
-
-       boost::shared_ptr<AudioStream> content_audio_stream () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_audio_stream;
-       }
-
-       std::vector<std::string> external_audio () const {
+       bool ab () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _external_audio;
+               return _ab;
        }
 
-       bool use_content_audio () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _use_content_audio;
-       }
-       
        float audio_gain () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _audio_gain;
@@ -240,18 +229,6 @@ public:
                return _audio_delay;
        }
 
-       int still_duration () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _still_duration;
-       }
-
-       int still_duration_in_frames () const;
-
-       boost::shared_ptr<SubtitleStream> subtitle_stream () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_stream;
-       }
-
        bool with_subtitles () const {
                boost::mutex::scoped_lock lm (_state_mutex);
                return _with_subtitles;
@@ -286,51 +263,22 @@ public:
                boost::mutex::scoped_lock lm (_state_mutex);
                return _dcp_frame_rate;
        }
-       
-       libdcp::Size size () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _size;
-       }
-
-       boost::optional<SourceFrame> length () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _length;
-       }
-       
-       std::string content_digest () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_digest;
-       }
-       
-       std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               return _content_audio_streams;
-       }
 
-       std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
+       AudioMapping audio_mapping () const {
                boost::mutex::scoped_lock lm (_state_mutex);
-               return _subtitle_streams;
-       }
-       
-       float source_frame_rate () const {
-               boost::mutex::scoped_lock lm (_state_mutex);
-               if (content_type() == STILL) {
-                       return 24;
-               }
-               
-               return _source_frame_rate;
+               return _audio_mapping;
        }
 
-       boost::shared_ptr<AudioStream> audio_stream () const;
-       bool has_audio () const;
-       
        /* SET */
 
        void set_directory (std::string);
        void set_name (std::string);
        void set_use_dci_name (bool);
-       void set_content (std::string);
-       void set_trust_content_header (bool);
+       void set_trust_content_headers (bool);
+       void add_content (boost::shared_ptr<Content>);
+       void remove_content (boost::shared_ptr<Content>);
+       void move_content_earlier (boost::shared_ptr<Content>);
+       void move_content_later (boost::shared_ptr<Content>);
        void set_dcp_content_type (DCPContentType const *);
        void set_format (Format const *);
        void set_crop (Crop);
@@ -342,14 +290,9 @@ public:
        void set_scaler (Scaler const *);
        void set_trim_start (int);
        void set_trim_end (int);
-       void set_dcp_ab (bool);
-       void set_content_audio_stream (boost::shared_ptr<AudioStream>);
-       void set_external_audio (std::vector<std::string>);
-       void set_use_content_audio (bool);
+       void set_ab (bool);
        void set_audio_gain (float);
        void set_audio_delay (int);
-       void set_still_duration (int);
-       void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
        void set_with_subtitles (bool);
        void set_subtitle_offset (int);
        void set_subtitle_scale (float);
@@ -357,17 +300,15 @@ public:
        void set_j2k_bandwidth (int);
        void set_dci_metadata (DCIMetadata);
        void set_dcp_frame_rate (int);
-       void set_size (libdcp::Size);
-       void set_length (SourceFrame);
-       void unset_length ();
-       void set_content_digest (std::string);
-       void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >);
-       void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >);
-       void set_source_frame_rate (float);
-
-       /** Emitted when some property has changed */
+       void set_dci_date_today ();
+       void set_audio_mapping (AudioMapping);
+
+       /** Emitted when some property has of the Film has changed */
        mutable boost::signals2::signal<void (Property)> Changed;
 
+       /** Emitted when some property of our content has changed */
+       mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+
        boost::signals2::signal<void ()> AudioAnalysisSucceeded;
 
        /** Current version number of the state file */
@@ -375,18 +316,19 @@ public:
 
 private:
        
+       void signal_changed (Property);
+       void analyse_audio_finished ();
+       std::string video_state_identifier () const;
+       void read_metadata ();
+       void content_changed (boost::weak_ptr<Content>, int);
+       boost::shared_ptr<FFmpegContent> ffmpeg () const;
+       void setup_default_audio_mapping ();
+
        /** Log to write to */
        boost::shared_ptr<Log> _log;
-
-       /** Any running ExamineContentJob, or 0 */
-       boost::shared_ptr<ExamineContentJob> _examine_content_job;
        /** Any running AnalyseAudioJob, or 0 */
        boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
-
-       void signal_changed (Property);
-       void examine_content_finished ();
-       void analyse_audio_finished ();
-       std::string video_state_identifier () const;
+       boost::shared_ptr<Playlist> _playlist;
 
        /** Complete path to directory containing the film metadata;
         *  must not be relative.
@@ -399,15 +341,8 @@ private:
        std::string _name;
        /** True if a auto-generated DCI-compliant name should be used for our DCP */
        bool _use_dci_name;
-       /** File or directory containing content; may be relative to our directory
-        *  or an absolute path.
-        */
-       std::string _content;
-       /** If this is true, we will believe the length specified by the content
-        *  file's header; if false, we will run through the whole content file
-        *  the first time we see it in order to obtain the length.
-        */
-       bool _trust_content_header;
+       bool _trust_content_headers;
+       ContentList _content;
        /** The type of content that this Film represents (feature, trailer etc.) */
        DCPContentType const * _dcp_content_type;
        /** The format to present this Film in (flat, scope, etc.) */
@@ -426,22 +361,11 @@ private:
            is the video without any filters or post-processing, and the right half
            has the specified filters and post-processing.
        */
-       bool _dcp_ab;
-       /** The audio stream to use from our content */
-       boost::shared_ptr<AudioStream> _content_audio_stream;
-       /** List of filenames of external audio files, in channel order
-           (L, R, C, Lfe, Ls, Rs)
-       */
-       std::vector<std::string> _external_audio;
-       /** true to use audio from our content file; false to use external audio */
-       bool _use_content_audio;
+       bool _ab;
        /** Gain to apply to audio in dB */
        float _audio_gain;
        /** Delay to apply to audio (positive moves audio later) in milliseconds */
        int _audio_delay;
-       /** Duration to make still-sourced films (in seconds) */
-       int _still_duration;
-       boost::shared_ptr<SubtitleStream> _subtitle_stream;
        /** True if subtitles should be shown for this film */
        bool _with_subtitles;
        /** y offset for placing subtitles, in source pixels; +ve is further down
@@ -457,30 +381,13 @@ private:
        int _colour_lut;
        /** bandwidth for J2K files in bits per second */
        int _j2k_bandwidth;
-
        /** DCI naming stuff */
        DCIMetadata _dci_metadata;
-       /** The date that we should use in a DCI name */
-       boost::gregorian::date _dci_date;
        /** Frames per second to run our DCP at */
        int _dcp_frame_rate;
-
-       /* Data which are cached to speed things up */
-
-       /** Size, in pixels, of the source (ignoring cropping) */
-       libdcp::Size _size;
-       /** The length of the source, in video frames (as far as we know) */
-       boost::optional<SourceFrame> _length;
-       /** MD5 digest of our content file */
-       std::string _content_digest;
-       /** The audio streams in our content */
-       std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams;
-       /** A stream to represent possible external audio (will always exist) */
-       boost::shared_ptr<AudioStream> _sndfile_stream;
-       /** the subtitle streams that we can use */
-       std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
-       /** Frames per second of the source */
-       float _source_frame_rate;
+       /** The date that we should use in a DCI name */
+       boost::gregorian::date _dci_date;
+       AudioMapping _audio_mapping;
 
        /** true if our state has changed since we last saved it */
        mutable bool _dirty;
index 045cbaa6a0ba504c6f4acda30adbaba711884e8e..a52c030fe549d475be012dbbf7788b2764f5d25d 100644 (file)
@@ -57,7 +57,7 @@ using libdcp::Size;
  *  @param s Size of the images to process.
  *  @param p Pixel format of the images to process.
  */
-FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
+FilterGraph::FilterGraph (shared_ptr<const Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p)
        : _buffer_src_context (0)
        , _buffer_sink_context (0)
        , _size (s)
index 7e4e8422b07d36236b2185c588f2a23311eab18e..db86a677d7292ca935915d38f5e7eb0c05f48efb 100644 (file)
@@ -37,7 +37,7 @@ class FFmpegDecoder;
 class FilterGraph
 {
 public:
-       FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
+       FilterGraph (boost::shared_ptr<const Film>, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p);
 
        bool can_process (libdcp::Size s, AVPixelFormat p) const;
        std::list<boost::shared_ptr<Image> > process (AVFrame const * frame);
index faadcd79779edf22b266093c0ce3a4d5015d2f7f..cce8762bdadd51766ef0cf01cd237c30f8892861 100644 (file)
@@ -29,6 +29,7 @@
 #include <iostream>
 #include "format.h"
 #include "film.h"
+#include "playlist.h"
 
 #include "i18n.h"
 
@@ -199,7 +200,7 @@ FixedFormat::FixedFormat (int r, libdcp::Size dcp, string id, string n, string d
 int
 Format::dcp_padding (shared_ptr<const Film> f) const
 {
-       int p = rint ((_dcp_size.width - (_dcp_size.height * ratio_as_float(f))) / 2.0);
+       int p = rint ((_dcp_size.width - (_dcp_size.height * ratio(f))) / 2.0);
 
        /* This comes out -ve for Scope; bodge it */
        if (p < 0) {
@@ -230,7 +231,7 @@ VariableFormat::ratio_as_integer (shared_ptr<const Film> f) const
 float
 VariableFormat::ratio_as_float (shared_ptr<const Film> f) const
 {
-       libdcp::Size const c = f->cropped_size (f->size ());
+       libdcp::Size const c = f->cropped_size (f->video_size ());
        return float (c.width) / c.height;
 }
 
index 783ff25ce455b31dd207fa4f37026d8fee132a1d..7958ca5341604e199a181b2295e0708b3614d542 100644 (file)
@@ -49,7 +49,7 @@ public:
        /** @return the ratio of the container (including any padding) as a floating point number */
        float container_ratio_as_float () const;
 
-       int dcp_padding (boost::shared_ptr<const Film> f) const;
+       int dcp_padding (boost::shared_ptr<const Film>) const;
 
        /** @return size in pixels of the images that we should
         *  put in a DCP for this ratio.  This size will not correspond
diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc
new file mode 100644 (file)
index 0000000..24f6d33
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+    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 <libcxml/cxml.h>
+#include "imagemagick_content.h"
+#include "imagemagick_decoder.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+ImageMagickContent::ImageMagickContent (boost::filesystem::path f)
+       : Content (f)
+       , VideoContent (f)
+{
+
+}
+
+ImageMagickContent::ImageMagickContent (shared_ptr<const cxml::Node> node)
+       : Content (node)
+       , VideoContent (node)
+{
+       
+}
+
+string
+ImageMagickContent::summary () const
+{
+       return String::compose (_("Image: %1"), file().filename().string());
+}
+
+bool
+ImageMagickContent::valid_file (boost::filesystem::path f)
+{
+       string ext = f.extension().string();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+       return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp");
+}
+
+void
+ImageMagickContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("ImageMagick");
+       Content::as_xml (node);
+       VideoContent::as_xml (node);
+}
+
+void
+ImageMagickContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
+{
+       Content::examine (film, job, quick);
+       shared_ptr<ImageMagickDecoder> decoder (new ImageMagickDecoder (film, shared_from_this()));
+
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               /* Initial length */
+               _video_length = 10 * 24;
+       }
+       
+       take_from_video_decoder (decoder);
+       
+        signal_changed (VideoContentProperty::VIDEO_LENGTH);
+}
+
+shared_ptr<Content>
+ImageMagickContent::clone () const
+{
+       return shared_ptr<Content> (new ImageMagickContent (*this));
+}
+
+void
+ImageMagickContent::set_video_length (ContentVideoFrame len)
+{
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _video_length = len;
+       }
+
+       signal_changed (VideoContentProperty::VIDEO_LENGTH);
+}
diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h
new file mode 100644 (file)
index 0000000..b1e7f94
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+    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/enable_shared_from_this.hpp>
+#include "video_content.h"
+
+namespace cxml {
+       class Node;
+}
+
+class ImageMagickContent : public VideoContent
+{
+public:
+       ImageMagickContent (boost::filesystem::path);
+       ImageMagickContent (boost::shared_ptr<const cxml::Node>);
+
+       boost::shared_ptr<ImageMagickContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<ImageMagickContent> (Content::shared_from_this ());
+       };
+
+       void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+       std::string summary () const;
+       void as_xml (xmlpp::Node *) const;
+       boost::shared_ptr<Content> clone () const;
+
+       void set_video_length (ContentVideoFrame);
+
+       static bool valid_file (boost::filesystem::path);
+};
index 5dc0b7b06d6ff9cfad6116ff12c7f7b6c1a4c7e0..7049b7d6e31945ace55eadd9e16bc4ef6f04a170 100644 (file)
@@ -20,6 +20,7 @@
 #include <iostream>
 #include <boost/filesystem.hpp>
 #include <Magick++.h>
+#include "imagemagick_content.h"
 #include "imagemagick_decoder.h"
 #include "image.h"
 #include "film.h"
@@ -31,57 +32,46 @@ using std::cout;
 using boost::shared_ptr;
 using libdcp::Size;
 
-ImageMagickDecoder::ImageMagickDecoder (
-       boost::shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , VideoDecoder (f, o)
+ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
+       : Decoder (f)
+       , VideoDecoder (f)
+       , _imagemagick_content (c)
+       , _position (0)
 {
-       if (boost::filesystem::is_directory (_film->content_path())) {
-               for (
-                       boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (_film->content_path());
-                       i != boost::filesystem::directory_iterator();
-                       ++i) {
-
-                       if (still_image_file (i->path().string())) {
-                               _files.push_back (i->path().string());
-                       }
-               }
-       } else {
-               _files.push_back (_film->content_path ());
-       }
-
-       _iter = _files.begin ();
+       
 }
 
 libdcp::Size
 ImageMagickDecoder::native_size () const
 {
-       if (_files.empty ()) {
-               throw DecodeError (_("no still image files found"));
-       }
-
-       /* Look at the first file and assume its size holds for all */
        using namespace MagickCore;
-       Magick::Image* image = new Magick::Image (_film->content_path ());
+       Magick::Image* image = new Magick::Image (_imagemagick_content->file().string());
        libdcp::Size const s = libdcp::Size (image->columns(), image->rows());
        delete image;
 
        return s;
 }
 
+int
+ImageMagickDecoder::video_length () const
+{
+       return _imagemagick_content->video_length ();
+}
+
 bool
 ImageMagickDecoder::pass ()
 {
-       if (_iter == _files.end()) {
-               if (video_frame() >= _film->still_duration_in_frames()) {
-                       return true;
-               }
+       if (_position < 0 || _position >= _imagemagick_content->video_length ()) {
+               return true;
+       }
 
-               repeat_last_video ();
+       if (have_last_video ()) {
+               repeat_last_video (double (_position) / 24);
+               _position++;
                return false;
        }
        
-       Magick::Image* magick_image = new Magick::Image (_film->content_path ());
+       Magick::Image* magick_image = new Magick::Image (_imagemagick_content->file().string ());
        
        libdcp::Size size = native_size ();
        shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, size, false));
@@ -102,9 +92,9 @@ ImageMagickDecoder::pass ()
 
        image = image->crop (_film->crop(), true);
        
-       emit_video (image, 0);
+       emit_video (image, double (_position) / 24);
 
-       ++_iter;
+       ++_position;
        return false;
 }
 
@@ -115,38 +105,16 @@ ImageMagickDecoder::pixel_format () const
        return PIX_FMT_RGB24;
 }
 
-bool
-ImageMagickDecoder::seek_to_last ()
-{
-       if (_iter == _files.end()) {
-               _iter = _files.begin();
-       } else {
-               --_iter;
-       }
-
-       return false;
-}
-
 bool
 ImageMagickDecoder::seek (double t)
 {
-       int const f = t * frames_per_second();
-       
-       _iter = _files.begin ();
-       for (int i = 0; i < f; ++i) {
-               if (_iter == _files.end()) {
-                       return true;
-               }
-               ++_iter;
-       }
-       
-       return false;
-}
+       int const f = t * _imagemagick_content->video_frame_rate ();
 
-void
-ImageMagickDecoder::film_changed (Film::Property p)
-{
-       if (p == Film::CROP) {
-               OutputChanged ();
+       if (f >= _imagemagick_content->video_length()) {
+               _position = 0;
+               return true;
        }
+
+       _position = f;
+       return false;
 }
index 2f4e2c9677a64e9a162f49a27645be9561f0840b..52c7bec186495cdbe4026e7bce1d325d3d6a8211 100644 (file)
@@ -23,22 +23,19 @@ namespace Magick {
        class Image;
 }
 
+class ImageMagickContent;
+
 class ImageMagickDecoder : public VideoDecoder
 {
 public:
-       ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions);
+       ImageMagickDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const ImageMagickContent>);
 
-       float frames_per_second () const {
-               /* We don't know */
-               return 0;
+       float video_frame_rate () const {
+               return 24;
        }
 
        libdcp::Size native_size () const;
-
-       SourceFrame length () const {
-               /* We don't know */
-               return 0;
-       }
+       ContentVideoFrame video_length () const;
 
        int audio_channels () const {
                return 0;
@@ -53,10 +50,9 @@ public:
        }
 
        bool seek (double);
-       bool seek_to_last ();
+       bool pass ();
 
 protected:
-       bool pass ();
        PixelFormat pixel_format () const;
 
        int time_base_numerator () const {
@@ -78,8 +74,6 @@ protected:
        }
 
 private:
-       void film_changed (Film::Property);
-       
-       std::list<std::string> _files;
-       std::list<std::string>::iterator _iter;
+       boost::shared_ptr<const ImageMagickContent> _imagemagick_content;
+       ContentVideoFrame _position;
 };
index ace02b8b307904569b8cc9c0d928c5356228fd23..ff0332d6daeee092ca29cc6c720bfb0098b6d30c 100644 (file)
@@ -34,8 +34,6 @@ using std::list;
 using std::stringstream;
 using boost::shared_ptr;
 
-/** @param s Film that we are operating on.
- */
 Job::Job (shared_ptr<Film> f)
        : _film (f)
        , _thread (0)
index fd036bce29c921625bfe71d099214fdd8a508821..f5175c525024ee6b4636e73c35f24e58cbc6a06f 100644 (file)
@@ -38,7 +38,7 @@ class Film;
 class Job : public boost::enable_shared_from_this<Job>
 {
 public:
-       Job (boost::shared_ptr<Film> s);
+       Job (boost::shared_ptr<Film>);
        virtual ~Job() {}
 
        /** @return user-readable name of this job */
@@ -87,7 +87,6 @@ protected:
        void set_state (State);
        void set_error (std::string s, std::string d);
 
-       /** Film for this job */
        boost::shared_ptr<Film> _film;
 
 private:
index 48f6ed9126dd980c13b79b6665a3a82ad203bf9b..77ed9b6515bbb3e40add4a89e3b4d6d6d81a0d52 100644 (file)
@@ -37,7 +37,7 @@ Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
 }
 
 void
-Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s)
+Matcher::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
 {
        Video (i, same, s);
        _video_frames++;
@@ -47,7 +47,7 @@ Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr
 }
 
 void
-Matcher::process_audio (boost::shared_ptr<AudioBuffers> b)
+Matcher::process_audio (shared_ptr<AudioBuffers> b)
 {
        Audio (b);
        _audio_frames += b->frames ();
diff --git a/src/lib/options.h b/src/lib/options.h
deleted file mode 100644 (file)
index 0d2c07f..0000000
+++ /dev/null
@@ -1,43 +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.
-
-*/
-
-#ifndef DVDOMATIC_OPTIONS_H
-#define DVDOMATIC_OPTIONS_H
-
-/** @file src/options.h
- *  @brief Options for a decoding operation.
- */
-
-class DecodeOptions
-{
-public:
-       DecodeOptions ()
-               : decode_video (true)
-               , decode_audio (true)
-               , decode_subtitles (false)
-               , video_sync (true)
-       {}
-
-       bool decode_video;
-       bool decode_audio;
-       bool decode_subtitles;
-       bool video_sync;
-};
-
-#endif
diff --git a/src/lib/player.cc b/src/lib/player.cc
new file mode 100644 (file)
index 0000000..c66d091
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+    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 "player.h"
+#include "film.h"
+#include "ffmpeg_decoder.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_decoder.h"
+#include "imagemagick_content.h"
+#include "sndfile_decoder.h"
+#include "sndfile_content.h"
+#include "playlist.h"
+#include "job.h"
+
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+
+Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
+       : _film (f)
+       , _playlist (p)
+       , _video (true)
+       , _audio (true)
+       , _subtitles (true)
+       , _have_valid_decoders (false)
+       , _video_sync (true)
+{
+       _playlist->Changed.connect (bind (&Player::playlist_changed, this));
+       _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
+}
+
+void
+Player::disable_video ()
+{
+       _video = false;
+}
+
+void
+Player::disable_audio ()
+{
+       _audio = false;
+}
+
+void
+Player::disable_subtitles ()
+{
+       _subtitles = false;
+}
+
+bool
+Player::pass ()
+{
+       if (!_have_valid_decoders) {
+               setup_decoders ();
+               _have_valid_decoders = true;
+       }
+       
+       bool done = true;
+       
+       if (_video_decoder != _video_decoders.end ()) {
+               if ((*_video_decoder)->pass ()) {
+                       _video_decoder++;
+               }
+               
+               if (_video_decoder != _video_decoders.end ()) {
+                       done = false;
+               }
+       }
+
+       if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+               for (list<shared_ptr<SndfileDecoder> >::iterator i = _sndfile_decoders.begin(); i != _sndfile_decoders.end(); ++i) {
+                       if (!(*i)->pass ()) {
+                               done = false;
+                       }
+               }
+
+               Audio (_sndfile_buffers);
+               _sndfile_buffers.reset ();
+       }
+
+       return done;
+}
+
+void
+Player::set_progress (shared_ptr<Job> job)
+{
+       /* Assume progress can be divined from how far through the video we are */
+
+       if (_video_decoder == _video_decoders.end() || !_playlist->video_length()) {
+               return;
+       }
+       
+       ContentVideoFrame p = 0;
+       list<shared_ptr<VideoDecoder> >::iterator i = _video_decoders.begin ();
+       while (i != _video_decoders.end() && i != _video_decoder) {
+               p += (*i)->video_length ();
+       }
+
+       job->set_progress (float ((*_video_decoder)->video_frame ()) / _playlist->video_length ());
+}
+
+void
+Player::process_video (shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
+{
+       Video (i, same, s);
+}
+
+void
+Player::process_audio (weak_ptr<const AudioContent> c, shared_ptr<AudioBuffers> b)
+{
+       if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+               AudioMapping mapping = _film->audio_mapping ();
+               if (!_sndfile_buffers) {
+                       _sndfile_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ()));
+                       _sndfile_buffers->make_silent ();
+               }
+
+               for (int i = 0; i < b->channels(); ++i) {
+                       list<libdcp::Channel> dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i));
+                       for (list<libdcp::Channel>::iterator j = dcp.begin(); j != dcp.end(); ++j) {
+                               _sndfile_buffers->accumulate (b, i, static_cast<int> (*j));
+                       }
+               }
+
+       } else {
+               Audio (b);
+       }
+}
+
+/** @return true on error */
+bool
+Player::seek (double t)
+{
+       if (!_have_valid_decoders) {
+               setup_decoders ();
+               _have_valid_decoders = true;
+       }
+
+       /* Find the decoder that contains this position */
+       _video_decoder = _video_decoders.begin ();
+       while (_video_decoder != _video_decoders.end ()) {
+               double const this_length = double ((*_video_decoder)->video_length()) / _film->video_frame_rate ();
+               if (t < this_length) {
+                       break;
+               }
+               t -= this_length;
+               ++_video_decoder;
+       }
+       
+       if (_video_decoder != _video_decoders.end()) {
+               (*_video_decoder)->seek (t);
+       } else {
+               return true;
+       }
+
+       /* XXX: don't seek audio because we don't need to... */
+
+       return false;
+}
+
+void
+Player::setup_decoders ()
+{
+       _video_decoders.clear ();
+       _video_decoder = _video_decoders.end ();
+       _sndfile_decoders.clear ();
+       
+       if (_video) {
+               list<shared_ptr<const VideoContent> > vc = _playlist->video ();
+               for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) {
+
+                       shared_ptr<VideoDecoder> d;
+                       
+                       /* XXX: into content? */
+                       
+                       shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+                       if (fc) {
+                               shared_ptr<FFmpegDecoder> fd (
+                                       new FFmpegDecoder (
+                                               _film, fc, _video,
+                                               _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG,
+                                               _subtitles && _film->with_subtitles(),
+                                               _video_sync
+                                               )
+                                       );
+
+                               if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) {
+                                       fd->Audio.connect (bind (&Player::process_audio, this, fc, _1));
+                               }
+
+                               d = fd;
+                       }
+
+                       shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
+                       if (ic) {
+                               d.reset (new ImageMagickDecoder (_film, ic));
+                       }
+
+                       d->connect_video (shared_from_this ());
+                       _video_decoders.push_back (d);
+               }
+
+               _video_decoder = _video_decoders.begin ();
+       }
+
+       if (_audio && _playlist->audio_from() == Playlist::AUDIO_SNDFILE) {
+               list<shared_ptr<const SndfileContent> > sc = _playlist->sndfile ();
+               for (list<shared_ptr<const SndfileContent> >::iterator i = sc.begin(); i != sc.end(); ++i) {
+                       shared_ptr<SndfileDecoder> d (new SndfileDecoder (_film, *i));
+                       _sndfile_decoders.push_back (d);
+                       d->Audio.connect (bind (&Player::process_audio, this, *i, _1));
+               }
+       }
+}
+
+void
+Player::disable_video_sync ()
+{
+       _video_sync = false;
+}
+
+double
+Player::last_video_time () const
+{
+       double t = 0;
+       for (list<shared_ptr<VideoDecoder> >::const_iterator i = _video_decoders.begin(); i != _video_decoder; ++i) {
+               t += (*i)->video_length() / (*i)->video_frame_rate ();
+       }
+
+       return t + (*_video_decoder)->last_content_time ();
+}
+
+void
+Player::content_changed (weak_ptr<Content> w, int p)
+{
+       shared_ptr<Content> c = w.lock ();
+       if (!c) {
+               return;
+       }
+
+       if (p == VideoContentProperty::VIDEO_LENGTH) {
+               if (dynamic_pointer_cast<FFmpegContent> (c)) {
+                       /* FFmpeg content length changes are serious; we need new decoders */
+                       _have_valid_decoders = false;
+               }
+       }
+}
+
+void
+Player::playlist_changed ()
+{
+       _have_valid_decoders = false;
+}
diff --git a/src/lib/player.h b/src/lib/player.h
new file mode 100644 (file)
index 0000000..8a82ab2
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_PLAYER_H
+#define DVDOMATIC_PLAYER_H
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "video_source.h"
+#include "audio_source.h"
+#include "video_sink.h"
+#include "audio_sink.h"
+
+class VideoDecoder;
+class SndfileDecoder;
+class Job;
+class Film;
+class Playlist;
+class AudioContent;
+
+class Player : public VideoSource, public AudioSource, public VideoSink, public boost::enable_shared_from_this<Player>
+{
+public:
+       Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
+
+       void disable_video ();
+       void disable_audio ();
+       void disable_subtitles ();
+       void disable_video_sync ();
+
+       bool pass ();
+       void set_progress (boost::shared_ptr<Job>);
+       bool seek (double);
+
+       double last_video_time () const;
+
+private:
+       void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s);
+       void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<AudioBuffers>);
+       void setup_decoders ();
+       void playlist_changed ();
+       void content_changed (boost::weak_ptr<Content>, int);
+
+       boost::shared_ptr<const Film> _film;
+       boost::shared_ptr<const Playlist> _playlist;
+       
+       bool _video;
+       bool _audio;
+       bool _subtitles;
+       
+       bool _have_valid_decoders;
+       std::list<boost::shared_ptr<VideoDecoder> > _video_decoders;
+       std::list<boost::shared_ptr<VideoDecoder> >::iterator _video_decoder;
+       std::list<boost::shared_ptr<SndfileDecoder> > _sndfile_decoders;
+
+       boost::shared_ptr<AudioBuffers> _sndfile_buffers;
+
+       bool _video_sync;
+};
+
+#endif
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
new file mode 100644 (file)
index 0000000..d26dae7
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+    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/shared_ptr.hpp>
+#include "playlist.h"
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "video_content.h"
+#include "ffmpeg_decoder.h"
+#include "ffmpeg_content.h"
+#include "imagemagick_decoder.h"
+#include "job.h"
+
+using std::list;
+using std::cout;
+using std::vector;
+using std::min;
+using std::max;
+using boost::shared_ptr;
+using boost::weak_ptr;
+using boost::dynamic_pointer_cast;
+
+Playlist::Playlist ()
+       : _audio_from (AUDIO_FFMPEG)
+{
+
+}
+
+void
+Playlist::setup (ContentList content)
+{
+       _audio_from = AUDIO_FFMPEG;
+
+       _video.clear ();
+       _sndfile.clear ();
+
+       for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
+               i->disconnect ();
+       }
+       
+       _content_connections.clear ();
+
+       for (ContentList::const_iterator i = content.begin(); i != content.end(); ++i) {
+               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
+               if (vc) {
+                       _video.push_back (vc);
+               }
+               
+               shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (*i);
+               if (sc) {
+                       _sndfile.push_back (sc);
+                       _audio_from = AUDIO_SNDFILE;
+               }
+
+               _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2)));
+       }
+
+       Changed ();
+}
+
+ContentAudioFrame
+Playlist::audio_length () const
+{
+       ContentAudioFrame len = 0;
+       
+       switch (_audio_from) {
+       case AUDIO_FFMPEG:
+               for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
+                       shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+                       if (fc) {
+                               len += fc->audio_length ();
+                       }
+               }
+               break;
+       case AUDIO_SNDFILE:
+               for (list<shared_ptr<const SndfileContent> >::const_iterator i = _sndfile.begin(); i != _sndfile.end(); ++i) {
+                       len += (*i)->audio_length ();
+               }
+               break;
+       }
+
+       return len;
+}
+
+int
+Playlist::audio_channels () const
+{
+       int channels = 0;
+       
+       switch (_audio_from) {
+       case AUDIO_FFMPEG:
+               for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
+                       shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+                       if (fc) {
+                               channels = max (channels, fc->audio_channels ());
+                       }
+               }
+               break;
+       case AUDIO_SNDFILE:
+               for (list<shared_ptr<const SndfileContent> >::const_iterator i = _sndfile.begin(); i != _sndfile.end(); ++i) {
+                       channels += (*i)->audio_channels ();
+               }
+               break;
+       }
+
+       return channels;
+}
+
+int
+Playlist::audio_frame_rate () const
+{
+       /* XXX: assuming that all content has the same rate */
+       
+       switch (_audio_from) {
+       case AUDIO_FFMPEG:
+       {
+               shared_ptr<const FFmpegContent> fc = first_ffmpeg ();
+               if (fc) {
+                       return fc->audio_channels ();
+               }
+               break;
+       }
+       case AUDIO_SNDFILE:
+               return _sndfile.front()->audio_frame_rate ();
+       }
+
+       return 0;
+}
+
+int64_t
+Playlist::audio_channel_layout () const
+{
+       /* XXX: assuming that all content has the same layout */
+
+       switch (_audio_from) {
+       case AUDIO_FFMPEG:
+       {
+               shared_ptr<const FFmpegContent> fc = first_ffmpeg ();
+               if (fc) {
+                       return fc->audio_channel_layout ();
+               }
+               break;
+       }
+       case AUDIO_SNDFILE:
+               /* XXX */
+               return 0;
+       }
+
+       return 0;
+}
+
+float
+Playlist::video_frame_rate () const
+{
+       if (_video.empty ()) {
+               return 0;
+       }
+       
+       /* XXX: assuming all the same */
+       return _video.front()->video_frame_rate ();
+}
+
+libdcp::Size
+Playlist::video_size () const
+{
+       if (_video.empty ()) {
+               return libdcp::Size ();
+       }
+
+       /* XXX: assuming all the same */
+       return _video.front()->video_size ();
+}
+
+ContentVideoFrame
+Playlist::video_length () const
+{
+       ContentVideoFrame len = 0;
+       for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
+               len += (*i)->video_length ();
+       }
+       
+       return len;
+}
+
+bool
+Playlist::has_audio () const
+{
+       /* XXX */
+       return true;
+}
+
+void
+Playlist::content_changed (weak_ptr<Content> c, int p)
+{
+       ContentChanged (c, p);
+}
+
+shared_ptr<const FFmpegContent>
+Playlist::first_ffmpeg () const
+{
+       for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
+               shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
+               if (fc) {
+                       return fc;
+               }
+       }
+
+       return shared_ptr<const FFmpegContent> ();
+}
+       
+
+AudioMapping
+Playlist::default_audio_mapping () const
+{
+       AudioMapping m;
+
+       switch (_audio_from) {
+       case AUDIO_FFMPEG:
+       {
+               shared_ptr<const FFmpegContent> fc = first_ffmpeg ();
+               if (!fc) {
+                       break;
+               }
+               
+               /* XXX: assumes all the same */
+               if (fc->audio_channels() == 1) {
+                       /* Map mono sources to centre */
+                       m.add (AudioMapping::Channel (fc, 0), libdcp::CENTRE);
+               } else {
+                       int const N = min (fc->audio_channels (), MAX_AUDIO_CHANNELS);
+                       /* Otherwise just start with a 1:1 mapping */
+                       for (int i = 0; i < N; ++i) {
+                               m.add (AudioMapping::Channel (fc, i), (libdcp::Channel) i);
+                       }
+               }
+               break;
+       }
+
+       case AUDIO_SNDFILE:
+       {
+               int n = 0;
+               for (list<shared_ptr<const SndfileContent> >::const_iterator i = _sndfile.begin(); i != _sndfile.end(); ++i) {
+                       for (int j = 0; j < (*i)->audio_channels(); ++j) {
+                               m.add (AudioMapping::Channel (*i, j), (libdcp::Channel) n);
+                               ++n;
+                               if (n >= MAX_AUDIO_CHANNELS) {
+                                       break;
+                               }
+                       }
+                       if (n >= MAX_AUDIO_CHANNELS) {
+                               break;
+                       }
+               }
+               break;
+       }
+       }
+
+       return m;
+}
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
new file mode 100644 (file)
index 0000000..4dd27f6
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    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 <list>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "video_source.h"
+#include "audio_source.h"
+#include "video_sink.h"
+#include "audio_sink.h"
+#include "ffmpeg_content.h"
+#include "audio_mapping.h"
+
+class Content;
+class FFmpegContent;
+class FFmpegDecoder;
+class ImageMagickContent;
+class ImageMagickDecoder;
+class SndfileContent;
+class SndfileDecoder;
+class Job;
+class Film;
+
+class Playlist
+{
+public:
+       Playlist ();
+
+       void setup (ContentList);
+
+       ContentAudioFrame audio_length () const;
+       int audio_channels () const;
+       int audio_frame_rate () const;
+       int64_t audio_channel_layout () const;
+       bool has_audio () const;
+       
+       float video_frame_rate () const;
+       libdcp::Size video_size () const;
+       ContentVideoFrame video_length () const;
+
+       AudioMapping default_audio_mapping () const;
+
+       enum AudioFrom {
+               AUDIO_FFMPEG,
+               AUDIO_SNDFILE
+       };
+
+       AudioFrom audio_from () const {
+               return _audio_from;
+       }
+
+       std::list<boost::shared_ptr<const VideoContent> > video () const {
+               return _video;
+       }
+
+       std::list<boost::shared_ptr<const SndfileContent> > sndfile () const {
+               return _sndfile;
+       }
+
+       mutable boost::signals2::signal<void ()> Changed;
+       mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
+       
+private:
+       void content_changed (boost::weak_ptr<Content>, int);
+       boost::shared_ptr<const FFmpegContent> first_ffmpeg () const;
+       
+       AudioFrom _audio_from;
+
+       std::list<boost::shared_ptr<const VideoContent> > _video;
+       std::list<boost::shared_ptr<const SndfileContent> > _sndfile;
+
+       std::list<boost::signals2::connection> _content_connections;
+};
index 08d8e2c787ec9b26abaec751127e875dd5f627e4..8c16d53fbf7c6867cb4ebebed4a3440a528c7172 100644 (file)
@@ -34,7 +34,7 @@ public:
 
 private:
        void set_status (std::string);
-       
+
        mutable boost::mutex _status_mutex;
        std::string _status;
 };
index 9c5a77f681b2903390fec271f5610c62cd2d8b16..ca0bec580b62a4412c77e33677814153c9b8a378 100644 (file)
@@ -29,6 +29,7 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/scoped_array.hpp>
+#include <libcxml/cxml.h>
 #include "server.h"
 #include "util.h"
 #include "scaler.h"
@@ -51,6 +52,19 @@ using boost::bind;
 using boost::scoped_array;
 using libdcp::Size;
 
+ServerDescription::ServerDescription (shared_ptr<const cxml::Node> node)
+{
+       _host_name = node->string_child ("HostName");
+       _threads = node->number_child<int> ("Threads");
+}
+
+void
+ServerDescription::as_xml (xmlpp::Node* root) const
+{
+       root->add_child("HostName")->add_child_text (_host_name);
+       root->add_child("Threads")->add_child_text (boost::lexical_cast<string> (_threads));
+}
+
 /** Create a server description from a string of metadata returned from as_metadata().
  *  @param v Metadata.
  *  @return ServerDescription, or 0.
@@ -68,15 +82,6 @@ ServerDescription::create_from_metadata (string v)
        return new ServerDescription (b[0], atoi (b[1].c_str ()));
 }
 
-/** @return Description of this server as text */
-string
-ServerDescription::as_metadata () const
-{
-       stringstream s;
-       s << _host_name << N_(" ") << _threads;
-       return s.str ();
-}
-
 Server::Server (shared_ptr<Log> log)
        : _log (log)
 {
index 89aeca62632c3aeda94bff27b43e7fe494d6efa6..398401a555dd1eff784ad1d2289677e5258660f2 100644 (file)
 #include <boost/thread.hpp>
 #include <boost/asio.hpp>
 #include <boost/thread/condition.hpp>
+#include <libxml++/libxml++.h>
 #include "log.h"
 
 class Socket;
 
+namespace cxml {
+       class Node;
+}
+
 /** @class ServerDescription
  *  @brief Class to describe a server to which we can send encoding work.
  */
@@ -44,6 +49,8 @@ public:
                , _threads (t)
        {}
 
+       ServerDescription (boost::shared_ptr<const cxml::Node>);
+       
        /** @return server's host name or IP address in string form */
        std::string host_name () const {
                return _host_name;
@@ -62,7 +69,7 @@ public:
                _threads = t;
        }
 
-       std::string as_metadata () const;
+       void as_xml (xmlpp::Node *) const;
        
        static ServerDescription * create_from_metadata (std::string v);
 
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
new file mode 100644 (file)
index 0000000..539b0df
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+    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 <libcxml/cxml.h>
+#include "sndfile_content.h"
+#include "sndfile_decoder.h"
+#include "compose.hpp"
+#include "job.h"
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+SndfileContent::SndfileContent (boost::filesystem::path f)
+       : Content (f)
+       , AudioContent (f)
+       , _audio_channels (0)
+       , _audio_length (0)
+       , _audio_frame_rate (0)
+{
+
+}
+
+SndfileContent::SndfileContent (shared_ptr<const cxml::Node> node)
+       : Content (node)
+       , AudioContent (node)
+{
+       _audio_channels = node->number_child<int> ("AudioChannels");
+       _audio_length = node->number_child<ContentAudioFrame> ("AudioLength");
+       _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
+}
+
+string
+SndfileContent::summary () const
+{
+       return String::compose (_("Sound file: %1"), file().filename().string());
+}
+
+string
+SndfileContent::information () const
+{
+       if (_audio_frame_rate == 0) {
+               return "";
+       }
+       
+       stringstream s;
+
+       s << String::compose (
+               _("%1 channels, %2kHz, %3 samples"),
+               audio_channels(),
+               audio_frame_rate() / 1000.0,
+               audio_length()
+               );
+       
+       return s.str ();
+}
+
+bool
+SndfileContent::valid_file (boost::filesystem::path f)
+{
+       /* XXX: more extensions */
+       string ext = f.extension().string();
+       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
+       return (ext == ".wav" || ext == ".aif" || ext == ".aiff");
+}
+
+shared_ptr<Content>
+SndfileContent::clone () const
+{
+       return shared_ptr<Content> (new SndfileContent (*this));
+}
+
+void
+SndfileContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
+{
+       job->set_progress_unknown ();
+       Content::examine (film, job, quick);
+
+       SndfileDecoder dec (film, shared_from_this());
+
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _audio_channels = dec.audio_channels ();
+               _audio_length = dec.audio_length ();
+               _audio_frame_rate = dec.audio_frame_rate ();
+       }
+
+       signal_changed (AudioContentProperty::AUDIO_CHANNELS);
+       signal_changed (AudioContentProperty::AUDIO_LENGTH);
+       signal_changed (AudioContentProperty::AUDIO_FRAME_RATE);
+}
+
+void
+SndfileContent::as_xml (xmlpp::Node* node) const
+{
+       node->add_child("Type")->add_child_text ("Sndfile");
+       Content::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("AudioFrameRate")->add_child_text (lexical_cast<string> (_audio_frame_rate));
+}
+
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
new file mode 100644 (file)
index 0000000..27c5f36
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+    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.
+
+*/
+
+extern "C" {
+#include <libavutil/audioconvert.h>
+}
+#include "audio_content.h"
+
+namespace cxml {
+       class Node;
+}
+
+class SndfileContent : public AudioContent
+{
+public:
+       SndfileContent (boost::filesystem::path);
+       SndfileContent (boost::shared_ptr<const cxml::Node>);
+
+       boost::shared_ptr<SndfileContent> shared_from_this () {
+               return boost::dynamic_pointer_cast<SndfileContent> (Content::shared_from_this ());
+       }
+       
+       void examine (boost::shared_ptr<Film>, boost::shared_ptr<Job>, bool);
+       std::string summary () const;
+       std::string information () const;
+       void as_xml (xmlpp::Node *) const;
+       boost::shared_ptr<Content> clone () const;
+
+        /* AudioContent */
+        int audio_channels () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_channels;
+       }
+       
+        ContentAudioFrame audio_length () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_length;
+       }
+       
+        int audio_frame_rate () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_frame_rate;
+       }
+       
+        int64_t audio_channel_layout () const {
+               return av_get_default_channel_layout (audio_channels ());
+       }
+
+       static bool valid_file (boost::filesystem::path);
+
+private:
+       int _audio_channels;
+       ContentAudioFrame _audio_length;
+       int _audio_frame_rate;
+};
index 0e3e5e2345a9538b2267493762a88d1485ff15da..c7311112acdd28cf4303acb5fb581912bdaa932e 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <iostream>
 #include <sndfile.h>
+#include "sndfile_content.h"
 #include "sndfile_decoder.h"
 #include "film.h"
 #include "exceptions.h"
 
 using std::vector;
 using std::string;
-using std::stringstream;
 using std::min;
 using std::cout;
 using boost::shared_ptr;
-using boost::optional;
 
-SndfileDecoder::SndfileDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
-       , AudioDecoder (f, o)
+SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
+       : Decoder (f)
+       , AudioDecoder (f)
+       , _sndfile_content (c)
 {
-       sf_count_t frames;
-       vector<SNDFILE*> sf = open_files (frames);
-       close_files (sf);
-}
-
-vector<SNDFILE*>
-SndfileDecoder::open_files (sf_count_t & frames)
-{
-       vector<string> const files = _film->external_audio ();
-
-       int N = 0;
-       for (size_t i = 0; i < files.size(); ++i) {
-               if (!files[i].empty()) {
-                       N = i + 1;
-               }
+       _sndfile = sf_open (_sndfile_content->file().string().c_str(), SFM_READ, &_info);
+       if (!_sndfile) {
+               throw DecodeError (_("could not open audio file for reading"));
        }
 
-       if (N == 0) {
-               return vector<SNDFILE*> ();
-       }
-
-       bool first = true;
-       frames = 0;
-       
-       vector<SNDFILE*> sndfiles;
-       for (size_t i = 0; i < (size_t) N; ++i) {
-               if (files[i].empty ()) {
-                       sndfiles.push_back (0);
-               } else {
-                       SF_INFO info;
-                       SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info);
-                       if (!s) {
-                               throw DecodeError (_("could not open external audio file for reading"));
-                       }
-
-                       if (info.channels != 1) {
-                               throw DecodeError (_("external audio files must be mono"));
-                       }
-                       
-                       sndfiles.push_back (s);
+       _remaining = _info.frames;
+}
 
-                       if (first) {
-                               shared_ptr<SndfileStream> st (
-                                       new SndfileStream (
-                                               info.samplerate, av_get_default_channel_layout (N)
-                                               )
-                                       );
-                               
-                               _audio_streams.push_back (st);
-                               _audio_stream = st;
-                               frames = info.frames;
-                               first = false;
-                       } else {
-                               if (info.frames != frames) {
-                                       throw DecodeError (_("external audio files have differing lengths"));
-                               }
-                       }
-               }
+SndfileDecoder::~SndfileDecoder ()
+{
+       if (_sndfile) {
+               sf_close (_sndfile);
        }
-
-       return sndfiles;
 }
 
 bool
 SndfileDecoder::pass ()
 {
-       sf_count_t frames;
-       vector<SNDFILE*> sndfiles = open_files (frames);
-       if (sndfiles.empty()) {
-               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.
        */
-       sf_count_t const block = _audio_stream->sample_rate() / 2;
-
-       shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block));
-       while (frames > 0) {
-               sf_count_t const this_time = min (block, frames);
-               for (size_t i = 0; i < sndfiles.size(); ++i) {
-                       if (!sndfiles[i]) {
-                               audio->make_silent (i);
-                       } else {
-                               sf_read_float (sndfiles[i], audio->data(i), block);
-                       }
-               }
-
-               audio->set_frames (this_time);
-               Audio (audio);
-               frames -= this_time;
-       }
-
-       close_files (sndfiles);
-
-       return true;
-}
-
-void
-SndfileDecoder::close_files (vector<SNDFILE*> const & sndfiles)
-{
-       for (size_t i = 0; i < sndfiles.size(); ++i) {
-               sf_close (sndfiles[i]);
-       }
-}
-
-shared_ptr<SndfileStream>
-SndfileStream::create ()
-{
-       return shared_ptr<SndfileStream> (new SndfileStream);
-}
-
-shared_ptr<SndfileStream>
-SndfileStream::create (string t, optional<int> v)
-{
-       if (!v) {
-               /* version < 1; no type in the string, and there's only FFmpeg streams anyway */
-               return shared_ptr<SndfileStream> ();
-       }
-
-       stringstream s (t);
-       string type;
-       s >> type;
-       if (type != N_("external")) {
-               return shared_ptr<SndfileStream> ();
-       }
+       sf_count_t const block = _sndfile_content->audio_frame_rate() / 2;
+       sf_count_t const this_time = min (block, _remaining);
+       
+       shared_ptr<AudioBuffers> audio (new AudioBuffers (_sndfile_content->audio_channels(), this_time));
+       sf_read_float (_sndfile, audio->data(0), this_time);
+       audio->set_frames (this_time);
+       Audio (audio);
+       _remaining -= this_time;
 
-       return shared_ptr<SndfileStream> (new SndfileStream (t, v));
+       return (_remaining == 0);
 }
 
-SndfileStream::SndfileStream (string t, optional<int> v)
+int
+SndfileDecoder::audio_channels () const
 {
-       assert (v);
-
-       stringstream s (t);
-       string type;
-       s >> type >> _sample_rate >> _channel_layout;
+       return _info.channels;
 }
 
-SndfileStream::SndfileStream ()
+ContentAudioFrame
+SndfileDecoder::audio_length () const
 {
-
+       return _info.frames;
 }
 
-string
-SndfileStream::to_string () const
+int
+SndfileDecoder::audio_frame_rate () const
 {
-       return String::compose (N_("external %1 %2"), _sample_rate, _channel_layout);
+       return _info.samplerate;
 }
index e16eab6731e2f23d4bc32e9f4e6dfd1a8c2fced7..2900afea0b3f258aaa5c1031328a716b800f9bef 100644 (file)
 #include <sndfile.h>
 #include "decoder.h"
 #include "audio_decoder.h"
-#include "stream.h"
 
-class SndfileStream : public AudioStream
-{
-public:
-       SndfileStream (int sample_rate, int64_t layout)
-               : AudioStream (sample_rate, layout)
-       {}
-                              
-       std::string to_string () const;
-
-       static boost::shared_ptr<SndfileStream> create ();
-       static boost::shared_ptr<SndfileStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       SndfileStream ();
-       SndfileStream (std::string t, boost::optional<int> v);
-};
+class SndfileContent;
 
 class SndfileDecoder : public AudioDecoder
 {
 public:
-       SndfileDecoder (boost::shared_ptr<Film>, DecodeOptions);
+       SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
+       ~SndfileDecoder ();
 
        bool pass ();
 
+       int audio_channels () const;
+       ContentAudioFrame audio_length () const;
+       int audio_frame_rate () const;
+
 private:
-       std::vector<SNDFILE*> open_files (sf_count_t &);
-       void close_files (std::vector<SNDFILE*> const &);
+       SNDFILE* open_file (sf_count_t &);
+       void close_file (SNDFILE*);
+
+       boost::shared_ptr<const SndfileContent> _sndfile_content;
+       SNDFILE* _sndfile;
+       SF_INFO _info;
+       ContentAudioFrame _remaining;
 };
diff --git a/src/lib/stream.cc b/src/lib/stream.cc
deleted file mode 100644 (file)
index bfe7b5e..0000000
+++ /dev/null
@@ -1,92 +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.
-
-*/
-
-#include <sstream>
-#include "compose.hpp"
-#include "stream.h"
-#include "ffmpeg_decoder.h"
-#include "sndfile_decoder.h"
-
-#include "i18n.h"
-
-using std::string;
-using std::stringstream;
-using boost::shared_ptr;
-using boost::optional;
-
-/** Construct a SubtitleStream from a value returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- */
-SubtitleStream::SubtitleStream (string t, boost::optional<int>)
-{
-       stringstream n (t);
-       n >> _id;
-
-       size_t const s = t.find (' ');
-       if (s != string::npos) {
-               _name = t.substr (s + 1);
-       }
-}
-
-/** @return A canonical string representation of this stream */
-string
-SubtitleStream::to_string () const
-{
-       return String::compose (N_("%1 %2"), _id, _name);
-}
-
-/** Create a SubtitleStream from a value returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- */
-shared_ptr<SubtitleStream>
-SubtitleStream::create (string t, optional<int> v)
-{
-       return shared_ptr<SubtitleStream> (new SubtitleStream (t, v));
-}
-
-/** Create an AudioStream from a string returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- *  @return AudioStream, or 0.
- */
-shared_ptr<AudioStream>
-audio_stream_factory (string t, optional<int> v)
-{
-       shared_ptr<AudioStream> s;
-
-       s = FFmpegAudioStream::create (t, v);
-       if (!s) {
-               s = SndfileStream::create (t, v);
-       }
-
-       return s;
-}
-
-/** Create a SubtitleStream from a string returned from to_string().
- *  @param t String returned from to_string().
- *  @param v State file version.
- *  @return SubtitleStream, or 0.
- */
-shared_ptr<SubtitleStream>
-subtitle_stream_factory (string t, optional<int> v)
-{
-       return SubtitleStream::create (t, v);
-}
diff --git a/src/lib/stream.h b/src/lib/stream.h
deleted file mode 100644 (file)
index 16b06e4..0000000
+++ /dev/null
@@ -1,121 +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/lib/stream.h
- *  @brief Representations of audio and subtitle streams.
- *
- *  Some content may have multiple `streams' of audio and/or subtitles; perhaps
- *  for multiple languages, or for stereo / surround mixes.  These classes represent
- *  those streams, and know about their details.
- */
-
-#ifndef DVDOMATIC_STREAM_H
-#define DVDOMATIC_STREAM_H
-
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/optional.hpp>
-extern "C" {
-#include <libavutil/audioconvert.h>
-}
-
-/** @class Stream
- *  @brief Parent class for streams.
- */
-class Stream
-{
-public:
-       virtual ~Stream () {}
-       virtual std::string to_string () const = 0;
-};
-
-/** @class AudioStream
- *  @brief A stream of audio data.
- */
-struct AudioStream : public Stream
-{
-public:
-       AudioStream (int r, int64_t l)
-               : _sample_rate (r)
-               , _channel_layout (l)
-       {}
-
-       /* Only used for backwards compatibility for state file version < 1 */
-       void set_sample_rate (int s) {
-               _sample_rate = s;
-       }
-
-       int channels () const {
-               return av_get_channel_layout_nb_channels (_channel_layout);
-       }
-
-       int sample_rate () const {
-               return _sample_rate;
-       }
-
-       int64_t channel_layout () const {
-               return _channel_layout;
-       }
-
-protected:
-       AudioStream ()
-               : _sample_rate (0)
-               , _channel_layout (0)
-       {}
-
-       int _sample_rate;
-       int64_t _channel_layout;
-};
-
-/** @class SubtitleStream
- *  @brief A stream of subtitle data.
- */
-class SubtitleStream : public Stream
-{
-public:
-       SubtitleStream (std::string n, int i)
-               : _name (n)
-               , _id (i)
-       {}
-
-       std::string to_string () const;
-
-       std::string name () const {
-               return _name;
-       }
-
-       int id () const {
-               return _id;
-       }
-
-       static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v);
-
-private:
-       friend class stream_test;
-       
-       SubtitleStream (std::string t, boost::optional<int> v);
-       
-       std::string _name;
-       int _id;
-};
-
-boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version);
-boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version);
-
-#endif
index 234ebe051f0b44ae678b43cd8e20cd1def6af5dc..0c3b8c37b978b9d5c94c058cfafbde11db552ec0 100644 (file)
@@ -39,11 +39,9 @@ using std::setprecision;
 using boost::shared_ptr;
 
 /** @param s Film to use.
- *  @param o Decode options.
  */
-TranscodeJob::TranscodeJob (shared_ptr<Film> f, DecodeOptions o)
+TranscodeJob::TranscodeJob (shared_ptr<Film> f)
        : Job (f)
-       , _decode_opt (o)
 {
        
 }
@@ -62,9 +60,8 @@ TranscodeJob::run ()
                _film->log()->log (N_("Transcode job starting"));
                _film->log()->log (String::compose (N_("Audio delay is %1ms"), _film->audio_delay()));
 
-               _encoder.reset (new Encoder (_film));
-               Transcoder w (_film, _decode_opt, this, _encoder);
-               w.go ();
+               _transcoder.reset (new Transcoder (_film, shared_from_this ()));
+               _transcoder->go ();
                set_progress (1);
                set_state (FINISHED_OK);
 
@@ -83,11 +80,11 @@ TranscodeJob::run ()
 string
 TranscodeJob::status () const
 {
-       if (!_encoder) {
+       if (!_transcoder) {
                return _("0%");
        }
 
-       float const fps = _encoder->current_frames_per_second ();
+       float const fps = _transcoder->current_encoding_rate ();
        if (fps == 0) {
                return Job::status ();
        }
@@ -106,24 +103,28 @@ TranscodeJob::status () const
 int
 TranscodeJob::remaining_time () const
 {
-       float fps = _encoder->current_frames_per_second ();
+       if (!_transcoder) {
+               return 0;
+       }
+       
+       float fps = _transcoder->current_encoding_rate ();
+
        if (fps == 0) {
                return 0;
        }
 
-       if (!_film->length()) {
+       if (!_film->video_length()) {
                return 0;
        }
 
        /* Compute approximate proposed length here, as it's only here that we need it */
-       int length = _film->length().get();
-       FrameRateConversion const frc (_film->source_frame_rate(), _film->dcp_frame_rate());
+       int length = _film->video_length();
+       FrameRateConversion const frc (_film->video_frame_rate(), _film->dcp_frame_rate());
        if (frc.skip) {
                length /= 2;
        }
        /* If we are repeating it shouldn't affect transcode time, so don't take it into account */
 
-       /* We assume that dcp_length() is valid, if it is set */
-       int const left = length - _encoder->video_frames_out();
+       int const left = length - _transcoder->video_frames_out();
        return left / fps;
 }
index 9b69e4e6563ac39f7260f6821afd2cad881c181b..7880a925ebb56e4921fea0b2745ab1b3f1882788 100644 (file)
@@ -23,9 +23,8 @@
 
 #include <boost/shared_ptr.hpp>
 #include "job.h"
-#include "options.h"
 
-class Encoder;
+class Transcoder;
 
 /** @class TranscodeJob
  *  @brief A job which transcodes from one format to another.
@@ -33,16 +32,14 @@ class Encoder;
 class TranscodeJob : public Job
 {
 public:
-       TranscodeJob (boost::shared_ptr<Film> f, DecodeOptions od);
+       TranscodeJob (boost::shared_ptr<Film> f);
        
        std::string name () const;
        void run ();
        std::string status () const;
 
-protected:
+private:
        int remaining_time () const;
 
-private:
-       DecodeOptions _decode_opt;
-       boost::shared_ptr<Encoder> _encoder;
+       boost::shared_ptr<Transcoder> _transcoder;
 };
index e0f3a03a2319c02de63918a6201647c48fe478cc..6744e9193ce9d415d1ad36eeceedd9f52f25ac83 100644 (file)
 #include <boost/signals2.hpp>
 #include "transcoder.h"
 #include "encoder.h"
-#include "decoder_factory.h"
 #include "film.h"
 #include "matcher.h"
 #include "delay_line.h"
-#include "options.h"
 #include "gain.h"
 #include "video_decoder.h"
 #include "audio_decoder.h"
+#include "player.h"
 
 using std::string;
 using boost::shared_ptr;
@@ -43,72 +42,45 @@ using boost::dynamic_pointer_cast;
 
 /** Construct a transcoder using a Decoder that we create and a supplied Encoder.
  *  @param f Film that we are transcoding.
- *  @param o Decode options.
  *  @param j Job that we are running under, or 0.
  *  @param e Encoder to use.
  */
-Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr<Encoder> e)
+Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<Job> j)
        : _job (j)
-       , _encoder (e)
-       , _decoders (decoder_factory (f, o))
+       , _player (f->player ())
+       , _encoder (new Encoder (f))
 {
-       assert (_encoder);
-
-       if (f->audio_stream()) {
-               shared_ptr<AudioStream> st = f->audio_stream();
-               _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate()));
-               _delay_line.reset (new DelayLine (f->log(), st->channels(), f->audio_delay() * st->sample_rate() / 1000));
+       if (f->has_audio ()) {
+               _matcher.reset (new Matcher (f->log(), f->audio_frame_rate(), f->video_frame_rate()));
+               _delay_line.reset (new DelayLine (f->log(), f->audio_channels(), f->audio_delay() * f->audio_frame_rate() / 1000));
                _gain.reset (new Gain (f->log(), f->audio_gain()));
        }
 
-       /* Set up the decoder to use the film's set streams */
-       _decoders.video->set_subtitle_stream (f->subtitle_stream ());
-       if (_decoders.audio) {
-               _decoders.audio->set_audio_stream (f->audio_stream ());
-       }
-
        if (_matcher) {
-               _decoders.video->connect_video (_matcher);
+               _player->connect_video (_matcher);
                _matcher->connect_video (_encoder);
        } else {
-               _decoders.video->connect_video (_encoder);
+               _player->connect_video (_encoder);
        }
        
-       if (_matcher && _delay_line && _decoders.audio) {
-               _decoders.audio->connect_audio (_delay_line);
+       if (_matcher && _delay_line && f->has_audio ()) {
+               _player->connect_audio (_delay_line);
                _delay_line->connect_audio (_matcher);
                _matcher->connect_audio (_gain);
                _gain->connect_audio (_encoder);
        }
 }
 
-/** Run the decoder, passing its output to the encoder, until the decoder
- *  has no more data to present.
- */
 void
 Transcoder::go ()
 {
        _encoder->process_begin ();
        try {
-               bool done[2] = { false, false };
-               
                while (1) {
-                       if (!done[0]) {
-                               done[0] = _decoders.video->pass ();
-                               if (_job) {
-                                       _decoders.video->set_progress (_job);
-                               }
-                       }
-
-                       if (!done[1] && _decoders.audio && dynamic_pointer_cast<Decoder> (_decoders.audio) != dynamic_pointer_cast<Decoder> (_decoders.video)) {
-                               done[1] = _decoders.audio->pass ();
-                       } else {
-                               done[1] = true;
-                       }
-
-                       if (done[0] && done[1]) {
+                       if (_player->pass ()) {
                                break;
                        }
+                       _player->set_progress (_job);
                }
                
        } catch (...) {
@@ -127,3 +99,15 @@ Transcoder::go ()
        }
        _encoder->process_end ();
 }
+
+float
+Transcoder::current_encoding_rate () const
+{
+       return _encoder->current_encoding_rate ();
+}
+
+int
+Transcoder::video_frames_out () const
+{
+       return _encoder->video_frames_out ();
+}
index b0c263d07823f6450a6daa725a63bb6852df5976..ecc8ebf629a2b245a4c9b0ae22862aebed3fe96b 100644 (file)
 */
 
 /** @file  src/transcoder.h
- *  @brief A class which takes a Film and some Options, then uses those to transcode the film.
  *
  *  A decoder is selected according to the content type, and the encoder can be specified
  *  as a parameter to the constructor.
  */
 
-#include "decoder_factory.h"
-
 class Film;
 class Job;
 class Encoder;
 class Matcher;
 class VideoFilter;
 class Gain;
-class VideoDecoder;
-class AudioDecoder;
 class DelayLine;
+class Player;
 
 /** @class Transcoder
- *  @brief A class which takes a Film and some Options, then uses those to transcode the film.
  *
  *  A decoder is selected according to the content type, and the encoder can be specified
  *  as a parameter to the constructor.
@@ -47,24 +42,19 @@ class Transcoder
 public:
        Transcoder (
                boost::shared_ptr<Film> f,
-               DecodeOptions o,
-               Job* j,
-               boost::shared_ptr<Encoder> e
+               boost::shared_ptr<Job> j
                );
 
        void go ();
 
-       boost::shared_ptr<VideoDecoder> video_decoder () const {
-               return _decoders.video;
-       }
+       float current_encoding_rate () const;
+       int video_frames_out () const;
 
 protected:
        /** A Job that is running this Transcoder, or 0 */
-       Job* _job;
-       /** The encoder that we will use */
+       boost::shared_ptr<Job> _job;
+       boost::shared_ptr<Player> _player;
        boost::shared_ptr<Encoder> _encoder;
-       /** The decoders that we will use */
-       Decoders _decoders;
        boost::shared_ptr<Matcher> _matcher;
        boost::shared_ptr<DelayLine> _delay_line;
        boost::shared_ptr<Gain> _gain;
diff --git a/src/lib/types.cc b/src/lib/types.cc
new file mode 100644 (file)
index 0000000..1e0f483
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    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 "types.h"
+
+bool operator== (Crop const & a, Crop const & b)
+{
+       return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
+}
+
+bool operator!= (Crop const & a, Crop const & b)
+{
+       return !(a == b);
+}
+
diff --git a/src/lib/types.h b/src/lib/types.h
new file mode 100644 (file)
index 0000000..f821a74
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_TYPES_H
+#define DVDOMATIC_TYPES_H
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <libdcp/util.h>
+
+class Content;
+
+typedef std::vector<boost::shared_ptr<Content> > ContentList;
+typedef int64_t ContentAudioFrame;
+typedef int ContentVideoFrame;
+
+/** @struct Crop
+ *  @brief A description of the crop of an image or video.
+ */
+struct Crop
+{
+       Crop () : left (0), right (0), top (0), bottom (0) {}
+
+       /** Number of pixels to remove from the left-hand side */
+       int left;
+       /** Number of pixels to remove from the right-hand side */
+       int right;
+       /** Number of pixels to remove from the top */
+       int top;
+       /** Number of pixels to remove from the bottom */
+       int bottom;
+};
+
+extern bool operator== (Crop const & a, Crop const & b);
+extern bool operator!= (Crop const & a, Crop const & b);
+
+/** @struct Position
+ *  @brief A position.
+ */
+struct Position
+{
+       Position ()
+               : x (0)
+               , y (0)
+       {}
+
+       Position (int x_, int y_)
+               : x (x_)
+               , y (y_)
+       {}
+
+       /** x coordinate */
+       int x;
+       /** y coordinate */
+       int y;
+};
+
+/** @struct Rect
+ *  @brief A rectangle.
+ */
+struct Rect
+{
+       Rect ()
+               : x (0)
+               , y (0)
+               , width (0)
+               , height (0)
+       {}
+
+       Rect (int x_, int y_, int w_, int h_)
+               : x (x_)
+               , y (y_)
+               , width (w_)
+               , height (h_)
+       {}
+
+       int x;
+       int y;
+       int width;
+       int height;
+
+       Position position () const {
+               return Position (x, y);
+       }
+
+       libdcp::Size size () const {
+               return libdcp::Size (width, height);
+       }
+
+       Rect intersection (Rect const & other) const;
+};
+
+#endif
index 557e9a34bd26563406292c7dce8cbfde55794426..06da94294e4b8fe877d155c9c9158dedd73c7a29 100644 (file)
@@ -63,8 +63,26 @@ extern "C" {
 
 #include "i18n.h"
 
-using namespace std;
-using namespace boost;
+using std::string;
+using std::stringstream;
+using std::setfill;
+using std::ostream;
+using std::endl;
+using std::vector;
+using std::hex;
+using std::setw;
+using std::ifstream;
+using std::ios;
+using std::min;
+using std::max;
+using std::list;
+using std::multimap;
+using std::istream;
+using std::numeric_limits;
+using std::pair;
+using boost::shared_ptr;
+using boost::thread;
+using boost::lexical_cast;
 using libdcp::Size;
 
 thread::id ui_thread;
@@ -243,7 +261,7 @@ dvdomatic_setup ()
        Filter::setup_filters ();
        SoundProcessor::setup_sound_processors ();
 
-       ui_thread = this_thread::get_id ();
+       ui_thread = boost::this_thread::get_id ();
 }
 
 #ifdef DVDOMATIC_WINDOWS
@@ -348,11 +366,11 @@ md5_digest (void const * data, int size)
  *  @return MD5 digest of file's contents.
  */
 string
-md5_digest (string file)
+md5_digest (boost::filesystem::path file)
 {
-       ifstream f (file.c_str(), ios::binary);
+       ifstream f (file.string().c_str(), ios::binary);
        if (!f.good ()) {
-               throw OpenFileError (file);
+               throw OpenFileError (file.string());
        }
        
        f.seekg (0, ios::end);
@@ -477,16 +495,6 @@ dcp_audio_sample_rate (int fs)
        return 96000;
 }
 
-bool operator== (Crop const & a, Crop const & b)
-{
-       return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
-}
-
-bool operator!= (Crop const & a, Crop const & b)
-{
-       return !(a == b);
-}
-
 /** @param index Colour LUT index.
  *  @return Human-readable name.
  */
@@ -509,16 +517,16 @@ Socket::Socket (int timeout)
        , _socket (_io_service)
        , _timeout (timeout)
 {
-       _deadline.expires_at (posix_time::pos_infin);
+       _deadline.expires_at (boost::posix_time::pos_infin);
        check ();
 }
 
 void
 Socket::check ()
 {
-       if (_deadline.expires_at() <= asio::deadline_timer::traits_type::now ()) {
+       if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
                _socket.close ();
-               _deadline.expires_at (posix_time::pos_infin);
+               _deadline.expires_at (boost::posix_time::pos_infin);
        }
 
        _deadline.async_wait (boost::bind (&Socket::check, this));
@@ -528,14 +536,14 @@ Socket::check ()
  *  @param endpoint End-point to connect to.
  */
 void
-Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint)
+Socket::connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint)
 {
-       _deadline.expires_from_now (posix_time::seconds (_timeout));
-       system::error_code ec = asio::error::would_block;
-       _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1);
+       _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+       boost::system::error_code ec = boost::asio::error::would_block;
+       _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
        do {
                _io_service.run_one();
-       } while (ec == asio::error::would_block);
+       } while (ec == boost::asio::error::would_block);
 
        if (ec || !_socket.is_open ()) {
                throw NetworkError (_("connect timed out"));
@@ -549,14 +557,14 @@ Socket::connect (asio::ip::basic_resolver_entry<asio::ip::tcp> const & endpoint)
 void
 Socket::write (uint8_t const * data, int size)
 {
-       _deadline.expires_from_now (posix_time::seconds (_timeout));
-       system::error_code ec = asio::error::would_block;
+       _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+       boost::system::error_code ec = boost::asio::error::would_block;
 
-       asio::async_write (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+       boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
        
        do {
                _io_service.run_one ();
-       } while (ec == asio::error::would_block);
+       } while (ec == boost::asio::error::would_block);
 
        if (ec) {
                throw NetworkError (ec.message ());
@@ -577,14 +585,14 @@ Socket::write (uint32_t v)
 void
 Socket::read (uint8_t* data, int size)
 {
-       _deadline.expires_from_now (posix_time::seconds (_timeout));
-       system::error_code ec = asio::error::would_block;
+       _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
+       boost::system::error_code ec = boost::asio::error::would_block;
 
-       asio::async_read (_socket, asio::buffer (data, size), lambda::var(ec) = lambda::_1);
+       boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
 
        do {
                _io_service.run_one ();
-       } while (ec == asio::error::would_block);
+       } while (ec == boost::asio::error::would_block);
        
        if (ec) {
                throw NetworkError (ec.message ());
@@ -861,37 +869,39 @@ AudioBuffers::move (int from, int to, int frames)
        }
 }
 
+/** Add data from from `from', `from_channel' to our channel `to_channel' */
+void
+AudioBuffers::accumulate (shared_ptr<AudioBuffers> from, int from_channel, int to_channel)
+{
+       int const N = frames ();
+       assert (from->frames() == N);
+
+       float* s = from->data (from_channel);
+       float* d = _data[to_channel];
+
+       for (int i = 0; i < N; ++i) {
+               *d++ += *s++;
+       }
+}
+
 /** Trip an assert if the caller is not in the UI thread */
 void
 ensure_ui_thread ()
 {
-       assert (this_thread::get_id() == ui_thread);
+       assert (boost::this_thread::get_id() == ui_thread);
 }
 
-/** @param v Source video frame.
+/** @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 (SourceFrame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second)
 {
        return ((int64_t) v * audio_sample_rate / frames_per_second);
 }
 
-/** @param f Filename.
- *  @return true if this file is a still image, false if it is something else.
- */
-bool
-still_image_file (string f)
-{
-       string ext = boost::filesystem::path(f).extension().string();
-
-       transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
-       
-       return (ext == N_(".tif") || ext == N_(".tiff") || ext == N_(".jpg") || ext == N_(".jpeg") || ext == N_(".png") || ext == N_(".bmp"));
-}
-
 /** @return A pair containing CPU model name and the number of processors */
 pair<string, int>
 cpu_info ()
@@ -938,58 +948,6 @@ audio_channel_name (int c)
        return channels[c];
 }
 
-AudioMapping::AudioMapping (int c)
-       : _source_channels (c)
-{
-
-}
-
-optional<libdcp::Channel>
-AudioMapping::source_to_dcp (int c) const
-{
-       if (c >= _source_channels) {
-               return optional<libdcp::Channel> ();
-       }
-
-       if (_source_channels == 1) {
-               /* mono sources to centre */
-               return libdcp::CENTRE;
-       }
-       
-       return static_cast<libdcp::Channel> (c);
-}
-
-optional<int>
-AudioMapping::dcp_to_source (libdcp::Channel c) const
-{
-       if (_source_channels == 1) {
-               if (c == libdcp::CENTRE) {
-                       return 0;
-               } else {
-                       return optional<int> ();
-               }
-       }
-
-       if (static_cast<int> (c) >= _source_channels) {
-               return optional<int> ();
-       }
-       
-       return static_cast<int> (c);
-}
-
-int
-AudioMapping::dcp_channels () const
-{
-       if (_source_channels == 1) {
-               /* The source is mono, so to put the mono channel into
-                  the centre we need to generate a 5.1 soundtrack.
-               */
-               return 6;
-       }
-
-       return _source_channels;
-}
-
 FrameRateConversion::FrameRateConversion (float source, int dcp)
        : skip (false)
        , repeat (false)
index 3d251cf06ad0d1d9ca36816ea438fdd41d26a680..f4af7c22bc8db2f17d84cf3b1881273bc014262d 100644 (file)
@@ -37,6 +37,7 @@ extern "C" {
 #include <libavfilter/avfilter.h>
 }
 #include "compose.hpp"
+#include "types.h"
 
 #ifdef DVDOMATIC_DEBUG
 #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
@@ -57,7 +58,7 @@ extern double seconds (struct timeval);
 extern void dvdomatic_setup ();
 extern void dvdomatic_setup_i18n (std::string);
 extern std::vector<std::string> split_at_spaces_considering_quotes (std::string);
-extern std::string md5_digest (std::string);
+extern std::string md5_digest (boost::filesystem::path);
 extern std::string md5_digest (void const *, int);
 extern void ensure_ui_thread ();
 extern std::string audio_channel_name (int);
@@ -65,8 +66,6 @@ extern std::string audio_channel_name (int);
 extern boost::filesystem::path mo_path ();
 #endif
 
-typedef int SourceFrame;
-
 struct FrameRateConversion
 {
        FrameRateConversion (float, int);
@@ -104,87 +103,6 @@ struct FrameRateConversion
 
 int best_dcp_frame_rate (float);
 
-enum ContentType {
-       STILL, ///< content is still images
-       VIDEO  ///< content is a video
-};
-
-/** @struct Crop
- *  @brief A description of the crop of an image or video.
- */
-struct Crop
-{
-       Crop () : left (0), right (0), top (0), bottom (0) {}
-
-       /** Number of pixels to remove from the left-hand side */
-       int left;
-       /** Number of pixels to remove from the right-hand side */
-       int right;
-       /** Number of pixels to remove from the top */
-       int top;
-       /** Number of pixels to remove from the bottom */
-       int bottom;
-};
-
-extern bool operator== (Crop const & a, Crop const & b);
-extern bool operator!= (Crop const & a, Crop const & b);
-
-/** @struct Position
- *  @brief A position.
- */
-struct Position
-{
-       Position ()
-               : x (0)
-               , y (0)
-       {}
-
-       Position (int x_, int y_)
-               : x (x_)
-               , y (y_)
-       {}
-
-       /** x coordinate */
-       int x;
-       /** y coordinate */
-       int y;
-};
-
-/** @struct Rect
- *  @brief A rectangle.
- */
-struct Rect
-{
-       Rect ()
-               : x (0)
-               , y (0)
-               , width (0)
-               , height (0)
-       {}
-
-       Rect (int x_, int y_, int w_, int h_)
-               : x (x_)
-               , y (y_)
-               , width (w_)
-               , height (h_)
-       {}
-
-       int x;
-       int y;
-       int width;
-       int height;
-
-       Position position () const {
-               return Position (x, y);
-       }
-
-       libdcp::Size size () const {
-               return libdcp::Size (width, height);
-       }
-
-       Rect intersection (Rect const & other) const;
-};
-
 extern std::string crop_string (Position, libdcp::Size);
 extern int dcp_audio_sample_rate (int);
 extern std::string colour_lut_index_to_name (int index);
@@ -264,6 +182,7 @@ public:
 
        void copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset);
        void move (int from, int to, int frames);
+       void accumulate (boost::shared_ptr<AudioBuffers>, int, int);
 
 private:
        /** Number of channels */
@@ -276,21 +195,7 @@ private:
        float** _data;
 };
 
-class AudioMapping
-{
-public:
-       AudioMapping (int);
-
-       boost::optional<libdcp::Channel> source_to_dcp (int c) const;
-       boost::optional<int> dcp_to_source (libdcp::Channel c) const;
-       int dcp_channels () const;
-
-private:
-       int _source_channels;
-};
-
-extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
-extern bool still_image_file (std::string);
+extern int64_t video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second);
 extern std::pair<std::string, int> cpu_info ();
 
 #endif
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
new file mode 100644 (file)
index 0000000..9fb2b9b
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+    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 <libcxml/cxml.h>
+#include "video_content.h"
+#include "video_decoder.h"
+
+#include "i18n.h"
+
+int const VideoContentProperty::VIDEO_LENGTH = 0;
+int const VideoContentProperty::VIDEO_SIZE = 1;
+int const VideoContentProperty::VIDEO_FRAME_RATE = 2;
+
+using std::string;
+using std::stringstream;
+using std::setprecision;
+using boost::shared_ptr;
+using boost::lexical_cast;
+
+VideoContent::VideoContent (boost::filesystem::path f)
+       : Content (f)
+       , _video_length (0)
+{
+
+}
+
+VideoContent::VideoContent (shared_ptr<const cxml::Node> node)
+       : Content (node)
+{
+       _video_length = node->number_child<ContentVideoFrame> ("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");
+}
+
+VideoContent::VideoContent (VideoContent const & o)
+       : Content (o)
+       , _video_length (o._video_length)
+       , _video_size (o._video_size)
+       , _video_frame_rate (o._video_frame_rate)
+{
+
+}
+
+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("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));
+}
+
+void
+VideoContent::take_from_video_decoder (shared_ptr<VideoDecoder> d)
+{
+       /* These decoder calls could call other content methods which take a lock on the mutex */
+       libdcp::Size const vs = d->native_size ();
+       float const vfr = d->video_frame_rate ();
+       
+        {
+                boost::mutex::scoped_lock lm (_mutex);
+                _video_size = vs;
+               _video_frame_rate = vfr;
+        }
+        
+        signal_changed (VideoContentProperty::VIDEO_SIZE);
+        signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
+}
+
+
+string
+VideoContent::information () const
+{
+       if (video_size().width == 0 || video_size().height == 0) {
+               return "";
+       }
+       
+       stringstream s;
+
+       s << String::compose (
+               _("%1x%2 pixels (%3:1)"),
+               video_size().width,
+               video_size().height,
+               setprecision (3), float (video_size().width) / video_size().height
+               );
+       
+       return s.str ();
+}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
new file mode 100644 (file)
index 0000000..3d2c4ca
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+    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.
+
+*/
+
+#ifndef DVDOMATIC_VIDEO_CONTENT_H
+#define DVDOMATIC_VIDEO_CONTENT_H
+
+#include "content.h"
+#include "util.h"
+
+class VideoDecoder;
+
+class VideoContentProperty
+{
+public:
+       static int const VIDEO_LENGTH;
+       static int const VIDEO_SIZE;
+       static int const VIDEO_FRAME_RATE;
+};
+
+class VideoContent : public virtual Content
+{
+public:
+       VideoContent (boost::filesystem::path);
+       VideoContent (boost::shared_ptr<const cxml::Node>);
+       VideoContent (VideoContent const &);
+
+       void as_xml (xmlpp::Node *) const;
+       virtual std::string information () const;
+
+       ContentVideoFrame video_length () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_length;
+       }
+
+       libdcp::Size video_size () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_size;
+       }
+       
+       float video_frame_rate () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_frame_rate;
+       }
+
+protected:
+       void take_from_video_decoder (boost::shared_ptr<VideoDecoder>);
+
+       ContentVideoFrame _video_length;
+
+private:
+       libdcp::Size _video_size;
+       float _video_frame_rate;
+};
+
+#endif
index 891720f6b85ecb04a4185e92ac49821fd228bf12..99d711693e44fa942b268375f7f2f6bfda955c3e 100644 (file)
@@ -22,7 +22,6 @@
 #include "film.h"
 #include "image.h"
 #include "log.h"
-#include "options.h"
 #include "job.h"
 
 #include "i18n.h"
 using boost::shared_ptr;
 using boost::optional;
 
-VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o)
-       : Decoder (f, o)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
+       : Decoder (f)
        , _video_frame (0)
-       , _last_source_time (0)
+       , _last_content_time (0)
 {
 
 }
@@ -51,8 +50,13 @@ VideoDecoder::emit_video (shared_ptr<Image> image, double t)
                sub = _timed_subtitle->subtitle ();
        }
 
-       signal_video (image, false, sub);
-       _last_source_time = t;
+       signal_video (image, false, sub, t);
+}
+
+bool
+VideoDecoder::have_last_video () const
+{
+       return _last_image;
 }
 
 /** Called by subclasses to repeat the last video frame that we
@@ -60,14 +64,14 @@ VideoDecoder::emit_video (shared_ptr<Image> image, double t)
  *  we will generate a black frame.
  */
 void
-VideoDecoder::repeat_last_video ()
+VideoDecoder::repeat_last_video (double t)
 {
        if (!_last_image) {
                _last_image.reset (new SimpleImage (pixel_format(), native_size(), true));
                _last_image->make_black ();
        }
 
-       signal_video (_last_image, true, _last_subtitle);
+       signal_video (_last_image, true, _last_subtitle, t);
 }
 
 /** Emit our signal to say that some video data is ready.
@@ -76,7 +80,7 @@ VideoDecoder::repeat_last_video ()
  *  @param sub Subtitle for this frame, or 0.
  */
 void
-VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub)
+VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub, double t)
 {
        TIMING (N_("Decoder emits %1"), _video_frame);
        Video (image, same, sub);
@@ -84,6 +88,7 @@ VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subti
 
        _last_image = image;
        _last_subtitle = sub;
+       _last_content_time = t;
 }
 
 /** Set up the current subtitle.  This will be put onto frames that
@@ -102,21 +107,12 @@ VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s)
        }
 }
 
-/** Set which stream of subtitles we should use from our source.
- *  @param s Stream to use.
- */
-void
-VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s)
-{
-       _subtitle_stream = s;
-}
-
 void
 VideoDecoder::set_progress (Job* j) const
 {
        assert (j);
-       
-       if (_film->length()) {
-               j->set_progress (float (_video_frame) / _film->length().get());
+
+       if (_film->video_length()) {
+               j->set_progress (float (_video_frame) / _film->video_length());
        }
 }
index 283ab5d884c0f3d4d379e15033dd549032ec29b2..05cf99a961e3ebbcdda8b97833f0ae5ef5ff48f4 100644 (file)
 #define DVDOMATIC_VIDEO_DECODER_H
 
 #include "video_source.h"
-#include "stream.h"
 #include "decoder.h"
 
+class VideoContent;
+
 class VideoDecoder : public VideoSource, public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<Film>, DecodeOptions);
+       VideoDecoder (boost::shared_ptr<const Film>);
 
-       /** @return video frames per second, or 0 if unknown */
-       virtual float frames_per_second () const = 0;
+       /** @return video frame rate second, or 0 if unknown */
+       virtual float video_frame_rate () const = 0;
        /** @return native size in pixels */
        virtual libdcp::Size native_size () const = 0;
-       /** @return length (in source video frames), according to our content's header */
-       virtual SourceFrame length () const = 0;
+       /** @return length according to our content's header */
+       virtual ContentVideoFrame video_length () const = 0;
 
        virtual int time_base_numerator () const = 0;
        virtual int time_base_denominator () const = 0;
        virtual int sample_aspect_ratio_numerator () const = 0;
        virtual int sample_aspect_ratio_denominator () const = 0;
 
-       virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>);
-
        void set_progress (Job *) const;
        
        int video_frame () const {
                return _video_frame;
        }
 
-       boost::shared_ptr<SubtitleStream> subtitle_stream () const {
-               return _subtitle_stream;
-       }
-
-       std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const {
-               return _subtitle_streams;
-       }
-
-       double last_source_time () const {
-               return _last_source_time;
+       double last_content_time () const {
+               return _last_content_time;
        }
 
 protected:
@@ -67,18 +58,14 @@ protected:
 
        void emit_video (boost::shared_ptr<Image>, double);
        void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
-       void repeat_last_video ();
-
-       /** Subtitle stream to use when decoding */
-       boost::shared_ptr<SubtitleStream> _subtitle_stream;
-       /** Subtitle streams that this decoder's content has */
-       std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams;
+       bool have_last_video () const;
+       void repeat_last_video (double);
 
 private:
-       void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>);
+       void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double);
 
        int _video_frame;
-       double _last_source_time;
+       double _last_content_time;
        
        boost::shared_ptr<TimedSubtitle> _timed_subtitle;
 
index 56742e2b4729a988680ee50bf5536e8682fbba16..1c4d6466cbd5c159bbcdd81de23745821b700080 100644 (file)
 #include "video_sink.h"
 
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::bind;
 
+static void
+process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<Image> i, bool same, shared_ptr<Subtitle> s)
+{
+       shared_ptr<VideoSink> p = sink.lock ();
+       if (p) {
+               p->process_video (i, same, s);
+       }
+}
+
 void
 VideoSource::connect_video (shared_ptr<VideoSink> s)
 {
-       Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3));
+       /* If we bind, say, a Playlist (as the VideoSink) to a Decoder (which is owned
+          by the Playlist) we create a cycle.  Use a weak_ptr to break it.
+       */
+       Video.connect (bind (process_video_proxy, boost::weak_ptr<VideoSink> (s), _1, _2, _3));
 }
index 893629160eb4bc61a7327302e07923671bfd0b3e..e60e7dfd09dce7fc16d31a126ddca4a83698a37e 100644 (file)
@@ -32,7 +32,7 @@ class VideoSink;
 class Subtitle;
 class Image;
 
-/** @class VideoSink
+/** @class VideoSource
  *  @param A class that emits video data.
  */
 class VideoSource
index 2d7ee9ba317f32d1246142064b09890bb25cadb5..7258826ba1774f5a9fd3e32656bb32a2a761edd3 100644 (file)
 #include <libdcp/sound_asset.h>
 #include <libdcp/picture_frame.h>
 #include <libdcp/reel.h>
+#include <libdcp/dcp.h>
 #include "writer.h"
 #include "compose.hpp"
 #include "film.h"
 #include "format.h"
 #include "log.h"
 #include "dcp_video_frame.h"
+#include "dcp_content_type.h"
+#include "player.h"
+#include "audio_mapping.h"
 
 #include "i18n.h"
 
@@ -74,16 +78,14 @@ Writer::Writer (shared_ptr<Film> f)
 
        _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
 
-       AudioMapping m (_film->audio_channels ());
-       
-       if (m.dcp_channels() > 0) {
+       if (_film->audio_channels() > 0) {
                _sound_asset.reset (
                        new libdcp::SoundAsset (
                                _film->dir (_film->dcp_name()),
                                N_("audio.mxf"),
                                _film->dcp_frame_rate (),
-                               m.dcp_channels (),
-                               dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
+                               _film->audio_mapping().dcp_channels (),
+                               dcp_audio_sample_rate (_film->audio_frame_rate())
                                )
                        );
 
index 8e9d3470665281269b3ad4c5ebdcdb2c323f9eb8..8f0e851e39d6a6387f2cad7bc6945bd47f5bdf27 100644 (file)
@@ -6,16 +6,18 @@ sources = """
          ab_transcoder.cc
           analyse_audio_job.cc
           audio_analysis.cc
+          audio_content.cc
           audio_decoder.cc
+          audio_mapping.cc
           audio_source.cc
           config.cc
           combiner.cc
+          content.cc
           cross.cc
           dci_metadata.cc
           dcp_content_type.cc
           dcp_video_frame.cc
           decoder.cc
-          decoder_factory.cc
           delay_line.cc
           dolby_cp750.cc
           encoder.cc
@@ -23,30 +25,36 @@ sources = """
           exceptions.cc
           filter_graph.cc
           ffmpeg_compatibility.cc
+          ffmpeg_content.cc
           ffmpeg_decoder.cc
           film.cc
           filter.cc
           format.cc
           gain.cc
           image.cc
+          imagemagick_content.cc
           imagemagick_decoder.cc
           job.cc
           job_manager.cc
           log.cc
           lut.cc
           matcher.cc
+          player.cc
+          playlist.cc
           scp_dcp_job.cc
           scaler.cc
           server.cc
+          sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
-          stream.cc
           subtitle.cc
           timer.cc
           transcode_job.cc
           transcoder.cc
+          types.cc
           ui_signaller.cc
           util.cc
+          video_content.cc
           video_decoder.cc
           video_source.cc
           writer.cc
@@ -63,7 +71,7 @@ def build(bld):
     obj.uselib = """
                  AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE 
                  BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 
-                 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP GLIB LZMA
+                 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA
                  """
     if bld.env.TARGET_WINDOWS:
         obj.uselib += ' WINSOCK2'
index 212d4848edd4f6b293f1f448deed86a5d594ee99..3fc19a91cfc0447170e8177a6f5fb47cdbf72074 100644 (file)
@@ -59,6 +59,7 @@ static FilmViewer* film_viewer = 0;
 static shared_ptr<Film> film;
 static std::string log_level;
 static std::string film_to_load;
+static std::string film_to_create;
 static wxMenu* jobs_menu = 0;
 static wxLocale* locale = 0;
 
@@ -236,9 +237,6 @@ public:
 
                set_menu_sensitivity ();
 
-               /* XXX: calling these here is a bit of a hack */
-               film_editor->setup_visibility ();
-               
                film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
                if (film) {
                        file_changed (film->directory ());
@@ -442,13 +440,15 @@ private:
 #if wxMINOR_VERSION == 9
 static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
-        { wxCMD_LINE_PARAM, 0, 0, "film to load", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+        { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 };
 #else
 static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_OPTION, wxT("l"), wxT("log"), wxT("set log level (silent, verbose or timing)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
-        { wxCMD_LINE_PARAM, 0, 0, wxT("film to load"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_SWITCH, wxT("n"), wxT("new"), wxT("create new film"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+        { wxCMD_LINE_PARAM, 0, 0, wxT("film to load or create"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
        { wxCMD_LINE_NONE, wxT(""), wxT(""), wxT(""), wxCmdLineParamType (0), 0 }
 };
 #endif
@@ -528,6 +528,12 @@ class App : public wxApp
                        }
                }
 
+               if (!film_to_create.empty ()) {
+                       film.reset (new Film (film_to_create, false));
+                       film->log()->set_level (log_level);
+                       film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
+               }
+
                Frame* f = new Frame (_("DVD-o-matic"));
                SetTopWindow (f);
                f->Maximize ();
@@ -548,11 +554,15 @@ class App : public wxApp
        bool OnCmdLineParsed (wxCmdLineParser& parser)
        {
                if (parser.GetParamCount() > 0) {
-                       film_to_load = wx_to_std (parser.GetParam(0));
+                       if (parser.FoundSwitch (wxT ("new"))) {
+                               film_to_create = wx_to_std (parser.GetParam (0));
+                       } else {
+                               film_to_load = wx_to_std (parser.GetParam(0));
+                       }
                }
 
                wxString log;
-               if (parser.Found(wxT("log"), &log)) {
+               if (parser.Found (wxT ("log"), &log)) {
                        log_level = wx_to_std (log);
                }
 
index 0c639077162250cb31b11fa58e9a84f7b5f347d8..85134b3c543ba8238baa49371038eadb2ff59ed3 100644 (file)
@@ -146,12 +146,12 @@ main (int argc, char* argv[])
        film->log()->set_level ((Log::Level) log_level);
 
        cout << "\nMaking ";
-       if (film->dcp_ab()) {
+       if (film->ab()) {
                cout << "A/B ";
        }
        cout << "DCP for " << film->name() << "\n";
        cout << "Test mode: " << (test_mode ? "yes" : "no") << "\n";
-       cout << "Content: " << film->content() << "\n";
+//     cout << "Content: " << film->content() << "\n";
        pair<string, string> const f = Filter::ffmpeg_strings (film->filters ());
        cout << "Filters: " << f.first << " " << f.second << "\n";
 
index f5756c6939e7770fc12040838fc2c7b7c0030ad3..d3222faa3d64ecfb2fdafa4099792da49e518bf0 100644 (file)
 #include "scaler.h"
 #include "server.h"
 #include "dcp_video_frame.h"
-#include "options.h"
 #include "decoder.h"
 #include "exceptions.h"
 #include "scaler.h"
 #include "log.h"
-#include "decoder_factory.h"
 #include "video_decoder.h"
+#include "player.h"
 
 using std::cout;
 using std::cerr;
@@ -151,17 +150,14 @@ main (int argc, char* argv[])
        server = new ServerDescription (server_host, 1);
        shared_ptr<Film> film (new Film (film_dir, true));
 
-       DecodeOptions opt;
-       opt.decode_audio = false;
-       opt.decode_subtitles = true;
-       opt.video_sync = true;
+       shared_ptr<Player> player = film->player ();
+       player->disable_audio ();
 
-       Decoders decoders = decoder_factory (film, opt);
        try {
-               decoders.video->Video.connect (boost::bind (process_video, _1, _2, _3));
+               player->Video.connect (boost::bind (process_video, _1, _2, _3));
                bool done = false;
                while (!done) {
-                       done = decoders.video->pass ();
+                       done = player->pass ();
                }
        } catch (std::exception& e) {
                cerr << "Error: " << e.what() << "\n";
index 39650d15718de466e8e5847ef93a4e1b766b2c9e..bfd92f0b65e8db97508cb61ee775e75121086662 100644 (file)
 */
 
 #include <boost/filesystem.hpp>
+#include "lib/audio_analysis.h"
+#include "lib/film.h"
 #include "audio_dialog.h"
 #include "audio_plot.h"
-#include "audio_analysis.h"
-#include "film.h"
 #include "wx_util.h"
 
 using boost::shared_ptr;
@@ -84,7 +84,7 @@ AudioDialog::AudioDialog (wxWindow* parent)
 }
 
 void
-AudioDialog::set_film (boost::shared_ptr<Film> f)
+AudioDialog::set_film (shared_ptr<Film> f)
 {
        _film_changed_connection.disconnect ();
        _film_audio_analysis_succeeded_connection.disconnect ();
@@ -92,7 +92,6 @@ AudioDialog::set_film (boost::shared_ptr<Film> f)
        _film = f;
 
        try_to_load_analysis ();
-       setup_channels ();
        _plot->set_gain (_film->audio_gain ());
 
        _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
@@ -101,23 +100,6 @@ AudioDialog::set_film (boost::shared_ptr<Film> f)
        SetTitle (wxString::Format (_("DVD-o-matic audio - %s"), std_to_wx(_film->name()).data()));
 }
 
-void
-AudioDialog::setup_channels ()
-{
-       if (!_film->audio_stream()) {
-               return;
-       }
-
-       AudioMapping m (_film->audio_stream()->channels ());
-       
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               if (m.dcp_to_source(static_cast<libdcp::Channel>(i))) {
-                       _channel_checkbox[i]->Show ();
-               } else {
-                       _channel_checkbox[i]->Hide ();
-               }
-       }
-}      
 
 void
 AudioDialog::try_to_load_analysis ()
@@ -134,12 +116,8 @@ AudioDialog::try_to_load_analysis ()
                
        _plot->set_analysis (a);
 
-       AudioMapping m (_film->audio_stream()->channels ());
-       optional<libdcp::Channel> c = m.source_to_dcp (0);
-       if (c) {
-               _channel_checkbox[c.get()]->SetValue (true);
-               _plot->set_channel_visible (0, true);
-       }
+       _channel_checkbox[0]->SetValue (true);
+       _plot->set_channel_visible (0, true);
 
        for (int i = 0; i < AudioPoint::COUNT; ++i) {
                _type_checkbox[i]->SetValue (true);
@@ -157,11 +135,7 @@ AudioDialog::channel_clicked (wxCommandEvent& ev)
 
        assert (c < MAX_AUDIO_CHANNELS);
 
-       AudioMapping m (_film->audio_stream()->channels ());
-       optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c));
-       if (s) {
-               _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ());
-       }
+       _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
 }
 
 void
@@ -171,11 +145,6 @@ AudioDialog::film_changed (Film::Property p)
        case Film::AUDIO_GAIN:
                _plot->set_gain (_film->audio_gain ());
                break;
-       case Film::CONTENT_AUDIO_STREAM:
-       case Film::EXTERNAL_AUDIO:
-       case Film::USE_CONTENT_AUDIO:
-               setup_channels ();
-               break;
        default:
                break;
        }
index 514faeea0e952ff83c716392d3aacf5a5514d2ee..db1d74f306393ceaf9860919aefe82434a92ea36 100644 (file)
@@ -39,7 +39,6 @@ private:
        void type_clicked (wxCommandEvent &);
        void smoothing_changed (wxScrollEvent &);
        void try_to_load_analysis ();
-       void setup_channels ();
 
        boost::shared_ptr<Film> _film;
        AudioPlot* _plot;
diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc
new file mode 100644 (file)
index 0000000..d62609d
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+    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 <wx/wx.h>
+#include <wx/renderer.h>
+#include <wx/grid.h>
+#include <libdcp/types.h>
+#include "lib/audio_mapping.h"
+#include "audio_mapping_view.h"
+#include "wx_util.h"
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+
+/* This could go away with wxWidgets 2.9, which has an API call
+   to find these values.
+*/
+
+#ifdef __WXMSW__
+#define CHECKBOX_WIDTH 16
+#define CHECKBOX_HEIGHT 16
+#endif
+
+#ifdef __WXGTK__
+#define CHECKBOX_WIDTH 20
+#define CHECKBOX_HEIGHT 20
+#endif
+
+
+class NoSelectionStringRenderer : public wxGridCellStringRenderer
+{
+public:
+       void Draw (wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool)
+       {
+               wxGridCellStringRenderer::Draw (grid, attr, dc, rect, row, col, false);
+       }
+};
+
+class CheckBoxRenderer : public wxGridCellRenderer
+{
+public:
+
+       void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool)
+       {
+               wxRendererNative::Get().DrawCheckBox (
+                       &grid,
+                       dc, rect,
+                       grid.GetCellValue (row, col) == "1" ? static_cast<int>(wxCONTROL_CHECKED) : 0
+                       );
+       }
+
+       wxSize GetBestSize (wxGrid &, wxGridCellAttr &, wxDC &, int, int)
+       {
+               return wxSize (CHECKBOX_WIDTH + 4, CHECKBOX_HEIGHT + 4);
+       }
+       
+       wxGridCellRenderer* Clone () const
+       {
+               return new CheckBoxRenderer;
+       }
+};
+
+
+AudioMappingView::AudioMappingView (wxWindow* parent)
+       : wxPanel (parent, wxID_ANY)
+{
+       _grid = new wxGrid (this, wxID_ANY);
+
+       _grid->CreateGrid (0, 7);
+       _grid->HideRowLabels ();
+       _grid->DisableDragRowSize ();
+       _grid->DisableDragColSize ();
+       _grid->EnableEditing (false);
+       _grid->SetCellHighlightPenWidth (0);
+       _grid->SetDefaultRenderer (new NoSelectionStringRenderer);
+
+       _grid->SetColLabelValue (0, _("Content channel"));
+       _grid->SetColLabelValue (1, _("L"));
+       _grid->SetColLabelValue (2, _("R"));
+       _grid->SetColLabelValue (3, _("C"));
+       _grid->SetColLabelValue (4, _("Lfe"));
+       _grid->SetColLabelValue (5, _("Ls"));
+       _grid->SetColLabelValue (6, _("Rs"));
+
+       _grid->AutoSize ();
+
+       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+       s->Add (_grid, 1, wxEXPAND);
+       SetSizerAndFit (s);
+
+       Connect (wxID_ANY, wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler (AudioMappingView::left_click), 0, this);
+}
+
+void
+AudioMappingView::left_click (wxGridEvent& ev)
+{
+       if (ev.GetCol() == 0) {
+               return;
+       }
+       
+       if (_grid->GetCellValue (ev.GetRow(), ev.GetCol()) == "1") {
+               _grid->SetCellValue (ev.GetRow(), ev.GetCol(), "0");
+       } else {
+               _grid->SetCellValue (ev.GetRow(), ev.GetCol(), "1");
+       }
+}
+
+void
+AudioMappingView::set_mapping (AudioMapping map)
+{
+       if (_grid->GetNumberRows ()) {
+               _grid->DeleteRows (0, _grid->GetNumberRows ());
+       }
+
+       list<AudioMapping::Channel> content_channels = map.content_channels ();
+       _grid->InsertRows (0, content_channels.size ());
+
+       for (size_t r = 0; r < content_channels.size(); ++r) {
+               for (int c = 1; c < 7; ++c) {
+                       _grid->SetCellRenderer (r, c, new CheckBoxRenderer);
+               }
+       }
+       
+       int n = 0;
+       for (list<AudioMapping::Channel>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) {
+               shared_ptr<const AudioContent> ac = i->content.lock ();
+               assert (ac);
+               _grid->SetCellValue (n, 0, wxString::Format ("%s %d", std_to_wx (ac->file().filename().string()), i->index + 1));
+
+               list<libdcp::Channel> const d = map.content_to_dcp (*i);
+               for (list<libdcp::Channel>::const_iterator j = d.begin(); j != d.end(); ++j) {
+                       _grid->SetCellValue (n, static_cast<int> (*j) + 1, "1");
+               }
+               ++n;
+       }
+
+       _grid->AutoSize ();
+}
+
diff --git a/src/wx/audio_mapping_view.h b/src/wx/audio_mapping_view.h
new file mode 100644 (file)
index 0000000..3642941
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+    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/signals2.hpp>
+#include <wx/wx.h>
+#include <wx/grid.h>
+
+class AudioMappingView : public wxPanel
+{
+public:
+       AudioMappingView (wxWindow *);
+
+       void set_mapping (AudioMapping);
+
+private:
+       void left_click (wxGridEvent &);
+
+       wxGrid* _grid;
+};
index cf44eb69fa59c84c93011619c99718b5a0f27b2e..2a6210164e3a23f66076fc7f1fe063d8ffda7c00 100644 (file)
@@ -21,7 +21,6 @@
 #include <boost/bind.hpp>
 #include <wx/graphics.h>
 #include "audio_plot.h"
-#include "lib/decoder_factory.h"
 #include "lib/audio_decoder.h"
 #include "lib/audio_analysis.h"
 #include "wx/wx_util.h"
index 62eecb70c4758b4104f88e54373c9d80b8a62780..c03a13bfe8cd3bb67c119a6ece24c96d1bb8d8ae 100644 (file)
@@ -25,6 +25,7 @@
 #include <iomanip>
 #include <wx/wx.h>
 #include <wx/notebook.h>
+#include <wx/listctrl.h>
 #include <boost/thread.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
@@ -37,6 +38,9 @@
 #include "lib/filter.h"
 #include "lib/config.h"
 #include "lib/ffmpeg_decoder.h"
+#include "lib/imagemagick_content.h"
+#include "lib/sndfile_content.h"
+#include "lib/dcp_content_type.h"
 #include "filter_dialog.h"
 #include "wx_util.h"
 #include "film_editor.h"
@@ -45,6 +49,8 @@
 #include "dci_metadata_dialog.h"
 #include "scaler.h"
 #include "audio_dialog.h"
+#include "imagemagick_content_dialog.h"
+#include "audio_mapping_view.h"
 
 using std::string;
 using std::cout;
@@ -55,22 +61,24 @@ using std::setprecision;
 using std::list;
 using std::vector;
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
 
 /** @param f Film to edit */
 FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
        : wxPanel (parent)
-       , _film (f)
        , _generally_sensitive (true)
        , _audio_dialog (0)
 {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-       SetSizer (s);
        _notebook = new wxNotebook (this, wxID_ANY);
        s->Add (_notebook, 1);
 
        make_film_panel ();
        _notebook->AddPage (_film_panel, _("Film"), true);
+       make_content_panel ();
+       _notebook->AddPage (_content_panel, _("Content"), false);
        make_video_panel ();
        _notebook->AddPage (_video_panel, _("Video"), false);
        make_audio_panel ();
@@ -78,15 +86,16 @@ FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
        make_subtitle_panel ();
        _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
 
-       set_film (_film);
+       set_film (f);
        connect_to_widgets ();
 
        JobManager::instance()->ActiveJobsChanged.connect (
                bind (&FilmEditor::active_jobs_changed, this, _1)
                );
        
-       setup_visibility ();
        setup_formats ();
+
+       SetSizerAndFit (s);
 }
 
 void
@@ -117,14 +126,8 @@ FilmEditor::make_film_panel ()
        grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
        ++r;
 
-       add_label_to_grid_bag_sizer (grid, _film_panel, _("Content"), wxGBPosition (r, 0));
-       _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), _("Select Content File"), wxT("*.*"));
-       grid->Add (_content, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
-       ++r;
-
-       _trust_content_header = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
-       video_control (_trust_content_header);
-       grid->Add (_trust_content_header, wxGBPosition (r, 0), wxGBSpan(1, 2));
+       _trust_content_headers = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
+       grid->Add (_trust_content_headers, wxGBPosition (r, 0), wxGBSpan(1, 2));
        ++r;
 
        add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), wxGBPosition (r, 0));
@@ -132,11 +135,6 @@ FilmEditor::make_film_panel ()
        grid->Add (_dcp_content_type, wxGBPosition (r, 1));
        ++r;
 
-       video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Frame Rate"), wxGBPosition (r, 0)));
-       _source_frame_rate = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       grid->Add (video_control (_source_frame_rate), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       ++r;
-
        {
                add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
@@ -149,54 +147,35 @@ FilmEditor::make_film_panel ()
        ++r;
 
        _frame_rate_description = new wxStaticText (_film_panel, wxID_ANY, wxT ("\n \n "), wxDefaultPosition, wxDefaultSize);
-       grid->Add (video_control (_frame_rate_description), wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
+       grid->Add (_frame_rate_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
        wxFont font = _frame_rate_description->GetFont();
        font.SetStyle(wxFONTSTYLE_ITALIC);
        font.SetPointSize(font.GetPointSize() - 1);
        _frame_rate_description->SetFont(font);
        ++r;
        
-       video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Original Size"), wxGBPosition (r, 0)));
-       _original_size = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       grid->Add (video_control (_original_size), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       ++r;
-       
-       video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), wxGBPosition (r, 0)));
+       add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), wxGBPosition (r, 0));
        _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
-       grid->Add (video_control (_length), wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       grid->Add (_length, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        ++r;
 
 
        {
-               video_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0)));
+               add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               video_control (add_label_to_sizer (s, _film_panel, _("Start")));
+               add_label_to_sizer (s, _film_panel, _("Start"));
                _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_trim_start));
-               video_control (add_label_to_sizer (s, _film_panel, _("End")));
+               s->Add (_trim_start);
+               add_label_to_sizer (s, _film_panel, _("End"));
                _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
-               s->Add (video_control (_trim_end));
+               s->Add (_trim_end);
 
                grid->Add (s, wxGBPosition (r, 1));
        }
        ++r;
 
-       _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
-       video_control (_dcp_ab);
-       grid->Add (_dcp_ab, wxGBPosition (r, 0));
-       ++r;
-
-       /* STILL-only stuff */
-       {
-               still_control (add_label_to_grid_bag_sizer (grid, _film_panel, _("Duration"), wxGBPosition (r, 0)));
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _still_duration = new wxSpinCtrl (_film_panel);
-               still_control (_still_duration);
-               s->Add (_still_duration, 1, wxEXPAND);
-               /// TRANSLATORS: `s' here is an abbreviation for seconds, the unit of time
-               still_control (add_label_to_sizer (s, _film_panel, _("s")));
-               grid->Add (s, wxGBPosition (r, 1));
-       }
+       _ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
+       grid->Add (_ab, wxGBPosition (r, 0));
        ++r;
 
        vector<DCPContentType const *> const ct = DCPContentType::all ();
@@ -217,8 +196,15 @@ FilmEditor::connect_to_widgets ()
        _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
        _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
        _format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
-       _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
-       _trust_content_header->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_header_changed), 0, this);
+       _trust_content_headers->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_headers_changed), 0, this);
+       _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this);
+       _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler (FilmEditor::content_selection_changed), 0, this);
+       _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler (FilmEditor::content_activated), 0, this);
+       _content_add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
+       _content_remove->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
+       _content_edit->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_edit_clicked), 0, this);
+       _content_earlier->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this);
+       _content_later->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this);
        _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
        _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
        _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
@@ -228,8 +214,7 @@ FilmEditor::connect_to_widgets ()
        _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
        _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
        _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
-       _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
-       _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
+       _ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::ab_toggled), 0, this);
        _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
        _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
        _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
@@ -237,21 +222,14 @@ FilmEditor::connect_to_widgets ()
        _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
        _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
        _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
-       _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
-       _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
+       _ffmpeg_subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_subtitle_stream_changed), 0, this);
+       _ffmpeg_audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_audio_stream_changed), 0, this);
        _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
        _audio_gain_calculate_button->Connect (
                wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
                );
        _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
        _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
-       _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
-       _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               _external_audio[i]->Connect (
-                       wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::external_audio_changed), 0, this
-                       );
-       }
 }
 
 void
@@ -300,21 +278,19 @@ FilmEditor::make_video_panel ()
 
        /* VIDEO-only stuff */
        {
-               video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0)));
+               add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0));
                wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _filters = new wxStaticText (_video_panel, wxID_ANY, _("None"));
-               video_control (_filters);
                s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
                _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit..."));
-               video_control (_filters_button);
                s->Add (_filters_button, 0);
                grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        }
        ++r;
 
-       video_control (add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0)));
+       add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0));
        _scaler = new wxChoice (_video_panel, wxID_ANY);
-       grid->Add (video_control (_scaler), wxGBPosition (r, 1));
+       grid->Add (_scaler, wxGBPosition (r, 1));
        ++r;
 
        vector<Scaler const *> const sc = Scaler::all ();
@@ -345,12 +321,48 @@ FilmEditor::make_video_panel ()
        _top_crop->SetRange (0, 1024);
        _right_crop->SetRange (0, 1024);
        _bottom_crop->SetRange (0, 1024);
-       _still_duration->SetRange (1, 60 * 60);
        _trim_start->SetRange (0, 100);
        _trim_end->SetRange (0, 100);
        _j2k_bandwidth->SetRange (50, 250);
 }
 
+void
+FilmEditor::make_content_panel ()
+{
+       _content_panel = new wxPanel (_notebook);
+       _content_sizer = new wxBoxSizer (wxVERTICAL);
+       _content_panel->SetSizer (_content_sizer);
+       
+        {
+                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                
+                _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL);
+                s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
+
+                _content->InsertColumn (0, wxT(""));
+               _content->SetColumnWidth (0, 512);
+
+                wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
+                _content_add = new wxButton (_content_panel, wxID_ANY, _("Add..."));
+                b->Add (_content_add);
+                _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
+                b->Add (_content_remove);
+                _content_edit = new wxButton (_content_panel, wxID_ANY, _("Edit..."));
+                b->Add (_content_edit);
+                _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Earlier"));
+                b->Add (_content_earlier);
+                _content_later = new wxButton (_content_panel, wxID_ANY, _("Later"));
+                b->Add (_content_later);
+
+                s->Add (b, 0, wxALL, 4);
+
+                _content_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
+        }
+
+       _content_information = new wxTextCtrl (_content_panel, wxID_ANY, wxT ("\n\n\n\n"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE);
+       _content_sizer->Add (_content_information, 1, wxEXPAND | wxALL, 6);
+}
+
 void
 FilmEditor::make_audio_panel ()
 {
@@ -366,48 +378,39 @@ FilmEditor::make_audio_panel ()
        grid->AddSpacer (0);
 
        {
-               video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain")));
+               add_label_to_sizer (grid, _audio_panel, _("Audio Gain"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _audio_gain = new wxSpinCtrl (_audio_panel);
-               s->Add (video_control (_audio_gain), 1);
-               video_control (add_label_to_sizer (s, _audio_panel, _("dB")));
+               s->Add (_audio_gain, 1);
+               add_label_to_sizer (s, _audio_panel, _("dB"));
                _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
-               video_control (_audio_gain_calculate_button);
                s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
                grid->Add (s);
        }
 
        {
-               video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay")));
+               add_label_to_sizer (grid, _audio_panel, _("Audio Delay"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _audio_delay = new wxSpinCtrl (_audio_panel);
-               s->Add (video_control (_audio_delay), 1);
+               s->Add (_audio_delay, 1);
                /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
-               video_control (add_label_to_sizer (s, _audio_panel, _("ms")));
+               add_label_to_sizer (s, _audio_panel, _("ms"));
                grid->Add (s);
        }
 
-       {
-               _use_content_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use content's audio"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
-               grid->Add (video_control (_use_content_audio));
-               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-               _audio_stream = new wxChoice (_audio_panel, wxID_ANY);
-               s->Add (video_control (_audio_stream), 1);
-               _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
-               s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
-               grid->Add (s, 1, wxEXPAND);
-       }
-
-       _use_external_audio = new wxRadioButton (_audio_panel, wxID_ANY, _("Use external audio"));
-       grid->Add (_use_external_audio);
-       grid->AddSpacer (0);
-
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               add_label_to_sizer (grid, _audio_panel, std_to_wx (audio_channel_name (i)));
-               _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
-               grid->Add (_external_audio[i], 1, wxEXPAND);
-       }
+        {
+                add_label_to_sizer (grid, _audio_panel, _("Audio Stream"));
+                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                _ffmpeg_audio_stream = new wxChoice (_audio_panel, wxID_ANY);
+                s->Add (_ffmpeg_audio_stream, 1);
+                _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
+                s->Add (_audio, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
+                grid->Add (s, 1, wxEXPAND);
+        }
 
+       _audio_mapping = new AudioMappingView (_audio_panel);
+       _audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
+       
        _audio_gain->SetRange (-60, 60);
        _audio_delay->SetRange (-1000, 1000);
 }
@@ -422,27 +425,26 @@ FilmEditor::make_subtitle_panel ()
        _subtitle_sizer->Add (grid, 0, wxALL, 8);
 
        _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
-       video_control (_with_subtitles);
        grid->Add (_with_subtitles, 1);
        
-       _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
-       grid->Add (video_control (_subtitle_stream));
+       _ffmpeg_subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
+       grid->Add (_ffmpeg_subtitle_stream);
 
        {
-               video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset")));
+               add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
                s->Add (_subtitle_offset);
-               video_control (add_label_to_sizer (s, _subtitle_panel, _("pixels")));
+               add_label_to_sizer (s, _subtitle_panel, _("pixels"));
                grid->Add (s);
        }
 
        {
-               video_control (add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale")));
+               add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
-               s->Add (video_control (_subtitle_scale));
-               video_control (add_label_to_sizer (s, _subtitle_panel, _("%")));
+               s->Add (_subtitle_scale);
+               add_label_to_sizer (s, _subtitle_panel, _("%"));
                grid->Add (s);
        }
 
@@ -494,41 +496,25 @@ FilmEditor::bottom_crop_changed (wxCommandEvent &)
        _film->set_bottom_crop (_bottom_crop->GetValue ());
 }
 
-/** Called when the content filename has been changed */
-void
-FilmEditor::content_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       try {
-               _film->set_content (wx_to_std (_content->GetPath ()));
-       } catch (std::exception& e) {
-               _content->SetPath (std_to_wx (_film->directory ()));
-               error_dialog (this, wxString::Format (_("Could not set content: %s"), std_to_wx (e.what()).data()));
-       }
-}
-
 void
-FilmEditor::trust_content_header_changed (wxCommandEvent &)
+FilmEditor::trust_content_headers_changed (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
 
-       _film->set_trust_content_header (_trust_content_header->GetValue ());
+       _film->set_trust_content_headers (_trust_content_headers->GetValue ());
 }
 
 /** Called when the DCP A/B switch has been toggled */
 void
-FilmEditor::dcp_ab_toggled (wxCommandEvent &)
+FilmEditor::ab_toggled (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
        
-       _film->set_dcp_ab (_dcp_ab->GetValue ());
+       _film->set_ab (_ab->GetValue ());
 }
 
 /** Called when the name widget has been changed */
@@ -616,43 +602,19 @@ FilmEditor::film_changed (Film::Property p)
        case Film::NONE:
                break;
        case Film::CONTENT:
-               checked_set (_content, _film->content ());
-               setup_visibility ();
+               setup_content ();
                setup_formats ();
+               setup_format ();
                setup_subtitle_control_sensitivity ();
                setup_streams ();
                setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
                break;
-       case Film::TRUST_CONTENT_HEADER:
-               checked_set (_trust_content_header, _film->trust_content_header ());
-               break;
-       case Film::SUBTITLE_STREAMS:
-               setup_subtitle_control_sensitivity ();
-               setup_streams ();
-               break;
-       case Film::CONTENT_AUDIO_STREAMS:
-               setup_streams ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
+       case Film::TRUST_CONTENT_HEADERS:
+               checked_set (_trust_content_headers, _film->trust_content_headers ());
                break;
        case Film::FORMAT:
-       {
-               int n = 0;
-               vector<Format const *>::iterator i = _formats.begin ();
-               while (i != _formats.end() && *i != _film->format ()) {
-                       ++i;
-                       ++n;
-               }
-               if (i == _formats.end()) {
-                       checked_set (_format, -1);
-               } else {
-                       checked_set (_format, n);
-               }
-               setup_dcp_name ();
-               setup_scaling_description ();
+               setup_format ();
                break;
-       }
        case Film::CROP:
                checked_set (_left_crop, _film->crop().left);
                checked_set (_right_crop, _film->crop().right);
@@ -676,40 +638,12 @@ FilmEditor::film_changed (Film::Property p)
                checked_set (_name, _film->name());
                setup_dcp_name ();
                break;
-       case Film::SOURCE_FRAME_RATE:
-               s << fixed << setprecision(2) << _film->source_frame_rate();
-               _source_frame_rate->SetLabel (std_to_wx (s.str ()));
-               setup_frame_rate_description ();
-               break;
-       case Film::SIZE:
-               if (_film->size().width == 0 && _film->size().height == 0) {
-                       _original_size->SetLabel (wxT (""));
-               } else {
-                       s << _film->size().width << " x " << _film->size().height;
-                       _original_size->SetLabel (std_to_wx (s.str ()));
-               }
-               setup_scaling_description ();
-               break;
-       case Film::LENGTH:
-               if (_film->source_frame_rate() > 0 && _film->length()) {
-                       s << _film->length().get() << " "
-                         << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->length().get() / _film->source_frame_rate());
-               } else if (_film->length()) {
-                       s << _film->length().get() << " "
-                         << wx_to_std (_("frames"));
-               } 
-               _length->SetLabel (std_to_wx (s.str ()));
-               if (_film->length()) {
-                       _trim_start->SetRange (0, _film->length().get());
-                       _trim_end->SetRange (0, _film->length().get());
-               }
-               break;
        case Film::DCP_CONTENT_TYPE:
                checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
                setup_dcp_name ();
                break;
-       case Film::DCP_AB:
-               checked_set (_dcp_ab, _film->dcp_ab ());
+       case Film::AB:
+               checked_set (_ab, _film->ab ());
                break;
        case Film::SCALER:
                checked_set (_scaler, Scaler::as_index (_film->scaler ()));
@@ -726,9 +660,6 @@ FilmEditor::film_changed (Film::Property p)
        case Film::AUDIO_DELAY:
                checked_set (_audio_delay, _film->audio_delay ());
                break;
-       case Film::STILL_DURATION:
-               checked_set (_still_duration, _film->still_duration ());
-               break;
        case Film::WITH_SUBTITLES:
                checked_set (_with_subtitles, _film->with_subtitles ());
                setup_subtitle_control_sensitivity ();
@@ -753,41 +684,6 @@ FilmEditor::film_changed (Film::Property p)
        case Film::DCI_METADATA:
                setup_dcp_name ();
                break;
-       case Film::CONTENT_AUDIO_STREAM:
-               if (_film->content_audio_stream()) {
-                       checked_set (_audio_stream, _film->content_audio_stream()->to_string());
-               }
-               setup_dcp_name ();
-               setup_audio_details ();
-               setup_audio_control_sensitivity ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               break;
-       case Film::USE_CONTENT_AUDIO:
-               checked_set (_use_content_audio, _film->use_content_audio());
-               checked_set (_use_external_audio, !_film->use_content_audio());
-               setup_dcp_name ();
-               setup_audio_details ();
-               setup_audio_control_sensitivity ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               break;
-       case Film::SUBTITLE_STREAM:
-               if (_film->subtitle_stream()) {
-                       checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
-               }
-               break;
-       case Film::EXTERNAL_AUDIO:
-       {
-               vector<string> a = _film->external_audio ();
-               for (size_t i = 0; i < a.size() && i < MAX_AUDIO_CHANNELS; ++i) {
-                       checked_set (_external_audio[i], a[i]);
-               }
-               setup_audio_details ();
-               setup_show_audio_sensitivity ();
-               setup_frame_rate_description ();
-               break;
-       }
        case Film::DCP_FRAME_RATE:
                for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) {
                        if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) {
@@ -798,30 +694,107 @@ FilmEditor::film_changed (Film::Property p)
                        }
                }
 
-               if (_film->source_frame_rate()) {
-                       _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->source_frame_rate ()) != _film->dcp_frame_rate ());
+               if (_film->video_frame_rate()) {
+                       _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->video_frame_rate ()) != _film->dcp_frame_rate ());
                } else {
                        _best_dcp_frame_rate->Disable ();
                }
-
                setup_frame_rate_description ();
+               break;
+       case Film::AUDIO_MAPPING:
+               _audio_mapping->set_mapping (_film->audio_mapping ());
+               break;
+       }
+}
+
+void
+FilmEditor::film_content_changed (weak_ptr<Content> content, int property)
+{
+       if (!_film) {
+               /* We call this method ourselves (as well as using it as a signal handler)
+                  so _film can be 0.
+               */
+               return;
+       }
+               
+       if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
+               setup_subtitle_control_sensitivity ();
+               setup_streams ();
+       } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
+               setup_streams ();
+               setup_show_audio_sensitivity ();
+       } else if (property == VideoContentProperty::VIDEO_LENGTH) {
+               setup_length ();
+               boost::shared_ptr<Content> c = content.lock ();
+               if (c && c == selected_content()) {
+                       setup_content_information ();
+               }
+       } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
+               if (_film->ffmpeg_audio_stream()) {
+                       checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id));
+               }
+               setup_dcp_name ();
+               setup_audio_details ();
+               setup_show_audio_sensitivity ();
+       } else if (property == FFmpegContentProperty::SUBTITLE_STREAM) {
+               if (_film->ffmpeg_subtitle_stream()) {
+                       checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id));
+               }
        }
 }
 
+void
+FilmEditor::setup_format ()
+{
+       int n = 0;
+       vector<Format const *>::iterator i = _formats.begin ();
+       while (i != _formats.end() && *i != _film->format ()) {
+               ++i;
+               ++n;
+       }
+       
+       if (i == _formats.end()) {
+               checked_set (_format, -1);
+       } else {
+               checked_set (_format, n);
+       }
+       
+       setup_dcp_name ();
+       setup_scaling_description ();
+}      
+
+void
+FilmEditor::setup_length ()
+{
+       stringstream s;
+       if (_film->video_frame_rate() > 0 && _film->video_length()) {
+               s << _film->video_length() << " "
+                 << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->video_length() / _film->video_frame_rate());
+       } else if (_film->video_length()) {
+               s << _film->video_length() << " "
+                 << wx_to_std (_("frames"));
+       } 
+       _length->SetLabel (std_to_wx (s.str ()));
+       if (_film->video_length()) {
+               _trim_start->SetRange (0, _film->video_length());
+               _trim_end->SetRange (0, _film->video_length());
+       }
+}      
+
 void
 FilmEditor::setup_frame_rate_description ()
 {
        wxString d;
        int lines = 0;
        
-       if (_film->source_frame_rate()) {
-               d << std_to_wx (FrameRateConversion (_film->source_frame_rate(), _film->dcp_frame_rate()).description);
+       if (_film->video_frame_rate()) {
+               d << std_to_wx (FrameRateConversion (_film->video_frame_rate(), _film->dcp_frame_rate()).description);
                ++lines;
 #ifdef HAVE_SWRESAMPLE
-               if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate ()) {
+               if (_film->audio_frame_rate() && _film->audio_frame_rate() != _film->target_audio_sample_rate ()) {
                        d << wxString::Format (
                                _("Audio will be resampled from %dHz to %dHz\n"),
-                               _film->audio_stream()->sample_rate(),
+                               _film->audio_frame_rate(),
                                _film->target_audio_sample_rate()
                                );
                        ++lines;
@@ -869,12 +842,17 @@ FilmEditor::dcp_content_type_changed (wxCommandEvent &)
 void
 FilmEditor::set_film (shared_ptr<Film> f)
 {
-       _film = f;
-
        set_things_sensitive (_film != 0);
 
+       if (_film == f) {
+               return;
+       }
+       
+       _film = f;
+
        if (_film) {
                _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
+               _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2));
        }
 
        if (_film) {
@@ -890,7 +868,7 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
        film_changed (Film::CONTENT);
-       film_changed (Film::TRUST_CONTENT_HEADER);
+       film_changed (Film::TRUST_CONTENT_HEADERS);
        film_changed (Film::DCP_CONTENT_TYPE);
        film_changed (Film::FORMAT);
        film_changed (Film::CROP);
@@ -898,25 +876,22 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::SCALER);
        film_changed (Film::TRIM_START);
        film_changed (Film::TRIM_END);
-       film_changed (Film::DCP_AB);
-       film_changed (Film::CONTENT_AUDIO_STREAM);
-       film_changed (Film::EXTERNAL_AUDIO);
-       film_changed (Film::USE_CONTENT_AUDIO);
+       film_changed (Film::AB);
        film_changed (Film::AUDIO_GAIN);
        film_changed (Film::AUDIO_DELAY);
-       film_changed (Film::STILL_DURATION);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
        film_changed (Film::COLOUR_LUT);
        film_changed (Film::J2K_BANDWIDTH);
        film_changed (Film::DCI_METADATA);
-       film_changed (Film::SIZE);
-       film_changed (Film::LENGTH);
-       film_changed (Film::CONTENT_AUDIO_STREAMS);
-       film_changed (Film::SUBTITLE_STREAMS);
-       film_changed (Film::SOURCE_FRAME_RATE);
        film_changed (Film::DCP_FRAME_RATE);
+       film_changed (Film::AUDIO_MAPPING);
+
+       film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAMS);
+       film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAM);
+       film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAMS);
+       film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAM);
 }
 
 /** Updates the sensitivity of lots of widgets to a given value.
@@ -932,30 +907,30 @@ FilmEditor::set_things_sensitive (bool s)
        _edit_dci_button->Enable (s);
        _format->Enable (s);
        _content->Enable (s);
-       _trust_content_header->Enable (s);
+       _trust_content_headers->Enable (s);
+       _content->Enable (s);
        _left_crop->Enable (s);
        _right_crop->Enable (s);
        _top_crop->Enable (s);
        _bottom_crop->Enable (s);
        _filters_button->Enable (s);
        _scaler->Enable (s);
-       _audio_stream->Enable (s);
+       _ffmpeg_audio_stream->Enable (s);
        _dcp_content_type->Enable (s);
        _dcp_frame_rate->Enable (s);
        _trim_start->Enable (s);
        _trim_end->Enable (s);
-       _dcp_ab->Enable (s);
+       _ab->Enable (s);
        _colour_lut->Enable (s);
        _j2k_bandwidth->Enable (s);
        _audio_gain->Enable (s);
        _audio_gain_calculate_button->Enable (s);
        _show_audio->Enable (s);
        _audio_delay->Enable (s);
-       _still_duration->Enable (s);
 
        setup_subtitle_control_sensitivity ();
-       setup_audio_control_sensitivity ();
        setup_show_audio_sensitivity ();
+       setup_content_button_sensitivity ();
 }
 
 /** Called when the `Edit filters' button has been clicked */
@@ -1002,40 +977,6 @@ FilmEditor::audio_delay_changed (wxCommandEvent &)
        _film->set_audio_delay (_audio_delay->GetValue ());
 }
 
-wxControl *
-FilmEditor::video_control (wxControl* c)
-{
-       _video_controls.push_back (c);
-       return c;
-}
-
-wxControl *
-FilmEditor::still_control (wxControl* c)
-{
-       _still_controls.push_back (c);
-       return c;
-}
-
-void
-FilmEditor::setup_visibility ()
-{
-       ContentType c = VIDEO;
-
-       if (_film) {
-               c = _film->content_type ();
-       }
-
-       for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
-               (*i)->Show (c == VIDEO);
-       }
-
-       for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
-               (*i)->Show (c == STILL);
-       }
-
-       setup_notebook_size ();
-}
-
 void
 FilmEditor::setup_notebook_size ()
 {
@@ -1054,16 +995,6 @@ FilmEditor::setup_notebook_size ()
        Fit ();
 }
 
-void
-FilmEditor::still_duration_changed (wxCommandEvent &)
-{
-       if (!_film) {
-               return;
-       }
-
-       _film->set_still_duration (_still_duration->GetValue ());
-}
-
 void
 FilmEditor::trim_start_changed (wxCommandEvent &)
 {
@@ -1114,20 +1045,7 @@ FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
 void
 FilmEditor::setup_formats ()
 {
-       ContentType c = VIDEO;
-
-       if (_film) {
-               c = _film->content_type ();
-       }
-       
-       _formats.clear ();
-
-       vector<Format const *> fmt = Format::all ();
-       for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
-               if (c == VIDEO || (c == STILL && dynamic_cast<VariableFormat const *> (*i))) {
-                       _formats.push_back (*i);
-               }
-       }
+       _formats = Format::all ();
 
        _format->Clear ();
        for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
@@ -1152,7 +1070,7 @@ FilmEditor::setup_subtitle_control_sensitivity ()
 {
        bool h = false;
        if (_generally_sensitive && _film) {
-               h = !_film->subtitle_streams().empty();
+               h = !_film->ffmpeg_subtitle_streams().empty();
        }
        
        _with_subtitles->Enable (h);
@@ -1162,26 +1080,11 @@ FilmEditor::setup_subtitle_control_sensitivity ()
                j = _film->with_subtitles ();
        }
        
-       _subtitle_stream->Enable (j);
+       _ffmpeg_subtitle_stream->Enable (j);
        _subtitle_offset->Enable (j);
        _subtitle_scale->Enable (j);
 }
 
-void
-FilmEditor::setup_audio_control_sensitivity ()
-{
-       _use_content_audio->Enable (_generally_sensitive && _film && !_film->content_audio_streams().empty());
-       _use_external_audio->Enable (_generally_sensitive);
-       
-       bool const source = _generally_sensitive && _use_content_audio->GetValue();
-       bool const external = _generally_sensitive && _use_external_audio->GetValue();
-
-       _audio_stream->Enable (source);
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               _external_audio[i]->Enable (external);
-       }
-}
-
 void
 FilmEditor::use_dci_name_toggled (wxCommandEvent &)
 {
@@ -1208,73 +1111,84 @@ FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
 void
 FilmEditor::setup_streams ()
 {
-       _audio_stream->Clear ();
-       vector<shared_ptr<AudioStream> > a = _film->content_audio_streams ();
-       for (vector<shared_ptr<AudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
-               shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (*i);
-               assert (ffa);
-               _audio_stream->Append (std_to_wx (ffa->name()), new wxStringClientData (std_to_wx (ffa->to_string ())));
+       if (!_film) {
+               return;
+       }
+       
+       _ffmpeg_audio_stream->Clear ();
+       vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams ();
+       for (vector<FFmpegAudioStream>::iterator i = a.begin(); i != a.end(); ++i) {
+               _ffmpeg_audio_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id))));
        }
        
-       if (_film->use_content_audio() && _film->audio_stream()) {
-               checked_set (_audio_stream, _film->audio_stream()->to_string());
+       if (_film->ffmpeg_audio_stream()) {
+               checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id));
        }
 
-       _subtitle_stream->Clear ();
-       vector<shared_ptr<SubtitleStream> > s = _film->subtitle_streams ();
-       for (vector<shared_ptr<SubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
-               _subtitle_stream->Append (std_to_wx ((*i)->name()), new wxStringClientData (std_to_wx ((*i)->to_string ())));
+       _ffmpeg_subtitle_stream->Clear ();
+       vector<FFmpegSubtitleStream> s = _film->ffmpeg_subtitle_streams ();
+       for (vector<FFmpegSubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) {
+               _ffmpeg_subtitle_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id))));
        }
-       if (_film->subtitle_stream()) {
-               checked_set (_subtitle_stream, _film->subtitle_stream()->to_string());
+       
+       if (_film->ffmpeg_subtitle_stream()) {
+               checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id));
        } else {
-               _subtitle_stream->SetSelection (wxNOT_FOUND);
+               _ffmpeg_subtitle_stream->SetSelection (wxNOT_FOUND);
        }
 }
 
 void
-FilmEditor::audio_stream_changed (wxCommandEvent &)
+FilmEditor::ffmpeg_audio_stream_changed (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
 
-       _film->set_content_audio_stream (
-               audio_stream_factory (
-                       string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ())),
-                       Film::state_version
-                       )
-               );
+       vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams ();
+       vector<FFmpegAudioStream>::iterator i = a.begin ();
+       string const s = string_client_data (_ffmpeg_audio_stream->GetClientObject (_ffmpeg_audio_stream->GetSelection ()));
+       while (i != a.end() && lexical_cast<string> (i->id) != s) {
+               ++i;
+       }
+
+       if (i != a.end ()) {
+               _film->set_ffmpeg_audio_stream (*i);
+       }
 }
 
 void
-FilmEditor::subtitle_stream_changed (wxCommandEvent &)
+FilmEditor::ffmpeg_subtitle_stream_changed (wxCommandEvent &)
 {
        if (!_film) {
                return;
        }
 
-       _film->set_subtitle_stream (
-               subtitle_stream_factory (
-                       string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ())),
-                       Film::state_version
-                       )
-               );
+       vector<FFmpegSubtitleStream> a = _film->ffmpeg_subtitle_streams ();
+       vector<FFmpegSubtitleStream>::iterator i = a.begin ();
+       string const s = string_client_data (_ffmpeg_subtitle_stream->GetClientObject (_ffmpeg_subtitle_stream->GetSelection ()));
+       while (i != a.end() && lexical_cast<string> (i->id) != s) {
+               ++i;
+       }
+
+       if (i != a.end ()) {
+               _film->set_ffmpeg_subtitle_stream (*i);
+       }
 }
 
 void
 FilmEditor::setup_audio_details ()
 {
-       if (!_film->content_audio_stream()) {
+       if (!_film->ffmpeg_audio_stream()) {
                _audio->SetLabel (wxT (""));
        } else {
                wxString s;
-               if (_film->audio_stream()->channels() == 1) {
+               if (_film->audio_channels() == 1) {
                        s << _("1 channel");
                } else {
-                       s << _film->audio_stream()->channels () << wxT (" ") << _("channels");
+                       s << _film->audio_channels() << wxT (" ") << _("channels");
                }
-               s << wxT (", ") << _film->audio_stream()->sample_rate() << _("Hz");
+               s << wxT (", ") << _film->audio_frame_rate() << _("Hz");
                _audio->SetLabel (s);
        }
 
@@ -1287,23 +1201,6 @@ FilmEditor::active_jobs_changed (bool a)
        set_things_sensitive (!a);
 }
 
-void
-FilmEditor::use_audio_changed (wxCommandEvent &)
-{
-       _film->set_use_content_audio (_use_content_audio->GetValue());
-}
-
-void
-FilmEditor::external_audio_changed (wxCommandEvent &)
-{
-       vector<string> a;
-       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               a.push_back (wx_to_std (_external_audio[i]->GetPath()));
-       }
-
-       _film->set_external_audio (a);
-}
-
 void
 FilmEditor::setup_dcp_name ()
 {
@@ -1336,7 +1233,7 @@ FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
                return;
        }
        
-       _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->source_frame_rate ()));
+       _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->video_frame_rate ()));
 }
 
 void
@@ -1345,6 +1242,163 @@ FilmEditor::setup_show_audio_sensitivity ()
        _show_audio->Enable (_film && _film->has_audio ());
 }
 
+void
+FilmEditor::setup_content ()
+{
+       string selected_summary;
+       int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (s != -1) {
+               selected_summary = wx_to_std (_content->GetItemText (s));
+       }
+       
+       _content->DeleteAllItems ();
+
+       ContentList content = _film->content ();
+       for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
+               int const t = _content->GetItemCount ();
+               _content->InsertItem (t, std_to_wx ((*i)->summary ()));
+               if ((*i)->summary() == selected_summary) {
+                       _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+               }
+       }
+
+       if (selected_summary.empty () && !content.empty ()) {
+               /* Select the first item of content if non was selected before */
+               _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+       }
+}
+
+void
+FilmEditor::content_add_clicked (wxCommandEvent &)
+{
+       wxFileDialog* d = new wxFileDialog (this);
+       int const r = d->ShowModal ();
+       d->Destroy ();
+
+       if (r != wxID_OK) {
+               return;
+       }
+
+       boost::filesystem::path p (wx_to_std (d->GetPath()));
+
+       if (ImageMagickContent::valid_file (p)) {
+               _film->add_content (shared_ptr<ImageMagickContent> (new ImageMagickContent (p)));
+       } else if (SndfileContent::valid_file (p)) {
+               _film->add_content (shared_ptr<SndfileContent> (new SndfileContent (p)));
+       } else {
+               _film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (p)));
+       }
+       
+}
+
+void
+FilmEditor::content_remove_clicked (wxCommandEvent &)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (c) {
+               _film->remove_content (c);
+       }
+}
+
+void
+FilmEditor::content_activated (wxListEvent& ev)
+{
+       ContentList c = _film->content ();
+       assert (ev.GetIndex() >= 0 && size_t (ev.GetIndex()) < c.size ());
+
+       edit_content (c[ev.GetIndex()]);
+}
+
+void
+FilmEditor::content_edit_clicked (wxCommandEvent &)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               return;
+       }
+
+       edit_content (c);
+}
+
+void
+FilmEditor::edit_content (shared_ptr<Content> c)
+{
+       shared_ptr<ImageMagickContent> im = dynamic_pointer_cast<ImageMagickContent> (c);
+       if (im) {
+               ImageMagickContentDialog* d = new ImageMagickContentDialog (this, im);
+               d->ShowModal ();
+               d->Destroy ();
+
+               im->set_video_length (d->video_length() * 24);
+       }
+}
+
+void
+FilmEditor::content_earlier_clicked (wxCommandEvent &)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (c) {
+               _film->move_content_earlier (c);
+       }
+}
+
+void
+FilmEditor::content_later_clicked (wxCommandEvent &)
+{
+       shared_ptr<Content> c = selected_content ();
+       if (c) {
+               _film->move_content_later (c);
+       }
+}
+
+void
+FilmEditor::content_selection_changed (wxListEvent &)
+{
+        setup_content_button_sensitivity ();
+       setup_content_information ();
+}
+
+void
+FilmEditor::setup_content_information ()
+{
+       shared_ptr<Content> c = selected_content ();
+       if (!c) {
+               _content_information->SetValue (wxT (""));
+               return;
+       }
+
+       _content_information->SetValue (std_to_wx (c->information ()));
+}
+
+void
+FilmEditor::setup_content_button_sensitivity ()
+{
+        _content_add->Enable (_generally_sensitive);
+
+       shared_ptr<Content> selection = selected_content ();
+
+        _content_edit->Enable (selection && _generally_sensitive && dynamic_pointer_cast<ImageMagickContent> (selection));
+        _content_remove->Enable (selection && _generally_sensitive);
+        _content_earlier->Enable (selection && _generally_sensitive);
+        _content_later->Enable (selection && _generally_sensitive);
+}
+
+shared_ptr<Content>
+FilmEditor::selected_content ()
+{
+       int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (s == -1) {
+               return shared_ptr<Content> ();
+       }
+
+       ContentList c = _film->content ();
+       if (s < 0 || size_t (s) >= c.size ()) {
+               return shared_ptr<Content> ();
+       }
+       
+       return c[s];
+}
+
 void
 FilmEditor::setup_scaling_description ()
 {
@@ -1352,18 +1406,18 @@ FilmEditor::setup_scaling_description ()
 
        int lines = 0;
 
-       if (_film->size().width && _film->size().height) {
+       if (_film->video_size().width && _film->video_size().height) {
                d << wxString::Format (
                        _("Original video is %dx%d (%.2f:1)\n"),
-                       _film->size().width, _film->size().height,
-                       float (_film->size().width) / _film->size().height
+                       _film->video_size().width, _film->video_size().height,
+                       float (_film->video_size().width) / _film->video_size().height
                        );
                ++lines;
        }
 
        Crop const crop = _film->crop ();
        if (crop.left || crop.right || crop.top || crop.bottom) {
-               libdcp::Size const cropped = _film->cropped_size (_film->size ());
+               libdcp::Size const cropped = _film->cropped_size (_film->video_size ());
                d << wxString::Format (
                        _("Cropped to %dx%d (%.2f:1)\n"),
                        cropped.width, cropped.height,
index 7123620d3753156b0d37382f4945b3fa0f123f7a..0f3d8eb507a5eabc0c6e316f81fd7b03c4f6924c 100644 (file)
@@ -16,7 +16,7 @@
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 */
-
 /** @file src/film_editor.h
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
  */
 #include "lib/film.h"
 
 class wxNotebook;
+class wxListCtrl;
+class wxListEvent;
 class Film;
 class AudioDialog;
+class AudioMappingView;
 
 /** @class FilmEditor
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -41,12 +44,12 @@ public:
        FilmEditor (boost::shared_ptr<Film>, wxWindow *);
 
        void set_film (boost::shared_ptr<Film>);
-       void setup_visibility ();
 
        boost::signals2::signal<void (std::string)> FileChanged;
 
 private:
        void make_film_panel ();
+       void make_content_panel ();
        void make_video_panel ();
        void make_audio_panel ();
        void make_subtitle_panel ();
@@ -60,13 +63,20 @@ private:
        void right_crop_changed (wxCommandEvent &);
        void top_crop_changed (wxCommandEvent &);
        void bottom_crop_changed (wxCommandEvent &);
-       void content_changed (wxCommandEvent &);
-       void trust_content_header_changed (wxCommandEvent &);
+       void trust_content_headers_changed (wxCommandEvent &);
+       void content_selection_changed (wxListEvent &);
+       void content_activated (wxListEvent &);
+       void content_add_clicked (wxCommandEvent &);
+       void content_remove_clicked (wxCommandEvent &);
+       void content_edit_clicked (wxCommandEvent &);
+       void content_earlier_clicked (wxCommandEvent &);
+       void content_later_clicked (wxCommandEvent &);
+       void imagemagick_video_length_changed (wxCommandEvent &);
        void format_changed (wxCommandEvent &);
        void trim_start_changed (wxCommandEvent &);
        void trim_end_changed (wxCommandEvent &);
        void dcp_content_type_changed (wxCommandEvent &);
-       void dcp_ab_toggled (wxCommandEvent &);
+       void ab_toggled (wxCommandEvent &);
        void scaler_changed (wxCommandEvent &);
        void audio_gain_changed (wxCommandEvent &);
        void audio_gain_calculate_button_clicked (wxCommandEvent &);
@@ -77,24 +87,19 @@ private:
        void subtitle_scale_changed (wxCommandEvent &);
        void colour_lut_changed (wxCommandEvent &);
        void j2k_bandwidth_changed (wxCommandEvent &);
-       void still_duration_changed (wxCommandEvent &);
-       void audio_stream_changed (wxCommandEvent &);
-       void subtitle_stream_changed (wxCommandEvent &);
-       void use_audio_changed (wxCommandEvent &);
-       void external_audio_changed (wxCommandEvent &);
+       void ffmpeg_audio_stream_changed (wxCommandEvent &);
+       void ffmpeg_subtitle_stream_changed (wxCommandEvent &);
        void dcp_frame_rate_changed (wxCommandEvent &);
        void best_dcp_frame_rate_clicked (wxCommandEvent &);
+       void edit_filters_clicked (wxCommandEvent &);
 
        /* Handle changes to the model */
        void film_changed (Film::Property);
-
-       /* Button clicks */
-       void edit_filters_clicked (wxCommandEvent &);
+       void film_content_changed (boost::weak_ptr<Content>, int);
 
        void set_things_sensitive (bool);
        void setup_formats ();
        void setup_subtitle_control_sensitivity ();
-       void setup_audio_control_sensitivity ();
        void setup_streams ();
        void setup_audio_details ();
        void setup_dcp_name ();
@@ -102,15 +107,21 @@ private:
        void setup_scaling_description ();
        void setup_notebook_size ();
        void setup_frame_rate_description ();
+       void setup_content ();
+       void setup_format ();
+       void setup_length ();
+       void setup_content_information ();
+       void setup_content_button_sensitivity ();
        
-       wxControl* video_control (wxControl *);
-       wxControl* still_control (wxControl *);
-
        void active_jobs_changed (bool);
+       boost::shared_ptr<Content> selected_content ();
+       void edit_content (boost::shared_ptr<Content>);
 
        wxNotebook* _notebook;
        wxPanel* _film_panel;
        wxSizer* _film_sizer;
+       wxPanel* _content_panel;
+       wxSizer* _content_sizer;
        wxPanel* _video_panel;
        wxSizer* _video_sizer;
        wxPanel* _audio_panel;
@@ -124,67 +135,49 @@ private:
        wxTextCtrl* _name;
        wxStaticText* _dcp_name;
        wxCheckBox* _use_dci_name;
+       wxListCtrl* _content;
+       wxButton* _content_add;
+       wxButton* _content_remove;
+       wxButton* _content_edit;
+       wxButton* _content_earlier;
+       wxButton* _content_later;
+       wxTextCtrl* _content_information;
        wxButton* _edit_dci_button;
-       /** The Film's format */
        wxChoice* _format;
+       wxStaticText* _format_description;
+       wxCheckBox* _trust_content_headers;
        wxStaticText* _scaling_description;
-       /** The Film's content file */
-       wxFilePickerCtrl* _content;
-       wxCheckBox* _trust_content_header;
-       /** The Film's left crop */
        wxSpinCtrl* _left_crop;
-       /** The Film's right crop */
        wxSpinCtrl* _right_crop;
-       /** The Film's top crop */
        wxSpinCtrl* _top_crop;
-       /** The Film's bottom crop */
        wxSpinCtrl* _bottom_crop;
-       /** Currently-applied filters */
        wxStaticText* _filters;
-       /** Button to open the filters dialogue */
        wxButton* _filters_button;
-       /** The Film's scaler */
        wxChoice* _scaler;
-       wxRadioButton* _use_content_audio;
-       wxChoice* _audio_stream;
-       wxRadioButton* _use_external_audio;
-       wxFilePickerCtrl* _external_audio[MAX_AUDIO_CHANNELS];
-       /** The Film's audio gain */
        wxSpinCtrl* _audio_gain;
-       /** A button to open the gain calculation dialogue */
        wxButton* _audio_gain_calculate_button;
        wxButton* _show_audio;
-       /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
+       wxChoice* _ffmpeg_audio_stream;
+       AudioMappingView* _audio_mapping;
        wxCheckBox* _with_subtitles;
-       wxChoice* _subtitle_stream;
+       wxChoice* _ffmpeg_subtitle_stream;
        wxSpinCtrl* _subtitle_offset;
        wxSpinCtrl* _subtitle_scale;
        wxChoice* _colour_lut;
        wxSpinCtrl* _j2k_bandwidth;
-       /** The Film's DCP content type */
        wxChoice* _dcp_content_type;
-       /** The Film's source frame rate */
-       wxStaticText* _source_frame_rate;
        wxChoice* _dcp_frame_rate;
        wxButton* _best_dcp_frame_rate;
        wxStaticText* _frame_rate_description;
-       /** The Film's original size */
-       wxStaticText* _original_size;
-       /** The Film's length */
        wxStaticText* _length;
        /** The Film's audio details */
        wxStaticText* _audio;
-       /** The Film's duration for still sources */
-       wxSpinCtrl* _still_duration;
 
        wxSpinCtrl* _trim_start;
        wxSpinCtrl* _trim_end;
        /** Selector to generate an A/B comparison DCP */
-       wxCheckBox* _dcp_ab;
-
-       std::list<wxControl*> _video_controls;
-       std::list<wxControl*> _still_controls;
+       wxCheckBox* _ab;
 
        std::vector<Format const *> _formats;
 
index dbbff37133ff5d7e97ffbacfb8e8e409f06da214..cba19c07c2ba6d093d6e420d33f13f4803bc47e0 100644 (file)
 #include "lib/format.h"
 #include "lib/util.h"
 #include "lib/job_manager.h"
-#include "lib/options.h"
 #include "lib/subtitle.h"
 #include "lib/image.h"
 #include "lib/scaler.h"
 #include "lib/exceptions.h"
 #include "lib/examine_content_job.h"
 #include "lib/filter.h"
+#include "lib/player.h"
+#include "lib/video_content.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/imagemagick_content.h"
 #include "film_viewer.h"
 #include "wx_util.h"
 #include "video_decoder.h"
@@ -45,6 +48,8 @@ using std::max;
 using std::cout;
 using std::list;
 using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::weak_ptr;
 using libdcp::Size;
 
 FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
@@ -96,43 +101,23 @@ FilmViewer::film_changed (Film::Property p)
                break;
        case Film::CONTENT:
        {
-               DecodeOptions o;
-               o.decode_audio = false;
-               o.decode_subtitles = true;
-               o.video_sync = false;
-
-               try {
-                       _decoders = decoder_factory (_film, o);
-               } catch (StringError& e) {
-                       error_dialog (this, wxString::Format (_("Could not open content file (%s)"), std_to_wx(e.what()).data()));
-                       return;
-               }
-               
-               if (_decoders.video == 0) {
-                       break;
-               }
-               _decoders.video->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
-               _decoders.video->OutputChanged.connect (boost::bind (&FilmViewer::decoder_changed, this));
-               _decoders.video->set_subtitle_stream (_film->subtitle_stream());
                calculate_sizes ();
                get_frame ();
                _panel->Refresh ();
-               _slider->Show (_film->content_type() == VIDEO);
-               _play_button->Show (_film->content_type() == VIDEO);
                _v_sizer->Layout ();
                break;
        }
        case Film::WITH_SUBTITLES:
        case Film::SUBTITLE_OFFSET:
        case Film::SUBTITLE_SCALE:
+               raw_to_display ();
+               _panel->Refresh ();
+               _panel->Update ();
+               break;
        case Film::SCALER:
        case Film::FILTERS:
-               update_from_raw ();
-               break;
-       case Film::SUBTITLE_STREAM:
-               if (_decoders.video) {
-                       _decoders.video->set_subtitle_stream (_film->subtitle_stream ());
-               }
+       case Film::CROP:
+               update_from_decoder ();
                break;
        default:
                break;
@@ -145,7 +130,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
        if (_film == f) {
                return;
        }
-       
+
        _film = f;
 
        _raw_frame.reset ();
@@ -157,20 +142,29 @@ FilmViewer::set_film (shared_ptr<Film> f)
                return;
        }
 
+       _player = f->player ();
+       _player->disable_audio ();
+       _player->disable_video_sync ();
+       /* Don't disable subtitles here as we may need them, and it's nice to be able to turn them
+          on and off without needing obtain a new Player.
+       */
+       
+       _player->Video.connect (bind (&FilmViewer::process_video, this, _1, _2, _3));
+       
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
+       _film->ContentChanged.connect (boost::bind (&FilmViewer::film_content_changed, this, _1, _2));
 
        film_changed (Film::CONTENT);
        film_changed (Film::FORMAT);
        film_changed (Film::WITH_SUBTITLES);
        film_changed (Film::SUBTITLE_OFFSET);
        film_changed (Film::SUBTITLE_SCALE);
-       film_changed (Film::SUBTITLE_STREAM);
 }
 
 void
-FilmViewer::decoder_changed ()
+FilmViewer::update_from_decoder ()
 {
-       if (_decoders.video == 0 || _decoders.video->seek_to_last ()) {
+       if (!_player || _player->seek (_player->last_video_time ())) {
                return;
        }
 
@@ -182,7 +176,7 @@ FilmViewer::decoder_changed ()
 void
 FilmViewer::timer (wxTimerEvent &)
 {
-       if (!_film || !_decoders.video) {
+       if (!_player) {
                return;
        }
        
@@ -191,8 +185,8 @@ FilmViewer::timer (wxTimerEvent &)
 
        get_frame ();
 
-       if (_film->length()) {
-               int const new_slider_position = 4096 * _decoders.video->last_source_time() / (_film->length().get() / _film->source_frame_rate());
+       if (_film->video_length()) {
+               int const new_slider_position = 4096 * _player->last_video_time() / (_film->video_length() / _film->video_frame_rate());
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@ -248,11 +242,11 @@ FilmViewer::paint_panel (wxPaintEvent &)
 void
 FilmViewer::slider_moved (wxScrollEvent &)
 {
-       if (!_film || !_film->length() || !_decoders.video) {
+       if (!_film || !_player) {
                return;
        }
-       
-       if (_decoders.video->seek (_slider->GetValue() * _film->length().get() / (4096 * _film->source_frame_rate()))) {
+
+       if (_player->seek (_slider->GetValue() * _film->video_length() / (4096 * _film->video_frame_rate()))) {
                return;
        }
        
@@ -290,7 +284,7 @@ FilmViewer::raw_to_display ()
                return;
        }
 
-       boost::shared_ptr<Image> input = _raw_frame;
+       shared_ptr<Image> input = _raw_frame;
 
        pair<string, string> const s = Filter::ffmpeg_strings (_film->filters());
        if (!s.second.empty ()) {
@@ -306,7 +300,7 @@ FilmViewer::raw_to_display ()
                   when working out the scale that we are applying.
                */
 
-               Size const cropped_size = _film->cropped_size (_film->size ());
+               Size const cropped_size = _film->cropped_size (_film->video_size ());
 
                Rect tx = subtitle_transformed_area (
                        float (_film_size.width) / cropped_size.width,
@@ -325,7 +319,7 @@ FilmViewer::raw_to_display ()
 void
 FilmViewer::calculate_sizes ()
 {
-       if (!_film) {
+       if (!_film || !_player) {
                return;
        }
 
@@ -375,7 +369,7 @@ FilmViewer::check_play_state ()
        }
        
        if (_play_button->GetValue()) {
-               _timer.Start (1000 / _film->source_frame_rate());
+               _timer.Start (1000 / _film->video_frame_rate());
        } else {
                _timer.Stop ();
        }
@@ -392,21 +386,24 @@ FilmViewer::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle> s
        _got_frame = true;
 }
 
+/** Get a new _raw_frame from the decoder and then do
+ *  raw_to_display ().
+ */
 void
 FilmViewer::get_frame ()
 {
        /* Clear our raw frame in case we don't get a new one */
        _raw_frame.reset ();
 
-       if (_decoders.video == 0) {
+       if (!_player) {
                _display_frame.reset ();
                return;
        }
-       
+
        try {
                _got_frame = false;
                while (!_got_frame) {
-                       if (_decoders.video->pass ()) {
+                       if (_player->pass ()) {
                                /* We didn't get a frame before the decoder gave up,
                                   so clear our display frame.
                                */
@@ -441,3 +438,12 @@ FilmViewer::active_jobs_changed (bool a)
        _play_button->Enable (!a);
 }
 
+void
+FilmViewer::film_content_changed (weak_ptr<Content>, int p)
+{
+       if (p == VideoContentProperty::VIDEO_LENGTH) {
+               /* Force an update to our frame */
+               wxScrollEvent ev;
+               slider_moved (ev);
+       }
+}
index 784434f6b60c2a198ce4c29edbd97575eba5b475..c81c65acd782d4deb26cb1e7c37c1c540e308955 100644 (file)
@@ -23,7 +23,6 @@
 
 #include <wx/wx.h>
 #include "lib/film.h"
-#include "lib/decoder_factory.h"
 
 class wxToggleButton;
 class FFmpegPlayer;
@@ -33,6 +32,25 @@ class Subtitle;
 
 /** @class FilmViewer
  *  @brief A wx widget to view a preview of a Film.
+ *
+ *  The film takes the following path through the viewer:
+ *
+ *  1.  get_frame() asks our _player to decode some data.  If it does, process_video()
+ *      will be called.
+ *
+ *  2.  process_video() takes the image and subtitle from the decoder (_raw_frame and _raw_sub)
+ *      and calls raw_to_display().
+ * 
+ *  3.  raw_to_display() copies _raw_frame to _display_frame, processing it and scaling it.
+ *
+ *  4.  calling _panel->Refresh() and _panel->Update() results in paint_panel() being called;
+ *      this creates frame_bitmap from _display_frame and blits it to the display.  It also
+ *      blits the subtitle, if required.
+ *
+ * update_from_decoder() asks the player to re-emit its current frame on the next pass(), and then
+ * starts from step #1.
+ *
+ * update_from_raw() starts at step #3, then calls _panel->Refresh and _panel->Update.
  */
 class FilmViewer : public wxPanel
 {
@@ -43,6 +61,7 @@ public:
 
 private:
        void film_changed (Film::Property);
+       void film_content_changed (boost::weak_ptr<Content>, int);
        void paint_panel (wxPaintEvent &);
        void panel_sized (wxSizeEvent &);
        void slider_moved (wxScrollEvent &);
@@ -52,12 +71,13 @@ private:
        void calculate_sizes ();
        void check_play_state ();
        void update_from_raw ();
-       void decoder_changed ();
+       void update_from_decoder ();
        void raw_to_display ();
        void get_frame ();
        void active_jobs_changed (bool);
 
        boost::shared_ptr<Film> _film;
+       boost::shared_ptr<Player> _player;
 
        wxSizer* _v_sizer;
        wxPanel* _panel;
@@ -65,7 +85,6 @@ private:
        wxToggleButton* _play_button;
        wxTimer _timer;
 
-       Decoders _decoders;
        boost::shared_ptr<Image> _raw_frame;
        boost::shared_ptr<Subtitle> _raw_sub;
        boost::shared_ptr<Image> _display_frame;
diff --git a/src/wx/imagemagick_content_dialog.cc b/src/wx/imagemagick_content_dialog.cc
new file mode 100644 (file)
index 0000000..726e4b8
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+    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 <wx/spinctrl.h>
+#include "lib/imagemagick_content.h"
+#include "imagemagick_content_dialog.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+
+ImageMagickContentDialog::ImageMagickContentDialog (wxWindow* parent, shared_ptr<ImageMagickContent> content)
+       : wxDialog (parent, wxID_ANY, _("Image"))
+{
+       wxFlexGridSizer* grid = new wxFlexGridSizer (3, 6, 6);
+       grid->AddGrowableCol (1, 1);
+
+       {
+               add_label_to_sizer (grid, this, (_("Duration")));
+               wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _video_length = new wxSpinCtrl (this);
+               s->Add (_video_length);
+               /// TRANSLATORS: this is an abbreviation for seconds, the unit of time
+               add_label_to_sizer (s, this, _("s"));
+               grid->Add (s);
+       }
+
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       overall_sizer->Add (grid, 1, wxEXPAND | wxALL, 6);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       SetSizer (overall_sizer);
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+
+       checked_set (_video_length, content->video_length () / 24);
+}
+
+int
+ImageMagickContentDialog::video_length () const
+{
+       return _video_length->GetValue ();
+}
diff --git a/src/wx/imagemagick_content_dialog.h b/src/wx/imagemagick_content_dialog.h
new file mode 100644 (file)
index 0000000..2fa9559
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    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 <wx/wx.h>
+
+class wxSpinCtrl;
+class ImageMagickContent;
+
+class ImageMagickContentDialog : public wxDialog
+{
+public:
+       ImageMagickContentDialog (wxWindow *, boost::shared_ptr<ImageMagickContent>);
+
+       int video_length () const;
+
+private:
+       wxSpinCtrl* _video_length;
+};
index f7d2315ccf68ea5ae3078d15eece597a8c6ded58..a7788ddd01334cd0a8d2dba94afb3f3b8e489321 100644 (file)
@@ -135,7 +135,7 @@ JobManagerView::details_clicked (wxCommandEvent& ev)
 {
        wxObject* o = ev.GetEventObject ();
 
-       for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+       for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
                if (i->second.details == o) {
                        string s = i->first->error_summary();
                        s[0] = toupper (s[0]);
@@ -149,7 +149,7 @@ JobManagerView::cancel_clicked (wxCommandEvent& ev)
 {
        wxObject* o = ev.GetEventObject ();
 
-       for (map<boost::shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
+       for (map<shared_ptr<Job>, JobRecord>::iterator i = _job_records.begin(); i != _job_records.end(); ++i) {
                if (i->second.cancel == o) {
                        i->first->cancel ();
                }
index 44a713dc34ce181bc9e857a878b93126815c3b33..06e2458321eb46850e3cc7e5aa981f2f4f4c450d 100644 (file)
@@ -50,6 +50,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
        _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this));
        table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL);
 
+#if 0  
        if (_film->length()) {
                _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().get())));
                FrameRateConversion frc (_film->source_frame_rate(), _film->dcp_frame_rate());
@@ -62,6 +63,7 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film)
                _frames->SetLabel (_("unknown"));
                _disk->SetLabel (_("unknown"));
        }
+#endif 
 
        wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
        overall_sizer->Add (table, 0, wxALL, 6);
@@ -85,9 +87,9 @@ PropertiesDialog::frames_already_encoded () const
                return "";
        }
        
-       if (_film->length()) {
-               /* XXX: encoded_frames() should check which frames have been encoded */
-               u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
-       }
+//     if (_film->length()) {
+//             /* XXX: encoded_frames() should check which frames have been encoded */
+//             u << " (" << (_film->encoded_frames() * 100 / _film->length().get()) << "%)";
+//     }
        return u.str ();
 }
index 42bb8ca8865ee679440fb0bb1395609ea0a90bf3..7f9cde9acae25bad106a0a6c9327df135e6b4314 100644 (file)
@@ -5,6 +5,7 @@ import i18n
 
 sources = """
           audio_dialog.cc
+          audio_mapping_view.cc
           audio_plot.cc
           config_dialog.cc
           dci_metadata_dialog.cc
@@ -14,6 +15,7 @@ sources = """
           filter_dialog.cc
           filter_view.cc
           gain_calculator_dialog.cc
+          imagemagick_content_dialog.cc
           job_manager_view.cc
           job_wrapper.cc
           new_film_dialog.cc
index d1bb400f98f2d557946b0032656264046c632972..4d25d50f85944fd6162ac5fb1294a53859c3d274 100644 (file)
@@ -40,6 +40,7 @@
 #include "scaler.h"
 #include "ffmpeg_decoder.h"
 #include "sndfile_decoder.h"
+#include "dcp_content_type.h"
 #define BOOST_TEST_DYN_LINK
 #define BOOST_TEST_MODULE dvdomatic_test
 #include <boost/test/unit_test.hpp>
@@ -142,7 +143,7 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        BOOST_CHECK (f->filters ().empty());
 
        f->set_name ("fred");
-       BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
+//     BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError);
        f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short"));
        f->set_format (Format::from_nickname ("Flat"));
        f->set_left_crop (1);
@@ -155,7 +156,7 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        f->set_filters (f_filters);
        f->set_trim_start (42);
        f->set_trim_end (99);
-       f->set_dcp_ab (true);
+       f->set_ab (true);
        f->write_metadata ();
 
        stringstream s;
@@ -177,56 +178,23 @@ BOOST_AUTO_TEST_CASE (film_metadata_test)
        BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp"));
        BOOST_CHECK_EQUAL (g->trim_start(), 42);
        BOOST_CHECK_EQUAL (g->trim_end(), 99);
-       BOOST_CHECK_EQUAL (g->dcp_ab(), true);
+       BOOST_CHECK_EQUAL (g->ab(), true);
        
        g->write_metadata ();
        BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0);
 }
 
-BOOST_AUTO_TEST_CASE (stream_test)
-{
-       FFmpegAudioStream a ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (a.id(), 4);
-       BOOST_CHECK_EQUAL (a.sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (a.channel_layout(), 1);
-       BOOST_CHECK_EQUAL (a.name(), "hello there world");
-       BOOST_CHECK_EQUAL (a.to_string(), "ffmpeg 4 44100 1 hello there world");
-
-       SndfileStream e ("external 44100 1", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (e.sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (e.channel_layout(), 1);
-       BOOST_CHECK_EQUAL (e.to_string(), "external 44100 1");
-
-       SubtitleStream s ("5 a b c", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (s.id(), 5);
-       BOOST_CHECK_EQUAL (s.name(), "a b c");
-
-       shared_ptr<AudioStream> ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1));
-       shared_ptr<FFmpegAudioStream> cff = dynamic_pointer_cast<FFmpegAudioStream> (ff);
-       BOOST_CHECK (cff);
-       BOOST_CHECK_EQUAL (cff->id(), 4);
-       BOOST_CHECK_EQUAL (cff->sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (cff->channel_layout(), 1);
-       BOOST_CHECK_EQUAL (cff->name(), "hello there world");
-       BOOST_CHECK_EQUAL (cff->to_string(), "ffmpeg 4 44100 1 hello there world");
-
-       shared_ptr<AudioStream> fe = audio_stream_factory ("external 44100 1", boost::optional<int> (1));
-       BOOST_CHECK_EQUAL (fe->sample_rate(), 44100);
-       BOOST_CHECK_EQUAL (fe->channel_layout(), 1);
-       BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1");
-}
-
 BOOST_AUTO_TEST_CASE (format_test)
 {
        Format::setup_formats ();
        
        Format const * f = Format::from_nickname ("Flat");
        BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
+//     BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 185);
        
        f = Format::from_nickname ("Scope");
        BOOST_CHECK (f);
-       BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
+//     BOOST_CHECK_EQUAL (f->ratio_as_integer(shared_ptr<const Film> ()), 239);
 }
 
 /* Test VariableFormat-based scaling of content */
@@ -381,17 +349,6 @@ BOOST_AUTO_TEST_CASE (md5_digest_test)
        BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError);
 }
 
-BOOST_AUTO_TEST_CASE (paths_test)
-{
-       shared_ptr<Film> f = new_test_film ("paths_test");
-       f->set_directory ("build/test/a/b/c/d/e");
-
-       f->_content = "/foo/bar/baz";
-       BOOST_CHECK_EQUAL (f->content_path(), "/foo/bar/baz");
-       f->_content = "foo/bar/baz";
-       BOOST_CHECK_EQUAL (f->content_path(), "build/test/a/b/c/d/e/foo/bar/baz");
-}
-
 void
 do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
 {
@@ -483,7 +440,7 @@ BOOST_AUTO_TEST_CASE (make_dcp_test)
 {
        shared_ptr<Film> film = new_test_film ("make_dcp_test");
        film->set_name ("test_film2");
-       film->set_content ("../../../test/test.mp4");
+//     film->set_content ("../../../test/test.mp4");
        film->set_format (Format::from_nickname ("Flat"));
        film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
        film->make_dcp ();
@@ -513,8 +470,8 @@ BOOST_AUTO_TEST_CASE (make_dcp_with_range_test)
 {
        shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test");
        film->set_name ("test_film3");
-       film->set_content ("../../../test/test.mp4");
-       film->examine_content ();
+//     film->set_content ("../../../test/test.mp4");
+//     film->examine_content ();
        film->set_format (Format::from_nickname ("Flat"));
        film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
        film->set_trim_end (42);
@@ -675,44 +632,44 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
        Config::instance()->set_allowed_dcp_frame_rates (afr);
 
        shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test");
-       f->set_source_frame_rate (24);
+//     f->set_source_frame_rate (24);
        f->set_dcp_frame_rate (24);
 
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
 
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000);
 
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000);
 
-       f->set_source_frame_rate (23.976);
+//     f->set_source_frame_rate (23.976);
        f->set_dcp_frame_rate (best_dcp_frame_rate (23.976));
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
 
-       f->set_source_frame_rate (29.97);
+//     f->set_source_frame_rate (29.97);
        f->set_dcp_frame_rate (best_dcp_frame_rate (29.97));
        BOOST_CHECK_EQUAL (f->dcp_frame_rate (), 30);
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952);
 
-       f->set_source_frame_rate (25);
+//     f->set_source_frame_rate (25);
        f->set_dcp_frame_rate (24);
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
 
-       f->set_source_frame_rate (25);
+//     f->set_source_frame_rate (25);
        f->set_dcp_frame_rate (24);
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
        BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000);
 
        /* Check some out-there conversions (not the best) */
        
-       f->set_source_frame_rate (14.99);
+//     f->set_source_frame_rate (14.99);
        f->set_dcp_frame_rate (25);
-       f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
+//     f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
        /* The FrameRateConversion within target_audio_sample_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
diff --git a/wscript b/wscript
index 10c06a74d72c27ae8acb7680f12ebd5bf480282e..3f3ae2019b9e0857879219ac4c37dfa7f4540e38 100644 (file)
--- a/wscript
+++ b/wscript
@@ -56,6 +56,7 @@ def configure(conf):
 
     if not conf.options.static:
         conf.check_cfg(package = 'libdcp', atleast_version = '0.41', args = '--cflags --libs', uselib_store = 'DCP', mandatory = True)
+        conf.check_cfg(package = 'libcxml', atleast_version = '0.01', args = '--cflags --libs', uselib_store = 'CXML', mandatory = True)
         conf.check_cfg(package = 'libavformat', args = '--cflags --libs', uselib_store = 'AVFORMAT', mandatory = True)
         conf.check_cfg(package = 'libavfilter', args = '--cflags --libs', uselib_store = 'AVFILTER', mandatory = True)
         conf.check_cfg(package = 'libavcodec', args = '--cflags --libs', uselib_store = 'AVCODEC', mandatory = True)
@@ -72,6 +73,8 @@ def configure(conf):
         conf.env.HAVE_DCP = 1
         conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
         conf.env.LIB_DCP = ['glibmm-2.4', 'xml++-2.6', 'ssl', 'crypto', 'bz2']
+        conf.env.HAVE_CXML = 1
+        conf.env.STLIB_CXML = ['cxml']
         conf.env.HAVE_AVFORMAT = 1
         conf.env.STLIB_AVFORMAT = ['avformat']
         conf.env.HAVE_AVFILTER = 1