Merge master.
[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 "playlist.h"
23 #include "sndfile_content.h"
24 #include "sndfile_decoder.h"
25 #include "video_content.h"
26 #include "ffmpeg_decoder.h"
27 #include "ffmpeg_content.h"
28 #include "image_decoder.h"
29 #include "content_factory.h"
30 #include "job.h"
31 #include "config.h"
32 #include "util.h"
33
34 #include "i18n.h"
35
36 using std::list;
37 using std::cout;
38 using std::vector;
39 using std::min;
40 using std::max;
41 using std::string;
42 using std::stringstream;
43 using std::pair;
44 using boost::optional;
45 using boost::shared_ptr;
46 using boost::weak_ptr;
47 using boost::dynamic_pointer_cast;
48
49 Playlist::Playlist ()
50         : _sequence_video (true)
51         , _sequencing_video (false)
52 {
53
54 }
55
56 Playlist::~Playlist ()
57 {
58         _content.clear ();
59         reconnect ();
60 }
61
62 void
63 Playlist::content_changed (weak_ptr<Content> content, int property, bool frequent)
64 {
65         if (property == ContentProperty::LENGTH || property == VideoContentProperty::VIDEO_FRAME_TYPE) {
66                 maybe_sequence_video ();
67         }
68         
69         ContentChanged (content, property, frequent);
70 }
71
72 void
73 Playlist::maybe_sequence_video ()
74 {
75         if (!_sequence_video || _sequencing_video) {
76                 return;
77         }
78         
79         _sequencing_video = true;
80         
81         ContentList cl = _content;
82         DCPTime next_left;
83         DCPTime next_right;
84         for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
85                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i);
86                 if (!vc) {
87                         continue;
88                 }
89                 
90                 if (vc->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
91                         vc->set_position (next_right);
92                         next_right = vc->end() + DCPTime::delta ();
93                 } else {
94                         vc->set_position (next_left);
95                         next_left = vc->end() + DCPTime::delta ();
96                 }
97         }
98
99         /* This won't change order, so it does not need a sort */
100         
101         _sequencing_video = false;
102 }
103
104 string
105 Playlist::video_identifier () const
106 {
107         string t;
108         
109         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
110                 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
111                 if (vc) {
112                         t += vc->identifier ();
113                 }
114         }
115
116         return md5_digest (t.c_str(), t.length());
117 }
118
119 /** @param node <Playlist> node */
120 void
121 Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
122 {
123         list<cxml::NodePtr> c = node->node_children ("Content");
124         for (list<cxml::NodePtr>::iterator i = c.begin(); i != c.end(); ++i) {
125                 _content.push_back (content_factory (film, *i, version, notes));
126         }
127
128         sort (_content.begin(), _content.end(), ContentSorter ());
129
130         reconnect ();
131 }
132
133 /** @param node <Playlist> node */
134 void
135 Playlist::as_xml (xmlpp::Node* node)
136 {
137         for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
138                 (*i)->as_xml (node->add_child ("Content"));
139         }
140 }
141
142 void
143 Playlist::add (shared_ptr<Content> c)
144 {
145         _content.push_back (c);
146         sort (_content.begin(), _content.end(), ContentSorter ());
147         reconnect ();
148         Changed ();
149 }
150
151 void
152 Playlist::remove (shared_ptr<Content> c)
153 {
154         ContentList::iterator i = _content.begin ();
155         while (i != _content.end() && *i != c) {
156                 ++i;
157         }
158         
159         if (i != _content.end ()) {
160                 _content.erase (i);
161                 Changed ();
162         }
163
164         /* This won't change order, so it does not need a sort */
165 }
166
167 void
168 Playlist::remove (ContentList c)
169 {
170         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
171                 ContentList::iterator j = _content.begin ();
172                 while (j != _content.end() && *j != *i) {
173                         ++j;
174                 }
175         
176                 if (j != _content.end ()) {
177                         _content.erase (j);
178                 }
179         }
180
181         /* This won't change order, so it does not need a sort */
182         
183         Changed ();
184 }
185
186 bool
187 Playlist::has_subtitles () const
188 {
189         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
190                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
191                 if (fc && !fc->subtitle_streams().empty()) {
192                         return true;
193                 }
194         }
195
196         return false;
197 }
198
199 class FrameRateCandidate
200 {
201 public:
202         FrameRateCandidate (float source_, int dcp_)
203                 : source (source_)
204                 , dcp (dcp_)
205         {}
206
207         float source;
208         int dcp;
209 };
210
211 int
212 Playlist::best_dcp_frame_rate () const
213 {
214         list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
215
216         /* Work out what rates we could manage, including those achieved by using skip / repeat. */
217         list<FrameRateCandidate> candidates;
218
219         /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
220         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
221                 candidates.push_back (FrameRateCandidate (*i, *i));
222         }
223
224         /* Then the skip/repeat ones */
225         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
226                 candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
227                 candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
228         }
229
230         /* Pick the best one */
231         float error = std::numeric_limits<float>::max ();
232         optional<FrameRateCandidate> best;
233         list<FrameRateCandidate>::iterator i = candidates.begin();
234         while (i != candidates.end()) {
235
236                 float this_error = 0;
237                 for (ContentList::const_iterator j = _content.begin(); j != _content.end(); ++j) {
238                         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
239                         if (!vc) {
240                                 continue;
241                         }
242
243                         /* Use the largest difference between DCP and source as the "error" */
244                         this_error = max (this_error, float (fabs (i->source - vc->video_frame_rate ())));
245                 }
246
247                 if (this_error < error) {
248                         error = this_error;
249                         best = *i;
250                 }
251
252                 ++i;
253         }
254
255         if (!best) {
256                 return 24;
257         }
258         
259         return best->dcp;
260 }
261
262 DCPTime
263 Playlist::length () const
264 {
265         DCPTime len;
266         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
267                 len = max (len, (*i)->end() + DCPTime::delta ());
268         }
269
270         return len;
271 }
272
273 void
274 Playlist::reconnect ()
275 {
276         for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
277                 i->disconnect ();
278         }
279
280         _content_connections.clear ();
281                 
282         for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
283                 _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2, _3)));
284         }
285 }
286
287 DCPTime
288 Playlist::video_end () const
289 {
290         DCPTime end;
291         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
292                 if (dynamic_pointer_cast<const VideoContent> (*i)) {
293                         end = max (end, (*i)->end ());
294                 }
295         }
296
297         return end;
298 }
299
300 FrameRateChange
301 Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
302 {
303         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
304                 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
305                 if (!vc) {
306                         break;
307                 }
308
309                 if (vc->position() >= t && t < vc->end()) {
310                         return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
311                 }
312         }
313
314         return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
315 }
316
317 void
318 Playlist::set_sequence_video (bool s)
319 {
320         _sequence_video = s;
321 }
322
323 bool
324 ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
325 {
326         return a->position() < b->position();
327 }
328
329 /** @return content in an undefined order */
330 ContentList
331 Playlist::content () const
332 {
333         return _content;
334 }
335
336 void
337 Playlist::repeat (ContentList c, int n)
338 {
339         pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
340         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
341                 range.first = min (range.first, (*i)->position ());
342                 range.second = max (range.second, (*i)->position ());
343                 range.first = min (range.first, (*i)->end ());
344                 range.second = max (range.second, (*i)->end ());
345         }
346
347         DCPTime pos = range.second;
348         for (int i = 0; i < n; ++i) {
349                 for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
350                         shared_ptr<Content> copy = (*i)->clone ();
351                         copy->set_position (pos + copy->position() - range.first);
352                         _content.push_back (copy);
353                 }
354                 pos += range.second - range.first;
355         }
356
357         sort (_content.begin(), _content.end(), ContentSorter ());
358         
359         reconnect ();
360         Changed ();
361 }
362
363 void
364 Playlist::move_earlier (shared_ptr<Content> c)
365 {
366         sort (_content.begin(), _content.end(), ContentSorter ());
367         
368         ContentList::iterator previous = _content.end ();
369         ContentList::iterator i = _content.begin();
370         while (i != _content.end() && *i != c) {
371                 previous = i;
372                 ++i;
373         }
374
375         assert (i != _content.end ());
376         if (previous == _content.end ()) {
377                 return;
378         }
379         
380         DCPTime const p = (*previous)->position ();
381         (*previous)->set_position (p + c->length_after_trim ());
382         c->set_position (p);
383         sort (_content.begin(), _content.end(), ContentSorter ());
384         
385         Changed ();
386 }
387
388 void
389 Playlist::move_later (shared_ptr<Content> c)
390 {
391         sort (_content.begin(), _content.end(), ContentSorter ());
392         
393         ContentList::iterator i = _content.begin();
394         while (i != _content.end() && *i != c) {
395                 ++i;
396         }
397
398         assert (i != _content.end ());
399
400         ContentList::iterator next = i;
401         ++next;
402
403         if (next == _content.end ()) {
404                 return;
405         }
406
407         DCPTime const p = (*next)->position ();
408         (*next)->set_position (c->position ());
409         c->set_position (p + c->length_after_trim ());
410         sort (_content.begin(), _content.end(), ContentSorter ());
411         
412         Changed ();
413 }