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