Untested use of Frame for video/audio content lengths.
[dcpomatic.git] / src / lib / ffmpeg_content.cc
1 /*
2     Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
3
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.
8
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.
13
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.
17
18 */
19
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"
25 #include "job.h"
26 #include "util.h"
27 #include "filter.h"
28 #include "film.h"
29 #include "log.h"
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>
35 extern "C" {
36 #include <libavformat/avformat.h>
37 }
38
39 #include "i18n.h"
40
41 #define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
42
43 using std::string;
44 using std::vector;
45 using std::list;
46 using std::cout;
47 using std::pair;
48 using boost::shared_ptr;
49 using boost::dynamic_pointer_cast;
50
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;
56
57 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
58         : Content (f, p)
59         , VideoContent (f, p)
60         , AudioContent (f, p)
61         , SubtitleContent (f, p)
62 {
63
64 }
65
66 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version, list<string>& notes)
67         : Content (f, node)
68         , VideoContent (f, node, version)
69         , AudioContent (f, node)
70         , SubtitleContent (f, node, version)
71 {
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 ();
77                 }
78         }
79
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 ();
85                 }
86         }
87
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 ());
91                 if (f) {
92                         _filters.push_back (f);
93                 } else {
94                         notes.push_back (String::compose (_("DCP-o-matic no longer supports the `%1' filter, so it has been turned off."), (*i)->content()));
95                 }
96         }
97
98         _first_video = node->optional_number_child<double> ("FirstVideo");
99 }
100
101 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, vector<boost::shared_ptr<Content> > c)
102         : Content (f, c)
103         , VideoContent (f, c)
104         , AudioContent (f, c)
105         , SubtitleContent (f, c)
106 {
107         shared_ptr<FFmpegContent> ref = dynamic_pointer_cast<FFmpegContent> (c[0]);
108         DCPOMATIC_ASSERT (ref);
109
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."));
114                 }
115
116                 if (*(fc->_audio_stream.get()) != *(ref->_audio_stream.get())) {
117                         throw JoinError (_("Content to be joined must use the same audio stream."));
118                 }
119         }
120
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;
126 }
127
128 void
129 FFmpegContent::as_xml (xmlpp::Node* node) const
130 {
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);
136
137         boost::mutex::scoped_lock lm (_mutex);
138
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");
143                 }
144                 (*i)->as_xml (t);
145         }
146
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");
151                 }
152                 (*i)->as_xml (t);
153         }
154
155         for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
156                 node->add_child("Filter")->add_child_text ((*i)->id ());
157         }
158
159         if (_first_video) {
160                 node->add_child("FirstVideo")->add_child_text (raw_convert<string> (_first_video.get().get()));
161         }
162 }
163
164 void
165 FFmpegContent::examine (shared_ptr<Job> job)
166 {
167         job->set_progress_unknown ();
168
169         Content::examine (job);
170
171         shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this (), job));
172         take_from_video_examiner (examiner);
173
174         shared_ptr<const Film> film = _film.lock ();
175         DCPOMATIC_ASSERT (film);
176
177         {
178                 boost::mutex::scoped_lock lm (_mutex);
179
180                 _subtitle_streams = examiner->subtitle_streams ();
181                 if (!_subtitle_streams.empty ()) {
182                         _subtitle_stream = _subtitle_streams.front ();
183                 }
184                 
185                 _audio_streams = examiner->audio_streams ();
186                 if (!_audio_streams.empty ()) {
187                         _audio_stream = _audio_streams.front ();
188                 }
189
190                 _first_video = examiner->first_video ();
191         }
192
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);
198 }
199
200 string
201 FFmpegContent::summary () const
202 {
203         /* Get the string() here so that the name does not have quotes around it */
204         return String::compose (_("%1 [movie]"), path_summary ());
205 }
206
207 string
208 FFmpegContent::technical_summary () const
209 {
210         string as = "none";
211         if (_audio_stream) {
212                 as = _audio_stream->technical_summary ();
213         }
214
215         string ss = "none";
216         if (_subtitle_stream) {
217                 ss = _subtitle_stream->technical_summary ();
218         }
219
220         string filt = Filter::ffmpeg_string (_filters);
221         
222         return Content::technical_summary() + " - "
223                 + VideoContent::technical_summary() + " - "
224                 + AudioContent::technical_summary() + " - "
225                 + String::compose (
226                         "ffmpeg: audio %1, subtitle %2, filters %3", as, ss, filt
227                         );
228 }
229
230 void
231 FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s)
232 {
233         {
234                 boost::mutex::scoped_lock lm (_mutex);
235                 _subtitle_stream = s;
236         }
237
238         signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
239 }
240
241 void
242 FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
243 {
244         {
245                 boost::mutex::scoped_lock lm (_mutex);
246                 _audio_stream = s;
247         }
248
249         signal_changed (FFmpegContentProperty::AUDIO_STREAM);
250 }
251
252 Frame
253 FFmpegContent::audio_length () const
254 {
255         if (!audio_stream ()) {
256                 return 0;
257         }
258
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.
262         */
263         return (video_length () / video_frame_rate ()) * audio_frame_rate ();
264 }
265
266 int
267 FFmpegContent::audio_channels () const
268 {
269         boost::mutex::scoped_lock lm (_mutex);
270         
271         if (!_audio_stream) {
272                 return 0;
273         }
274
275         return _audio_stream->channels ();
276 }
277
278 int
279 FFmpegContent::audio_frame_rate () const
280 {
281         boost::mutex::scoped_lock lm (_mutex);
282
283         if (!_audio_stream) {
284                 return 0;
285         }
286
287         return _audio_stream->frame_rate ();
288 }
289
290 bool
291 operator== (FFmpegStream const & a, FFmpegStream const & b)
292 {
293         return a._id == b._id;
294 }
295
296 bool
297 operator!= (FFmpegStream const & a, FFmpegStream const & b)
298 {
299         return a._id != b._id;
300 }
301
302 DCPTime
303 FFmpegContent::full_length () const
304 {
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());
309 }
310
311 AudioMapping
312 FFmpegContent::audio_mapping () const
313 {
314         boost::mutex::scoped_lock lm (_mutex);
315
316         if (!_audio_stream) {
317                 return AudioMapping ();
318         }
319
320         return _audio_stream->mapping ();
321 }
322
323 void
324 FFmpegContent::set_filters (vector<Filter const *> const & filters)
325 {
326         {
327                 boost::mutex::scoped_lock lm (_mutex);
328                 _filters = filters;
329         }
330
331         signal_changed (FFmpegContentProperty::FILTERS);
332 }
333
334 void
335 FFmpegContent::set_audio_mapping (AudioMapping m)
336 {
337         audio_stream()->set_mapping (m);
338         AudioContent::set_audio_mapping (m);
339 }
340
341 string
342 FFmpegContent::identifier () const
343 {
344         SafeStringStream s;
345
346         s << VideoContent::identifier();
347
348         boost::mutex::scoped_lock lm (_mutex);
349
350         if (_subtitle_stream) {
351                 s << "_" << _subtitle_stream->identifier ();
352         }
353
354         for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
355                 s << "_" << (*i)->id ();
356         }
357
358         return s.str ();
359 }
360
361 boost::filesystem::path
362 FFmpegContent::audio_analysis_path () const
363 {
364         shared_ptr<const Film> film = _film.lock ();
365         if (!film) {
366                 return boost::filesystem::path ();
367         }
368
369         /* We need to include the stream ID in this path so that we get different
370            analyses for each stream.
371         */
372
373         boost::filesystem::path p = AudioContent::audio_analysis_path ();
374         if (audio_stream ()) {
375                 p = p.string() + "_" + audio_stream()->identifier ();
376         }
377         return p;
378 }
379
380 list<ContentTimePeriod>
381 FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const
382 {
383         shared_ptr<FFmpegSubtitleStream> stream = subtitle_stream ();
384         if (!stream) {
385                 return list<ContentTimePeriod> ();
386         }
387
388         return stream->subtitles_during (period, starting);
389 }
390
391 bool
392 FFmpegContent::has_subtitles () const
393 {
394         return !subtitle_streams().empty ();
395 }
396
397 void
398 FFmpegContent::set_default_colour_conversion ()
399 {
400         dcp::Size const s = video_size ();
401
402         boost::mutex::scoped_lock lm (_mutex);
403
404         if (s.width < 1080) {
405                 _colour_conversion = PresetColourConversion::from_id ("rec601").conversion;
406         } else {
407                 _colour_conversion = PresetColourConversion::from_id ("rec709").conversion;
408         }
409 }
410
411