XML metadata and some other bits.
[dcpomatic.git] / src / lib / ffmpeg_content.cc
1 #include <libcxml/cxml.h>
2 #include "ffmpeg_content.h"
3 #include "ffmpeg_decoder.h"
4 #include "compose.hpp"
5 #include "job.h"
6 #include "util.h"
7 #include "log.h"
8
9 #include "i18n.h"
10
11 using std::string;
12 using std::vector;
13 using std::list;
14 using boost::shared_ptr;
15 using boost::lexical_cast;
16
17 int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
18 int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
19 int const FFmpegContentProperty::AUDIO_STREAMS = 102;
20 int const FFmpegContentProperty::AUDIO_STREAM = 103;
21
22 FFmpegContent::FFmpegContent (boost::filesystem::path f)
23         : Content (f)
24         , VideoContent (f)
25         , AudioContent (f)
26 {
27
28 }
29
30 FFmpegContent::FFmpegContent (shared_ptr<const cxml::Node> node)
31         : Content (node)
32         , VideoContent (node)
33         , AudioContent (node)
34 {
35         list<shared_ptr<cxml::Node> > c = node->node_children ("SubtitleStream");
36         for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
37                 _subtitle_streams.push_back (FFmpegSubtitleStream (*i));
38                 if ((*i)->optional_number_child<int> ("Selected")) {
39                         _subtitle_stream = _subtitle_streams.back ();
40                 }
41         }
42
43         c = node->node_children ("AudioStream");
44         for (list<shared_ptr<cxml::Node> >::const_iterator i = c.begin(); i != c.end(); ++i) {
45                 _audio_streams.push_back (FFmpegAudioStream (*i));
46                 if ((*i)->optional_number_child<int> ("Selected")) {
47                         _audio_stream = _audio_streams.back ();
48                 }
49         }
50 }
51
52 void
53 FFmpegContent::as_xml (xmlpp::Node* node) const
54 {
55         node->add_child("Type")->add_child_text ("FFmpeg");
56         Content::as_xml (node);
57         VideoContent::as_xml (node);
58
59         boost::mutex::scoped_lock lm (_mutex);
60
61         for (vector<FFmpegSubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) {
62                 xmlpp::Node* t = node->add_child("SubtitleStream");
63                 if (_subtitle_stream && *i == _subtitle_stream.get()) {
64                         t->add_child("Selected")->add_child_text("1");
65                 }
66                 i->as_xml (t);
67         }
68
69         for (vector<FFmpegAudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
70                 xmlpp::Node* t = node->add_child("AudioStream");
71                 if (_audio_stream && *i == _audio_stream.get()) {
72                         t->add_child("Selected")->add_child_text("1");
73                 }
74                 i->as_xml (t);
75         }
76 }
77
78 void
79 FFmpegContent::examine (shared_ptr<Film> film, shared_ptr<Job> job, bool quick)
80 {
81         job->descend (0.5);
82         Content::examine (film, job, quick);
83         job->ascend ();
84
85         job->set_progress_unknown ();
86
87         shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false, true));
88
89         ContentVideoFrame video_length = 0;
90         if (quick) {
91                 video_length = decoder->video_length ();
92                 film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ()));
93         } else {
94                 while (!decoder->pass ()) {
95                         /* keep going */
96                 }
97
98                 video_length = decoder->video_frame ();
99                 film->log()->log (String::compose ("Video length examined as %1 frames", decoder->video_frame ()));
100         }
101
102         {
103                 boost::mutex::scoped_lock lm (_mutex);
104
105                 _video_length = video_length;
106
107                 _subtitle_streams = decoder->subtitle_streams ();
108                 if (!_subtitle_streams.empty ()) {
109                         _subtitle_stream = _subtitle_streams.front ();
110                 }
111                 
112                 _audio_streams = decoder->audio_streams ();
113                 if (!_audio_streams.empty ()) {
114                         _audio_stream = _audio_streams.front ();
115                 }
116         }
117
118         take_from_video_decoder (decoder);
119
120         Changed (VideoContentProperty::VIDEO_LENGTH);
121         Changed (FFmpegContentProperty::SUBTITLE_STREAMS);
122         Changed (FFmpegContentProperty::SUBTITLE_STREAM);
123         Changed (FFmpegContentProperty::AUDIO_STREAMS);
124         Changed (FFmpegContentProperty::AUDIO_STREAM);
125 }
126
127 string
128 FFmpegContent::summary () const
129 {
130         return String::compose (_("Movie: %1"), file().filename ());
131 }
132
133 void
134 FFmpegContent::set_subtitle_stream (FFmpegSubtitleStream s)
135 {
136         {
137                 boost::mutex::scoped_lock lm (_mutex);
138                 _subtitle_stream = s;
139         }
140
141         Changed (FFmpegContentProperty::SUBTITLE_STREAM);
142 }
143
144 void
145 FFmpegContent::set_audio_stream (FFmpegAudioStream s)
146 {
147         {
148                 boost::mutex::scoped_lock lm (_mutex);
149                 _audio_stream = s;
150         }
151
152         Changed (FFmpegContentProperty::AUDIO_STREAM);
153 }
154
155 ContentAudioFrame
156 FFmpegContent::audio_length () const
157 {
158         if (!_audio_stream) {
159                 return 0;
160         }
161         
162         return video_frames_to_audio_frames (_video_length, audio_frame_rate(), video_frame_rate());
163 }
164
165 int
166 FFmpegContent::audio_channels () const
167 {
168         if (!_audio_stream) {
169                 return 0;
170         }
171
172         return _audio_stream->channels ();
173 }
174
175 int
176 FFmpegContent::audio_frame_rate () const
177 {
178         if (!_audio_stream) {
179                 return 0;
180         }
181
182         return _audio_stream->frame_rate;
183 }
184
185 int64_t
186 FFmpegContent::audio_channel_layout () const
187 {
188         if (!_audio_stream) {
189                 return 0;
190         }
191
192         return _audio_stream->channel_layout;
193 }
194         
195 bool
196 operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
197 {
198         return a.id == b.id;
199 }
200
201 bool
202 operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
203 {
204         return a.id == b.id;
205 }
206
207 FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
208 {
209         name = node->string_child ("Name");
210         id = node->number_child<int> ("Id");
211         frame_rate = node->number_child<int> ("FrameRate");
212         channel_layout = node->number_child<int64_t> ("ChannelLayout");
213 }
214
215 void
216 FFmpegAudioStream::as_xml (xmlpp::Node* root) const
217 {
218         root->add_child("Name")->add_child_text (name);
219         root->add_child("Id")->add_child_text (lexical_cast<string> (id));
220         root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
221         root->add_child("ChannelLayout")->add_child_text (lexical_cast<string> (channel_layout));
222 }
223
224 /** Construct a SubtitleStream from a value returned from to_string().
225  *  @param t String returned from to_string().
226  *  @param v State file version.
227  */
228 FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
229 {
230         name = node->string_child ("Name");
231         id = node->number_child<int> ("Id");
232 }
233
234 void
235 FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
236 {
237         root->add_child("Name")->add_child_text (name);
238         root->add_child("Id")->add_child_text (lexical_cast<string> (id));
239 }