Add total-playlist loop option; move state of content list into the Playlist.
[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         Changed ();
108 }
109
110 /** @return Length of our audio */
111 ContentAudioFrame
112 Playlist::audio_length () const
113 {
114         ContentAudioFrame len = 0;
115
116         switch (_audio_from) {
117         case AUDIO_FFMPEG:
118                 /* FFmpeg content is sequential */
119                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
120                         len += (*i)->audio_length ();
121                 }
122                 break;
123         case AUDIO_SNDFILE:
124                 /* Sndfile content is simultaneous */
125                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
126                         len = max (len, (*i)->audio_length ());
127                 }
128                 break;
129         }
130
131         return len * _loop;
132 }
133
134 /** @return number of audio channels */
135 int
136 Playlist::audio_channels () const
137 {
138         int channels = 0;
139         
140         switch (_audio_from) {
141         case AUDIO_FFMPEG:
142                 /* FFmpeg audio is sequential, so use the maximum channel count */
143                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
144                         channels = max (channels, (*i)->audio_channels ());
145                 }
146                 break;
147         case AUDIO_SNDFILE:
148                 /* Sndfile audio is simultaneous, so it's the sum of the channel counts */
149                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
150                         channels += (*i)->audio_channels ();
151                 }
152                 break;
153         }
154
155         return channels;
156 }
157
158 int
159 Playlist::audio_frame_rate () const
160 {
161         if (_audio.empty ()) {
162                 return 0;
163         }
164
165         /* XXX: assuming that all content has the same rate */
166         return _audio.front()->audio_frame_rate ();
167 }
168
169 float
170 Playlist::video_frame_rate () const
171 {
172         if (_video.empty ()) {
173                 return 0;
174         }
175         
176         /* XXX: assuming all the same */
177         return _video.front()->video_frame_rate ();
178 }
179
180 libdcp::Size
181 Playlist::video_size () const
182 {
183         if (_video.empty ()) {
184                 return libdcp::Size ();
185         }
186
187         /* XXX: assuming all the same */
188         return _video.front()->video_size ();
189 }
190
191 ContentVideoFrame
192 Playlist::video_length () const
193 {
194         ContentVideoFrame len = 0;
195         for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
196                 len += (*i)->video_length ();
197         }
198         
199         return len * _loop;
200 }
201
202 bool
203 Playlist::has_audio () const
204 {
205         return !_audio.empty ();
206 }
207
208 void
209 Playlist::content_changed (weak_ptr<Content> c, int p)
210 {
211         ContentChanged (c, p);
212 }
213
214 AudioMapping
215 Playlist::default_audio_mapping () const
216 {
217         AudioMapping m;
218         if (_audio.empty ()) {
219                 return m;
220         }
221
222         switch (_audio_from) {
223         case AUDIO_FFMPEG:
224         {
225                 /* XXX: assumes all the same */
226                 if (_audio.front()->audio_channels() == 1) {
227                         /* Map mono sources to centre */
228                         m.add (AudioMapping::Channel (_audio.front(), 0), libdcp::CENTRE);
229                 } else {
230                         int const N = min (_audio.front()->audio_channels (), MAX_AUDIO_CHANNELS);
231                         /* Otherwise just start with a 1:1 mapping */
232                         for (int i = 0; i < N; ++i) {
233                                 m.add (AudioMapping::Channel (_audio.front(), i), (libdcp::Channel) i);
234                         }
235                 }
236                 break;
237         }
238
239         case AUDIO_SNDFILE:
240         {
241                 int n = 0;
242                 for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
243                         for (int j = 0; j < (*i)->audio_channels(); ++j) {
244                                 m.add (AudioMapping::Channel (*i, j), (libdcp::Channel) n);
245                                 ++n;
246                                 if (n >= MAX_AUDIO_CHANNELS) {
247                                         break;
248                                 }
249                         }
250                         if (n >= MAX_AUDIO_CHANNELS) {
251                                 break;
252                         }
253                 }
254                 break;
255         }
256         }
257
258         return m;
259 }
260
261 string
262 Playlist::audio_digest () const
263 {
264         string t;
265         
266         for (list<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) {
267                 t += (*i)->digest ();
268
269                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
270                 if (fc) {
271                         t += lexical_cast<string> (fc->audio_stream()->id);
272                 }
273         }
274
275         t += lexical_cast<string> (_loop);
276
277         return md5_digest (t.c_str(), t.length());
278 }
279
280 string
281 Playlist::video_digest () const
282 {
283         string t;
284         
285         for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) {
286                 t += (*i)->digest ();
287                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
288                 if (fc && fc->subtitle_stream()) {
289                         t += fc->subtitle_stream()->id;
290                 }
291         }
292
293         t += lexical_cast<string> (_loop);
294
295         return md5_digest (t.c_str(), t.length());
296 }
297
298 ContentVideoFrame
299 Playlist::content_length () const
300 {
301         float const vfr = video_frame_rate() > 0 ? video_frame_rate() : 24;
302         int const afr = audio_frame_rate() > 0 ? audio_frame_rate() : 48000;
303
304         return max (
305                 video_length(),
306                 ContentVideoFrame (audio_length() * vfr / afr)
307                 );
308 }
309
310 void
311 Playlist::set_from_xml (shared_ptr<const cxml::Node> node)
312 {
313         list<shared_ptr<cxml::Node> > c = node->node_children ("Content");
314         for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
315
316                 string const type = (*i)->string_child ("Type");
317                 boost::shared_ptr<Content> c;
318                 
319                 if (type == "FFmpeg") {
320                         c.reset (new FFmpegContent (*i));
321                 } else if (type == "ImageMagick") {
322                         c.reset (new ImageMagickContent (*i));
323                 } else if (type == "Sndfile") {
324                         c.reset (new SndfileContent (*i));
325                 }
326
327                 _content.push_back (c);
328         }
329
330         _loop = node->number_child<int> ("Loop");
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 }
349
350 void
351 Playlist::remove (shared_ptr<Content> c)
352 {
353         ContentList::iterator i = find (_content.begin(), _content.end(), c);
354         if (i != _content.end ()) {
355                 _content.erase (i);
356         }
357
358         setup ();
359 }
360
361 void
362 Playlist::move_earlier (shared_ptr<Content> c)
363 {
364         ContentList::iterator i = find (_content.begin(), _content.end(), c);
365         if (i == _content.begin () || i == _content.end()) {
366                 return;
367         }
368
369         ContentList::iterator j = i;
370         --j;
371
372         swap (*i, *j);
373
374         setup ();
375 }
376
377 void
378 Playlist::move_later (shared_ptr<Content> c)
379 {
380         ContentList::iterator i = find (_content.begin(), _content.end(), c);
381         if (i == _content.end()) {
382                 return;
383         }
384
385         ContentList::iterator j = i;
386         ++j;
387         if (j == _content.end ()) {
388                 return;
389         }
390
391         swap (*i, *j);
392
393         setup ();
394 }
395
396 void
397 Playlist::set_loop (int l)
398 {
399         _loop = l;
400         Changed ();
401 }
402                 
403 shared_ptr<FFmpegContent>
404 Playlist::ffmpeg () const
405 {
406         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
407                 shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
408                 if (fc) {
409                         return fc;
410                 }
411         }
412
413         return shared_ptr<FFmpegContent> ();
414 }