Fix initial display of viewer image.
[dcpomatic.git] / src / lib / playlist.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 <boost/shared_ptr.hpp>
22 #include <boost/lexical_cast.hpp>
23 #include "playlist.h"
24 #include "sndfile_content.h"
25 #include "sndfile_decoder.h"
26 #include "video_content.h"
27 #include "ffmpeg_decoder.h"
28 #include "ffmpeg_content.h"
29 #include "imagemagick_decoder.h"
30 #include "imagemagick_content.h"
31 #include "job.h"
32
33 using std::list;
34 using std::cout;
35 using std::vector;
36 using std::min;
37 using std::max;
38 using std::string;
39 using boost::shared_ptr;
40 using boost::weak_ptr;
41 using boost::dynamic_pointer_cast;
42 using boost::lexical_cast;
43
44 Playlist::Playlist ()
45         : _audio_from (AUDIO_FFMPEG)
46         , _loop (1)
47 {
48
49 }
50
51 Playlist::Playlist (shared_ptr<const Playlist> other)
52         : _audio_from (other->_audio_from)
53         , _loop (other->_loop)
54 {
55         for (ContentList::const_iterator i = other->_content.begin(); i != other->_content.end(); ++i) {
56                 _content.push_back ((*i)->clone ());
57         }
58
59         setup ();
60 }
61
62 void
63 Playlist::setup ()
64 {
65         _audio_from = AUDIO_FFMPEG;
66
67         _video.clear ();
68         _audio.clear ();
69
70         for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
71                 i->disconnect ();
72         }
73         
74         _content_connections.clear ();
75
76         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
77
78                 /* Video is video */
79                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
80                 if (vc) {
81                         _video.push_back (vc);
82                 }
83
84                 /* FFmpegContent is audio if we are doing AUDIO_FFMPEG */
85                 shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
86                 if (fc && _audio_from == AUDIO_FFMPEG) {
87                         _audio.push_back (fc);
88                 }
89
90                 /* SndfileContent trumps FFmpegContent for audio */
91                 shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (*i);
92                 if (sc) {
93                         if (_audio_from == AUDIO_FFMPEG) {
94                                 /* This is our fist SndfileContent; clear any FFmpegContent and
95                                    say that we are using Sndfile.
96                                 */
97                                 _audio.clear ();
98                                 _audio_from = AUDIO_SNDFILE;
99                         }
100                         
101                         _audio.push_back (sc);
102                 }
103
104                 _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2)));
105         }
106 }
107
108 /** @return Length of our audio */
109 ContentAudioFrame
110 Playlist::audio_length () const
111 {
112         ContentAudioFrame len = 0;
113
114         switch (_audio_from) {
115         case AUDIO_FFMPEG:
116                 /* FFmpeg content is sequential */
117                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
118                         len += (*i)->audio_length ();
119                 }
120                 break;
121         case AUDIO_SNDFILE:
122                 /* Sndfile content is simultaneous */
123                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
124                         len = max (len, (*i)->audio_length ());
125                 }
126                 break;
127         }
128
129         return len * _loop;
130 }
131
132 /** @return number of audio channels */
133 int
134 Playlist::audio_channels () const
135 {
136         int channels = 0;
137         
138         switch (_audio_from) {
139         case AUDIO_FFMPEG:
140                 /* FFmpeg audio is sequential, so use the maximum channel count */
141                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
142                         channels = max (channels, (*i)->audio_channels ());
143                 }
144                 break;
145         case AUDIO_SNDFILE:
146                 /* Sndfile audio is simultaneous, so it's the sum of the channel counts */
147                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
148                         channels += (*i)->audio_channels ();
149                 }
150                 break;
151         }
152
153         return channels;
154 }
155
156 int
157 Playlist::audio_frame_rate () const
158 {
159         if (_audio.empty ()) {
160                 return 0;
161         }
162
163         /* XXX: assuming that all content has the same rate */
164         return _audio.front()->audio_frame_rate ();
165 }
166
167 float
168 Playlist::video_frame_rate () const
169 {
170         if (_video.empty ()) {
171                 return 0;
172         }
173         
174         /* XXX: assuming all the same */
175         return _video.front()->video_frame_rate ();
176 }
177
178 libdcp::Size
179 Playlist::video_size () const
180 {
181         if (_video.empty ()) {
182                 return libdcp::Size ();
183         }
184
185         /* XXX: assuming all the same */
186         return _video.front()->video_size ();
187 }
188
189 ContentVideoFrame
190 Playlist::video_length () const
191 {
192         ContentVideoFrame len = 0;
193         for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
194                 len += (*i)->video_length ();
195         }
196         
197         return len * _loop;
198 }
199
200 bool
201 Playlist::has_audio () const
202 {
203         return !_audio.empty ();
204 }
205
206 void
207 Playlist::content_changed (weak_ptr<Content> c, int p)
208 {
209         ContentChanged (c, p);
210 }
211
212 AudioMapping
213 Playlist::default_audio_mapping () const
214 {
215         AudioMapping m;
216         if (_audio.empty ()) {
217                 return m;
218         }
219
220         switch (_audio_from) {
221         case AUDIO_FFMPEG:
222         {
223                 /* XXX: assumes all the same */
224                 if (_audio.front()->audio_channels() == 1) {
225                         /* Map mono sources to centre */
226                         m.add (AudioMapping::Channel (_audio.front(), 0), libdcp::CENTRE);
227                 } else {
228                         int const N = min (_audio.front()->audio_channels (), MAX_AUDIO_CHANNELS);
229                         /* Otherwise just start with a 1:1 mapping */
230                         for (int i = 0; i < N; ++i) {
231                                 m.add (AudioMapping::Channel (_audio.front(), i), (libdcp::Channel) i);
232                         }
233                 }
234                 break;
235         }
236
237         case AUDIO_SNDFILE:
238         {
239                 int n = 0;
240                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
241                         for (int j = 0; j < (*i)->audio_channels(); ++j) {
242                                 m.add (AudioMapping::Channel (*i, j), (libdcp::Channel) n);
243                                 ++n;
244                                 if (n >= MAX_AUDIO_CHANNELS) {
245                                         break;
246                                 }
247                         }
248                         if (n >= MAX_AUDIO_CHANNELS) {
249                                 break;
250                         }
251                 }
252                 break;
253         }
254         }
255
256         return m;
257 }
258
259 string
260 Playlist::audio_digest () const
261 {
262         string t;
263         
264         for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
265                 t += (*i)->digest ();
266
267                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
268                 if (fc) {
269                         t += lexical_cast<string> (fc->audio_stream()->id);
270                 }
271         }
272
273         t += lexical_cast<string> (_loop);
274
275         return md5_digest (t.c_str(), t.length());
276 }
277
278 string
279 Playlist::video_digest () const
280 {
281         string t;
282         
283         for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
284                 t += (*i)->digest ();
285                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
286                 if (fc && fc->subtitle_stream()) {
287                         t += fc->subtitle_stream()->id;
288                 }
289         }
290
291         t += lexical_cast<string> (_loop);
292
293         return md5_digest (t.c_str(), t.length());
294 }
295
296 ContentVideoFrame
297 Playlist::content_length () const
298 {
299         float const vfr = video_frame_rate() > 0 ? video_frame_rate() : 24;
300         int const afr = audio_frame_rate() > 0 ? audio_frame_rate() : 48000;
301
302         return max (
303                 video_length(),
304                 ContentVideoFrame (audio_length() * vfr / afr)
305                 );
306 }
307
308 void
309 Playlist::set_from_xml (shared_ptr<const cxml::Node> node)
310 {
311         list<shared_ptr<cxml::Node> > c = node->node_children ("Content");
312         for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
313
314                 string const type = (*i)->string_child ("Type");
315                 boost::shared_ptr<Content> c;
316                 
317                 if (type == "FFmpeg") {
318                         c.reset (new FFmpegContent (*i));
319                 } else if (type == "ImageMagick") {
320                         c.reset (new ImageMagickContent (*i));
321                 } else if (type == "Sndfile") {
322                         c.reset (new SndfileContent (*i));
323                 }
324
325                 _content.push_back (c);
326         }
327
328         _loop = node->number_child<int> ("Loop");
329
330         setup ();
331 }
332
333 void
334 Playlist::as_xml (xmlpp::Node* node)
335 {
336         for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
337                 (*i)->as_xml (node->add_child ("Content"));
338         }
339
340         node->add_child("Loop")->add_child_text(lexical_cast<string> (_loop));
341 }
342
343 void
344 Playlist::add (shared_ptr<Content> c)
345 {
346         _content.push_back (c);
347         setup ();
348         Changed ();
349 }
350
351 void
352 Playlist::remove (shared_ptr<Content> c)
353 {
354         ContentList::iterator i = find (_content.begin(), _content.end(), c);
355         if (i != _content.end ()) {
356                 _content.erase (i);
357         }
358
359         setup ();
360         Changed ();
361 }
362
363 void
364 Playlist::move_earlier (shared_ptr<Content> c)
365 {
366         ContentList::iterator i = find (_content.begin(), _content.end(), c);
367         if (i == _content.begin () || i == _content.end()) {
368                 return;
369         }
370
371         ContentList::iterator j = i;
372         --j;
373
374         swap (*i, *j);
375
376         setup ();
377         Changed ();
378 }
379
380 void
381 Playlist::move_later (shared_ptr<Content> c)
382 {
383         ContentList::iterator i = find (_content.begin(), _content.end(), c);
384         if (i == _content.end()) {
385                 return;
386         }
387
388         ContentList::iterator j = i;
389         ++j;
390         if (j == _content.end ()) {
391                 return;
392         }
393
394         swap (*i, *j);
395
396         setup ();
397         Changed ();
398 }
399
400 void
401 Playlist::set_loop (int l)
402 {
403         _loop = l;
404         Changed ();
405 }
406                 
407 shared_ptr<FFmpegContent>
408 Playlist::ffmpeg () const
409 {
410         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
411                 shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
412                 if (fc) {
413                         return fc;
414                 }
415         }
416
417         return shared_ptr<FFmpegContent> ();
418 }