f0df1519393e96c423fa1f935b1357e6fe5cf7d9
[dcpomatic.git] / src / lib / ffmpeg_content.cc
1 /*
2     Copyright (C) 2013 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 <libcxml/cxml.h>
21 #include "ffmpeg_content.h"
22 #include "ffmpeg_decoder.h"
23 #include "compose.hpp"
24 #include "job.h"
25 #include "util.h"
26 #include "log.h"
27
28 #include "i18n.h"
29
30 using std::string;
31 using std::stringstream;
32 using std::vector;
33 using std::list;
34 using std::cout;
35 using boost::shared_ptr;
36 using boost::lexical_cast;
37
38 int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
39 int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
40 int const FFmpegContentProperty::AUDIO_STREAMS = 102;
41 int const FFmpegContentProperty::AUDIO_STREAM = 103;
42
43 FFmpegContent::FFmpegContent (boost::filesystem::path f)
44         : Content (f)
45         , VideoContent (f)
46         , AudioContent (f)
47 {
48
49 }
50
51 FFmpegContent::FFmpegContent (shared_ptr<const cxml::Node> node)
52         : Content (node)
53         , VideoContent (node)
54         , AudioContent (node)
55 {
56         list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
57         for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
58                 _subtitle_streams.push_back (FFmpegSubtitleStream (*i));
59                 if ((*i)->optional_number_child<int> ("Selected")) {
60                         _subtitle_stream = _subtitle_streams.back ();
61                 }
62         }
63
64         c = node->node_children ("AudioStream");
65         for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
66                 _audio_streams.push_back (FFmpegAudioStream (*i));
67                 if ((*i)->optional_number_child<int> ("Selected")) {
68                         _audio_stream = _audio_streams.back ();
69                 }
70         }
71 }
72
73 FFmpegContent::FFmpegContent (FFmpegContent const & o)
74         : Content (o)
75         , VideoContent (o)
76         , AudioContent (o)
77         , _subtitle_streams (o._subtitle_streams)
78         , _subtitle_stream (o._subtitle_stream)
79         , _audio_streams (o._audio_streams)
80         , _audio_stream (o._audio_stream)
81 {
82
83 }
84
85 void
86 FFmpegContent::as_xml (xmlpp::Node* node) const
87 {
88         node->add_child("Type")->add_child_text ("FFmpeg");
89         Content::as_xml (node);
90         VideoContent::as_xml (node);
91
92         boost::mutex::scoped_lock lm (_mutex);
93
94         for (vector<FFmpegSubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
95                 xmlpp::Node* t = node->add_child("SubtitleStream");
96                 if (_subtitle_stream && *i == _subtitle_stream.get()) {
97                         t->add_child("Selected")->add_child_text("1");
98                 }
99                 i->as_xml (t);
100         }
101
102         for (vector<FFmpegAudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
103                 xmlpp::Node* t = node->add_child("AudioStream");
104                 if (_audio_stream && *i == _audio_stream.get()) {
105                         t->add_child("Selected")->add_child_text("1");
106                 }
107                 i->as_xml (t);
108         }
109 }
110
111 void
112 FFmpegContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
113 {
114         job->set_progress_unknown ();
115
116         Content::examine (film, job, quick);
117
118         shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false));
119
120         ContentVideoFrame video_length = 0;
121         if (quick) {
122                 video_length = decoder->video_length ();
123                 film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ()));
124         } else {
125                 while (!decoder->pass ()) {
126                         /* keep going */
127                 }
128
129                 video_length = decoder->video_frame ();
130                 film->log()->log (String::compose ("Video length examined as %1 frames", decoder->video_frame ()));
131         }
132
133         {
134                 boost::mutex::scoped_lock lm (_mutex);
135
136                 _video_length = video_length;
137
138                 _subtitle_streams = decoder->subtitle_streams ();
139                 if (!_subtitle_streams.empty ()) {
140                         _subtitle_stream = _subtitle_streams.front ();
141                 }
142                 
143                 _audio_streams = decoder->audio_streams ();
144                 if (!_audio_streams.empty ()) {
145                         _audio_stream = _audio_streams.front ();
146                 }
147         }
148
149         take_from_video_decoder (decoder);
150
151         signal_changed (VideoContentProperty::VIDEO_LENGTH);
152         signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
153         signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
154         signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
155         signal_changed (FFmpegContentProperty::AUDIO_STREAM);
156         signal_changed (AudioContentProperty::AUDIO_CHANNELS);
157 }
158
159 string
160 FFmpegContent::summary () const
161 {
162         return String::compose (_("Movie: %1"), file().filename().string());
163 }
164
165 string
166 FFmpegContent::information () const
167 {
168         if (video_length() == 0 || video_frame_rate() == 0) {
169                 return "";
170         }
171         
172         stringstream s;
173         
174         s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n";
175         s << VideoContent::information ();
176
177         return s.str ();
178 }
179
180 void
181 FFmpegContent::set_subtitle_stream (FFmpegSubtitleStream s)
182 {
183         {
184                 boost::mutex::scoped_lock lm (_mutex);
185                 _subtitle_stream = s;
186         }
187
188         signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
189 }
190
191 void
192 FFmpegContent::set_audio_stream (FFmpegAudioStream s)
193 {
194         {
195                 boost::mutex::scoped_lock lm (_mutex);
196                 _audio_stream = s;
197         }
198
199         signal_changed (FFmpegContentProperty::AUDIO_STREAM);
200 }
201
202 ContentAudioFrame
203 FFmpegContent::audio_length () const
204 {
205         if (!_audio_stream) {
206                 return 0;
207         }
208         
209         return video_frames_to_audio_frames (_video_length, content_audio_frame_rate(), video_frame_rate());
210 }
211
212 int
213 FFmpegContent::audio_channels () const
214 {
215         if (!_audio_stream) {
216                 return 0;
217         }
218
219         return _audio_stream->channels;
220 }
221
222 int
223 FFmpegContent::content_audio_frame_rate () const
224 {
225         if (!_audio_stream) {
226                 return 0;
227         }
228
229         return _audio_stream->frame_rate;
230 }
231
232 int
233 FFmpegContent::output_audio_frame_rate (shared_ptr<const Film> film) const
234 {
235         /* Resample to a DCI-approved sample rate */
236         double t = dcp_audio_frame_rate (content_audio_frame_rate ());
237
238         FrameRateConversion frc (video_frame_rate(), film->dcp_video_frame_rate());
239
240         /* Compensate if the DCP is being run at a different frame rate
241            to the source; that is, if the video is run such that it will
242            look different in the DCP compared to the source (slower or faster).
243            skip/repeat doesn't come into effect here.
244         */
245
246         if (frc.change_speed) {
247                 t *= video_frame_rate() * frc.factor() / film->dcp_video_frame_rate();
248         }
249
250         return rint (t);
251 }
252
253 bool
254 operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
255 {
256         return a.id == b.id;
257 }
258
259 bool
260 operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
261 {
262         return a.id == b.id;
263 }
264
265 FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
266 {
267         name = node->string_child ("Name");
268         id = node->number_child<int> ("Id");
269         frame_rate = node->number_child<int> ("FrameRate");
270         channels = node->number_child<int64_t> ("Channels");
271 }
272
273 void
274 FFmpegAudioStream::as_xml (xmlpp::Node* root) const
275 {
276         root->add_child("Name")->add_child_text (name);
277         root->add_child("Id")->add_child_text (lexical_cast<string> (id));
278         root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
279         root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
280 }
281
282 /** Construct a SubtitleStream from a value returned from to_string().
283  *  @param t String returned from to_string().
284  *  @param v State file version.
285  */
286 FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
287 {
288         name = node->string_child ("Name");
289         id = node->number_child<int> ("Id");
290 }
291
292 void
293 FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
294 {
295         root->add_child("Name")->add_child_text (name);
296         root->add_child("Id")->add_child_text (lexical_cast<string> (id));
297 }
298
299 shared_ptr<Content>
300 FFmpegContent::clone () const
301 {
302         return shared_ptr<Content> (new FFmpegContent (*this));
303 }
304
305 Time
306 FFmpegContent::length (shared_ptr<const Film> film) const
307 {
308         FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ());
309         return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate ();
310 }