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