Remove LocaleGuard and lexical_cast<> in favour of libdcp::raw_convert,
[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         Time next_left = 0;
83         Time next_right = 0;
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() + 1;
93                 } else {
94                         vc->set_position (next_left);
95                         next_left = vc->end() + 1;
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, shared_ptr<const cxml::Node> 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 Time
263 Playlist::length () const
264 {
265         Time len = 0;
266         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
267                 len = max (len, (*i)->end() + 1);
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 Time
288 Playlist::video_end () const
289 {
290         Time end = 0;
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 void
301 Playlist::set_sequence_video (bool s)
302 {
303         _sequence_video = s;
304 }
305
306 bool
307 ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
308 {
309         return a->position() < b->position();
310 }
311
312 /** @return content in an undefined order */
313 ContentList
314 Playlist::content () const
315 {
316         return _content;
317 }
318
319 void
320 Playlist::repeat (ContentList c, int n)
321 {
322         pair<Time, Time> range (TIME_MAX, 0);
323         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
324                 range.first = min (range.first, (*i)->position ());
325                 range.second = max (range.second, (*i)->position ());
326                 range.first = min (range.first, (*i)->end ());
327                 range.second = max (range.second, (*i)->end ());
328         }
329
330         Time pos = range.second;
331         for (int i = 0; i < n; ++i) {
332                 for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
333                         shared_ptr<Content> copy = (*i)->clone ();
334                         copy->set_position (pos + copy->position() - range.first);
335                         _content.push_back (copy);
336                 }
337                 pos += range.second - range.first;
338         }
339
340         sort (_content.begin(), _content.end(), ContentSorter ());
341         
342         reconnect ();
343         Changed ();
344 }
345
346 void
347 Playlist::move_earlier (shared_ptr<Content> c)
348 {
349         sort (_content.begin(), _content.end(), ContentSorter ());
350         
351         ContentList::iterator previous = _content.end ();
352         ContentList::iterator i = _content.begin();
353         while (i != _content.end() && *i != c) {
354                 previous = i;
355                 ++i;
356         }
357
358         assert (i != _content.end ());
359         if (previous == _content.end ()) {
360                 return;
361         }
362         
363         Time const p = (*previous)->position ();
364         (*previous)->set_position (p + c->length_after_trim ());
365         c->set_position (p);
366         sort (_content.begin(), _content.end(), ContentSorter ());
367         
368         Changed ();
369 }
370
371 void
372 Playlist::move_later (shared_ptr<Content> c)
373 {
374         sort (_content.begin(), _content.end(), ContentSorter ());
375         
376         ContentList::iterator i = _content.begin();
377         while (i != _content.end() && *i != c) {
378                 ++i;
379         }
380
381         assert (i != _content.end ());
382
383         ContentList::iterator next = i;
384         ++next;
385
386         if (next == _content.end ()) {
387                 return;
388         }
389
390         Time const p = (*next)->position ();
391         (*next)->set_position (c->position ());
392         c->set_position (p + c->length_after_trim ());
393         sort (_content.begin(), _content.end(), ContentSorter ());
394         
395         Changed ();
396 }