2 Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #include "ffmpeg_content.h"
21 #include "ffmpeg_examiner.h"
22 #include "ffmpeg_subtitle_stream.h"
23 #include "ffmpeg_audio_stream.h"
24 #include "compose.hpp"
30 #include "exceptions.h"
31 #include "frame_rate_change.h"
32 #include "safe_stringstream.h"
33 #include "raw_convert.h"
34 #include <libcxml/cxml.h>
36 #include <libavformat/avformat.h>
41 #define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
48 using boost::shared_ptr;
49 using boost::dynamic_pointer_cast;
51 int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
52 int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
53 int const FFmpegContentProperty::AUDIO_STREAMS = 102;
54 int const FFmpegContentProperty::AUDIO_STREAM = 103;
55 int const FFmpegContentProperty::FILTERS = 104;
57 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
61 , SubtitleContent (f, p)
66 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version, list<string>& notes)
68 , VideoContent (f, node, version)
69 , AudioContent (f, node)
70 , SubtitleContent (f, node, version)
72 list<cxml::NodePtr> c = node->node_children ("SubtitleStream");
73 for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
74 _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
75 if ((*i)->optional_number_child<int> ("Selected")) {
76 _subtitle_stream = _subtitle_streams.back ();
80 c = node->node_children ("AudioStream");
81 for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
82 _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i, version)));
83 if ((*i)->optional_number_child<int> ("Selected")) {
84 _audio_stream = _audio_streams.back ();
88 c = node->node_children ("Filter");
89 for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
90 Filter const * f = Filter::from_id ((*i)->content ());
92 _filters.push_back (f);
94 notes.push_back (String::compose (_("DCP-o-matic no longer supports the `%1' filter, so it has been turned off."), (*i)->content()));
98 _first_video = node->optional_number_child<double> ("FirstVideo");
101 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, vector<boost::shared_ptr<Content> > c)
103 , VideoContent (f, c)
104 , AudioContent (f, c)
105 , SubtitleContent (f, c)
107 shared_ptr<FFmpegContent> ref = dynamic_pointer_cast<FFmpegContent> (c[0]);
108 DCPOMATIC_ASSERT (ref);
110 for (size_t i = 0; i < c.size(); ++i) {
111 shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c[i]);
112 if (fc->use_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) {
113 throw JoinError (_("Content to be joined must use the same subtitle stream."));
116 if (*(fc->_audio_stream.get()) != *(ref->_audio_stream.get())) {
117 throw JoinError (_("Content to be joined must use the same audio stream."));
121 _subtitle_streams = ref->subtitle_streams ();
122 _subtitle_stream = ref->subtitle_stream ();
123 _audio_streams = ref->audio_streams ();
124 _audio_stream = ref->audio_stream ();
125 _first_video = ref->_first_video;
129 FFmpegContent::as_xml (xmlpp::Node* node) const
131 node->add_child("Type")->add_child_text ("FFmpeg");
132 Content::as_xml (node);
133 VideoContent::as_xml (node);
134 AudioContent::as_xml (node);
135 SubtitleContent::as_xml (node);
137 boost::mutex::scoped_lock lm (_mutex);
139 for (vector<shared_ptr<FFmpegSubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
140 xmlpp::Node* t = node->add_child("SubtitleStream");
141 if (_subtitle_stream && *i == _subtitle_stream) {
142 t->add_child("Selected")->add_child_text("1");
147 for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
148 xmlpp::Node* t = node->add_child("AudioStream");
149 if (_audio_stream && *i == _audio_stream) {
150 t->add_child("Selected")->add_child_text("1");
155 for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
156 node->add_child("Filter")->add_child_text ((*i)->id ());
160 node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get().get()));
165 FFmpegContent::examine (shared_ptr<Job> job)
167 job->set_progress_unknown ();
169 Content::examine (job);
171 shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this (), job));
172 take_from_video_examiner (examiner);
174 shared_ptr<const Film> film = _film.lock ();
175 DCPOMATIC_ASSERT (film);
178 boost::mutex::scoped_lock lm (_mutex);
180 _subtitle_streams = examiner->subtitle_streams ();
181 if (!_subtitle_streams.empty ()) {
182 _subtitle_stream = _subtitle_streams.front ();
185 _audio_streams = examiner->audio_streams ();
186 if (!_audio_streams.empty ()) {
187 _audio_stream = _audio_streams.front ();
190 _first_video = examiner->first_video ();
193 signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
194 signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
195 signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
196 signal_changed (FFmpegContentProperty::AUDIO_STREAM);
197 signal_changed (AudioContentProperty::AUDIO_CHANNELS);
201 FFmpegContent::summary () const
203 /* Get the string() here so that the name does not have quotes around it */
204 return String::compose (_("%1 [movie]"), path_summary ());
208 FFmpegContent::technical_summary () const
212 as = _audio_stream->technical_summary ();
216 if (_subtitle_stream) {
217 ss = _subtitle_stream->technical_summary ();
220 string filt = Filter::ffmpeg_string (_filters);
222 return Content::technical_summary() + " - "
223 + VideoContent::technical_summary() + " - "
224 + AudioContent::technical_summary() + " - "
226 "ffmpeg: audio %1, subtitle %2, filters %3", as, ss, filt
231 FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s)
234 boost::mutex::scoped_lock lm (_mutex);
235 _subtitle_stream = s;
238 signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
242 FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
245 boost::mutex::scoped_lock lm (_mutex);
249 signal_changed (FFmpegContentProperty::AUDIO_STREAM);
253 FFmpegContent::audio_length () const
255 if (!audio_stream ()) {
259 /* We're talking about the content's audio length here, at the content's frame
260 rate. We assume it's the same as the video's length, and we can just convert
261 using the content's rates.
263 return (video_length () / video_frame_rate ()) * audio_frame_rate ();
267 FFmpegContent::audio_channels () const
269 boost::mutex::scoped_lock lm (_mutex);
271 if (!_audio_stream) {
275 return _audio_stream->channels ();
279 FFmpegContent::audio_frame_rate () const
281 boost::mutex::scoped_lock lm (_mutex);
283 if (!_audio_stream) {
287 return _audio_stream->frame_rate ();
291 operator== (FFmpegStream const & a, FFmpegStream const & b)
293 return a._id == b._id;
297 operator!= (FFmpegStream const & a, FFmpegStream const & b)
299 return a._id != b._id;
303 FFmpegContent::full_length () const
305 shared_ptr<const Film> film = _film.lock ();
306 DCPOMATIC_ASSERT (film);
307 FrameRateChange const frc (video_frame_rate (), film->video_frame_rate ());
308 return DCPTime::from_frames (rint (video_length_after_3d_combine() * frc.factor()), film->video_frame_rate());
312 FFmpegContent::audio_mapping () const
314 boost::mutex::scoped_lock lm (_mutex);
316 if (!_audio_stream) {
317 return AudioMapping ();
320 return _audio_stream->mapping ();
324 FFmpegContent::set_filters (vector<Filter const *> const & filters)
327 boost::mutex::scoped_lock lm (_mutex);
331 signal_changed (FFmpegContentProperty::FILTERS);
335 FFmpegContent::set_audio_mapping (AudioMapping m)
337 audio_stream()->set_mapping (m);
338 AudioContent::set_audio_mapping (m);
342 FFmpegContent::identifier () const
346 s << VideoContent::identifier();
348 boost::mutex::scoped_lock lm (_mutex);
350 if (_subtitle_stream) {
351 s << "_" << _subtitle_stream->identifier ();
354 for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
355 s << "_" << (*i)->id ();
361 boost::filesystem::path
362 FFmpegContent::audio_analysis_path () const
364 shared_ptr<const Film> film = _film.lock ();
366 return boost::filesystem::path ();
369 /* We need to include the stream ID in this path so that we get different
370 analyses for each stream.
373 boost::filesystem::path p = AudioContent::audio_analysis_path ();
374 if (audio_stream ()) {
375 p = p.string() + "_" + audio_stream()->identifier ();
380 list<ContentTimePeriod>
381 FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const
383 shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream ();
385 return list<ContentTimePeriod> ();
388 return stream->subtitles_during (period, starting);
392 FFmpegContent::has_subtitles () const
394 return !subtitle_streams().empty ();
398 FFmpegContent::set_default_colour_conversion ()
400 dcp::Size const s = video_size ();
402 boost::mutex::scoped_lock lm (_mutex);
404 if (s.width < 1080) {
405 _colour_conversion = PresetColourConversion::from_id ("rec601").conversion;
407 _colour_conversion = PresetColourConversion::from_id ("rec709").conversion;