Remove Film pointer from clone().
[dcpomatic.git] / src / lib / playlist.cc
1 /*
2     Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "playlist.h"
22 #include "video_content.h"
23 #include "text_content.h"
24 #include "ffmpeg_decoder.h"
25 #include "ffmpeg_content.h"
26 #include "image_decoder.h"
27 #include "audio_content.h"
28 #include "content_factory.h"
29 #include "dcp_content.h"
30 #include "job.h"
31 #include "config.h"
32 #include "util.h"
33 #include "digester.h"
34 #include "compose.hpp"
35 #include <libcxml/cxml.h>
36 #include <libxml++/libxml++.h>
37 #include <boost/shared_ptr.hpp>
38 #include <boost/foreach.hpp>
39 #include <iostream>
40
41 #include "i18n.h"
42
43 using std::list;
44 using std::cout;
45 using std::vector;
46 using std::min;
47 using std::max;
48 using std::string;
49 using std::pair;
50 using boost::optional;
51 using boost::shared_ptr;
52 using boost::weak_ptr;
53 using boost::dynamic_pointer_cast;
54
55 Playlist::Playlist ()
56         : _sequence (true)
57         , _sequencing (false)
58 {
59
60 }
61
62 Playlist::~Playlist ()
63 {
64         _content.clear ();
65         disconnect ();
66 }
67
68 void
69 Playlist::content_change (weak_ptr<const Film> weak_film, ChangeType type, weak_ptr<Content> content, int property, bool frequent)
70 {
71         shared_ptr<const Film> film = weak_film.lock ();
72         DCPOMATIC_ASSERT (film);
73
74         if (type == CHANGE_TYPE_DONE) {
75                 if (
76                         property == ContentProperty::TRIM_START ||
77                         property == ContentProperty::TRIM_END ||
78                         property == ContentProperty::LENGTH ||
79                         property == VideoContentProperty::FRAME_TYPE
80                         ) {
81                         /* Don't respond to position changes here, as:
82                            - sequencing after earlier/later changes is handled by move_earlier/move_later
83                            - any other position changes will be timeline drags which should not result in content
84                            being sequenced.
85                         */
86                         maybe_sequence (film);
87                 }
88
89                 if (
90                         property == ContentProperty::POSITION ||
91                         property == ContentProperty::LENGTH ||
92                         property == ContentProperty::TRIM_START ||
93                         property == ContentProperty::TRIM_END) {
94
95                         ContentList old = _content;
96                         sort (_content.begin(), _content.end(), ContentSorter ());
97                         if (_content != old) {
98                                 OrderChanged ();
99                         }
100                 }
101         }
102
103         ContentChange (type, content, property, frequent);
104 }
105
106 void
107 Playlist::maybe_sequence (shared_ptr<const Film> film)
108 {
109         if (!_sequence || _sequencing) {
110                 return;
111         }
112
113         _sequencing = true;
114
115         /* Keep track of the content that we've set the position of so that we don't
116            do it twice.
117         */
118         ContentList placed;
119
120         /* Video */
121
122         DCPTime next_left;
123         DCPTime next_right;
124         BOOST_FOREACH (shared_ptr<Content> i, _content) {
125                 if (!i->video) {
126                         continue;
127                 }
128
129                 if (i->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
130                         i->set_position (film, next_right);
131                         next_right = i->end(film);
132                 } else {
133                         i->set_position (film, next_left);
134                         next_left = i->end(film);
135                 }
136
137                 placed.push_back (i);
138         }
139
140         /* Captions */
141
142         DCPTime next;
143         BOOST_FOREACH (shared_ptr<Content> i, _content) {
144                 if (i->text.empty() || find (placed.begin(), placed.end(), i) != placed.end()) {
145                         continue;
146                 }
147
148                 i->set_position (film, next);
149                 next = i->end(film);
150         }
151
152
153         /* This won't change order, so it does not need a sort */
154
155         _sequencing = false;
156 }
157
158 string
159 Playlist::video_identifier () const
160 {
161         string t;
162
163         BOOST_FOREACH (shared_ptr<const Content> i, _content) {
164                 bool burn = false;
165                 BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
166                         if (j->burn()) {
167                                 burn = true;
168                         }
169                 }
170                 if (i->video || burn) {
171                         t += i->identifier ();
172                 }
173         }
174
175         Digester digester;
176         digester.add (t.c_str(), t.length());
177         return digester.get ();
178 }
179
180 /** @param film Film that this Playlist is for.
181  *  @param node &lt;Playlist&gt; node.
182  *  @param version Metadata version number.
183  *  @param notes Output notes about that happened.
184  */
185 void
186 Playlist::set_from_xml (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version, list<string>& notes)
187 {
188         BOOST_FOREACH (cxml::NodePtr i, node->node_children ("Content")) {
189                 shared_ptr<Content> content = content_factory (i, version, notes);
190
191                 /* See if this content should be nudged to start on a video frame */
192                 DCPTime const old_pos = content->position();
193                 content->set_position(film, old_pos);
194                 if (old_pos != content->position()) {
195                         string note = _("Your project contains video content that was not aligned to a frame boundary.");
196                         note += "  ";
197                         if (old_pos < content->position()) {
198                                 note += String::compose(
199                                         _("The file %1 has been moved %2 milliseconds later."),
200                                         content->path_summary(), DCPTime(content->position() - old_pos).seconds() * 1000
201                                         );
202                         } else {
203                                 note += String::compose(
204                                         _("The file %1 has been moved %2 milliseconds earlier."),
205                                         content->path_summary(), DCPTime(content->position() - old_pos).seconds() * 1000
206                                         );
207                         }
208                         notes.push_back (note);
209                 }
210
211                 /* ...or have a start trim which is an integer number of frames */
212                 ContentTime const old_trim = content->trim_start();
213                 content->set_trim_start(old_trim);
214                 if (old_trim != content->trim_start()) {
215                         string note = _("Your project contains video content whose trim was not aligned to a frame boundary.");
216                         note += "  ";
217                         if (old_trim < content->trim_start()) {
218                                 note += String::compose(
219                                         _("The file %1 has been trimmed by %2 milliseconds more."),
220                                         content->path_summary(), ContentTime(content->trim_start() - old_trim).seconds() * 1000
221                                         );
222                         } else {
223                                 note += String::compose(
224                                         _("The file %1 has been trimmed by %2 milliseconds less."),
225                                         content->path_summary(), ContentTime(old_trim - content->trim_start()).seconds() * 1000
226                                         );
227                         }
228                         notes.push_back (note);
229                 }
230
231                 _content.push_back (content);
232         }
233
234         /* This shouldn't be necessary but better safe than sorry (there could be old files) */
235         sort (_content.begin(), _content.end(), ContentSorter ());
236
237         reconnect (film);
238 }
239
240 /** @param node &lt;Playlist&gt; node.
241  *  @param with_content_paths true to include &lt;Path&gt; nodes in &lt;Content&gt; nodes, false to omit them.
242  */
243 void
244 Playlist::as_xml (xmlpp::Node* node, bool with_content_paths)
245 {
246         BOOST_FOREACH (shared_ptr<Content> i, _content) {
247                 i->as_xml (node->add_child ("Content"), with_content_paths);
248         }
249 }
250
251 void
252 Playlist::add (shared_ptr<const Film> film, shared_ptr<Content> c)
253 {
254         Change (CHANGE_TYPE_PENDING);
255         _content.push_back (c);
256         sort (_content.begin(), _content.end(), ContentSorter ());
257         reconnect (film);
258         Change (CHANGE_TYPE_DONE);
259 }
260
261 void
262 Playlist::remove (shared_ptr<Content> c)
263 {
264         Change (CHANGE_TYPE_PENDING);
265
266         ContentList::iterator i = _content.begin ();
267         while (i != _content.end() && *i != c) {
268                 ++i;
269         }
270
271         if (i != _content.end ()) {
272                 _content.erase (i);
273                 Change (CHANGE_TYPE_DONE);
274         } else {
275                 Change (CHANGE_TYPE_CANCELLED);
276         }
277
278         /* This won't change order, so it does not need a sort */
279 }
280
281 void
282 Playlist::remove (ContentList c)
283 {
284         Change (CHANGE_TYPE_PENDING);
285
286         BOOST_FOREACH (shared_ptr<Content> i, c) {
287                 ContentList::iterator j = _content.begin ();
288                 while (j != _content.end() && *j != i) {
289                         ++j;
290                 }
291
292                 if (j != _content.end ()) {
293                         _content.erase (j);
294                 }
295         }
296
297         /* This won't change order, so it does not need a sort */
298
299         Change (CHANGE_TYPE_DONE);
300 }
301
302 class FrameRateCandidate
303 {
304 public:
305         FrameRateCandidate (float source_, int dcp_)
306                 : source (source_)
307                 , dcp (dcp_)
308         {}
309
310         float source;
311         int dcp;
312 };
313
314 int
315 Playlist::best_video_frame_rate () const
316 {
317         list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
318
319         /* Work out what rates we could manage, including those achieved by using skip / repeat */
320         list<FrameRateCandidate> candidates;
321
322         /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
323         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
324                 candidates.push_back (FrameRateCandidate (*i, *i));
325         }
326
327         /* Then the skip/repeat ones */
328         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
329                 candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
330                 candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
331         }
332
333         /* Pick the best one */
334         float error = std::numeric_limits<float>::max ();
335         optional<FrameRateCandidate> best;
336         list<FrameRateCandidate>::iterator i = candidates.begin();
337         while (i != candidates.end()) {
338
339                 float this_error = 0;
340                 BOOST_FOREACH (shared_ptr<Content> j, _content) {
341                         if (!j->video || !j->video_frame_rate()) {
342                                 continue;
343                         }
344
345                         /* Best error for this content; we could use the content as-is or double its rate */
346                         float best_error = min (
347                                 float (fabs (i->source - j->video_frame_rate().get())),
348                                 float (fabs (i->source - j->video_frame_rate().get() * 2))
349                                 );
350
351                         /* Use the largest difference between DCP and source as the "error" */
352                         this_error = max (this_error, best_error);
353                 }
354
355                 if (this_error < error) {
356                         error = this_error;
357                         best = *i;
358                 }
359
360                 ++i;
361         }
362
363         if (!best) {
364                 return 24;
365         }
366
367         return best->dcp;
368 }
369
370 /** @return length of the playlist from time 0 to the last thing on the playlist */
371 DCPTime
372 Playlist::length (shared_ptr<const Film> film) const
373 {
374         DCPTime len;
375         BOOST_FOREACH (shared_ptr<const Content> i, _content) {
376                 len = max (len, i->end(film));
377         }
378
379         return len;
380 }
381
382 /** @return position of the first thing on the playlist, if it's not empty */
383 optional<DCPTime>
384 Playlist::start () const
385 {
386         if (_content.empty ()) {
387                 return optional<DCPTime> ();
388         }
389
390         DCPTime start = DCPTime::max ();
391         BOOST_FOREACH (shared_ptr<Content> i, _content) {
392                 start = min (start, i->position ());
393         }
394
395         return start;
396 }
397
398 void
399 Playlist::disconnect ()
400 {
401         for (list<boost::signals2::connection>::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) {
402                 i->disconnect ();
403         }
404
405         _content_connections.clear ();
406 }
407
408 void
409 Playlist::reconnect (shared_ptr<const Film> film)
410 {
411         disconnect ();
412
413         BOOST_FOREACH (shared_ptr<Content> i, _content) {
414                 _content_connections.push_back (i->Change.connect(boost::bind(&Playlist::content_change, this, film, _1, _2, _3, _4)));
415         }
416 }
417
418 DCPTime
419 Playlist::video_end (shared_ptr<const Film> film) const
420 {
421         DCPTime end;
422         BOOST_FOREACH (shared_ptr<Content> i, _content) {
423                 if (i->video) {
424                         end = max (end, i->end(film));
425                 }
426         }
427
428         return end;
429 }
430
431 DCPTime
432 Playlist::text_end (shared_ptr<const Film> film) const
433 {
434         DCPTime end;
435         BOOST_FOREACH (shared_ptr<Content> i, _content) {
436                 if (!i->text.empty ()) {
437                         end = max (end, i->end(film));
438                 }
439         }
440
441         return end;
442 }
443
444 FrameRateChange
445 Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
446 {
447         for (ContentList::const_reverse_iterator i = _content.rbegin(); i != _content.rend(); ++i) {
448                 if (!(*i)->video) {
449                         continue;
450                 }
451
452                 if ((*i)->position() <= t) {
453                         /* This is the first piece of content (going backwards...) that starts before t,
454                            so it's the active one.
455                         */
456                         if ((*i)->video_frame_rate ()) {
457                                 /* This content specified a rate, so use it */
458                                 return FrameRateChange ((*i)->video_frame_rate().get(), dcp_video_frame_rate);
459                         } else {
460                                 /* No specified rate so just use the DCP one */
461                                 return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
462                         }
463                 }
464         }
465
466         return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
467 }
468
469 void
470 Playlist::set_sequence (bool s)
471 {
472         _sequence = s;
473 }
474
475 bool
476 ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
477 {
478         if (a->position() != b->position()) {
479                 return a->position() < b->position();
480         }
481
482         /* Put video before audio if they start at the same time */
483         if (a->video && !b->video) {
484                 return true;
485         } else if (!a->video && b->video) {
486                 return false;
487         }
488
489         /* Last resort */
490         return a->digest() < b->digest();
491 }
492
493 /** @return content in ascending order of position */
494 ContentList
495 Playlist::content () const
496 {
497         return _content;
498 }
499
500 void
501 Playlist::repeat (shared_ptr<const Film> film, ContentList c, int n)
502 {
503         pair<DCPTime, DCPTime> range (DCPTime::max (), DCPTime ());
504         BOOST_FOREACH (shared_ptr<Content> i, c) {
505                 range.first = min (range.first, i->position ());
506                 range.second = max (range.second, i->position ());
507                 range.first = min (range.first, i->end(film));
508                 range.second = max (range.second, i->end(film));
509         }
510
511         Change (CHANGE_TYPE_PENDING);
512
513         DCPTime pos = range.second;
514         for (int i = 0; i < n; ++i) {
515                 BOOST_FOREACH (shared_ptr<Content> j, c) {
516                         shared_ptr<Content> copy = j->clone ();
517                         copy->set_position (film, pos + copy->position() - range.first);
518                         _content.push_back (copy);
519                 }
520                 pos += range.second - range.first;
521         }
522
523         sort (_content.begin(), _content.end(), ContentSorter ());
524
525         reconnect (film);
526         Change (CHANGE_TYPE_DONE);
527 }
528
529 void
530 Playlist::move_earlier (shared_ptr<const Film> film, shared_ptr<Content> c)
531 {
532         ContentList::iterator previous = _content.end ();
533         ContentList::iterator i = _content.begin();
534         while (i != _content.end() && *i != c) {
535                 previous = i;
536                 ++i;
537         }
538
539         DCPOMATIC_ASSERT (i != _content.end ());
540         if (previous == _content.end ()) {
541                 return;
542         }
543
544         shared_ptr<Content> previous_c = *previous;
545
546         DCPTime const p = previous_c->position ();
547         previous_c->set_position (film, p + c->length_after_trim(film));
548         c->set_position (film, p);
549 }
550
551 void
552 Playlist::move_later (shared_ptr<const Film> film, shared_ptr<Content> c)
553 {
554         ContentList::iterator i = _content.begin();
555         while (i != _content.end() && *i != c) {
556                 ++i;
557         }
558
559         DCPOMATIC_ASSERT (i != _content.end ());
560
561         ContentList::iterator next = i;
562         ++next;
563
564         if (next == _content.end ()) {
565                 return;
566         }
567
568         shared_ptr<Content> next_c = *next;
569
570         next_c->set_position (film, c->position());
571         c->set_position (film, c->position() + next_c->length_after_trim(film));
572 }
573
574 int64_t
575 Playlist::required_disk_space (shared_ptr<const Film> film, int j2k_bandwidth, int audio_channels, int audio_frame_rate) const
576 {
577         int64_t video = uint64_t (j2k_bandwidth / 8) * length(film).seconds();
578         int64_t audio = uint64_t (audio_channels * audio_frame_rate * 3) * length(film).seconds();
579
580         BOOST_FOREACH (shared_ptr<Content> i, _content) {
581                 shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (i);
582                 if (d) {
583                         if (d->reference_video()) {
584                                 video -= uint64_t (j2k_bandwidth / 8) * d->length_after_trim(film).seconds();
585                         }
586                         if (d->reference_audio()) {
587                                 audio -= uint64_t (audio_channels * audio_frame_rate * 3) * d->length_after_trim(film).seconds();
588                         }
589                 }
590         }
591
592         /* Add on 64k for bits and pieces (metadata, subs etc) */
593         return video + audio + 65536;
594 }
595
596 string
597 Playlist::content_summary (shared_ptr<const Film> film, DCPTimePeriod period) const
598 {
599         string best_summary;
600         int best_score = -1;
601         BOOST_FOREACH (shared_ptr<Content> i, _content) {
602                 int score = 0;
603                 optional<DCPTimePeriod> const o = DCPTimePeriod(i->position(), i->end(film)).overlap (period);
604                 if (o) {
605                         score += 100 * o.get().duration().get() / period.duration().get();
606                 }
607
608                 if (i->video) {
609                         score += 100;
610                 }
611
612                 if (score > best_score) {
613                         best_summary = i->path(0).leaf().string();
614                         best_score = score;
615                 }
616         }
617
618         return best_summary;
619 }
620
621 pair<double, double>
622 Playlist::speed_up_range (int dcp_video_frame_rate) const
623 {
624         pair<double, double> range (DBL_MAX, -DBL_MAX);
625
626         BOOST_FOREACH (shared_ptr<Content> i, _content) {
627                 if (!i->video) {
628                         continue;
629                 }
630                 if (i->video_frame_rate()) {
631                         FrameRateChange const frc (i->video_frame_rate().get(), dcp_video_frame_rate);
632                         range.first = min (range.first, frc.speed_up);
633                         range.second = max (range.second, frc.speed_up);
634                 } else {
635                         FrameRateChange const frc (dcp_video_frame_rate, dcp_video_frame_rate);
636                         range.first = min (range.first, frc.speed_up);
637                         range.second = max (range.second, frc.speed_up);
638                 }
639         }
640
641         return range;
642 }