Various time-related fixes; fix daft hang on decodes.
[dcpomatic.git] / src / lib / playlist.cc
1 /* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
2
3 /*
4     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
5
6     This program 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     This program 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 this program; if not, write to the Free Software
18     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 */
21
22 #include <libcxml/cxml.h>
23 #include <boost/shared_ptr.hpp>
24 #include <boost/lexical_cast.hpp>
25 #include "playlist.h"
26 #include "sndfile_content.h"
27 #include "sndfile_decoder.h"
28 #include "video_content.h"
29 #include "ffmpeg_decoder.h"
30 #include "ffmpeg_content.h"
31 #include "imagemagick_decoder.h"
32 #include "imagemagick_content.h"
33 #include "job.h"
34 #include "config.h"
35 #include "util.h"
36
37 #include "i18n.h"
38
39 using std::list;
40 using std::cout;
41 using std::vector;
42 using std::min;
43 using std::max;
44 using std::string;
45 using std::stringstream;
46 using boost::optional;
47 using boost::shared_ptr;
48 using boost::weak_ptr;
49 using boost::dynamic_pointer_cast;
50 using boost::lexical_cast;
51
52 Playlist::Playlist ()
53         : _loop (1)
54 {
55
56 }
57
58 Playlist::Playlist (shared_ptr<const Playlist> other)
59         : _loop (other->_loop)
60 {
61         for (RegionList::const_iterator i = other->_regions.begin(); i != other->_regions.end(); ++i) {
62                 _regions.push_back (Region (i->content->clone(), i->time, this));
63         }
64 }
65
66 void
67 Playlist::content_changed (weak_ptr<Content> c, int p)
68 {
69         ContentChanged (c, p);
70 }
71
72 string
73 Playlist::audio_digest () const
74 {
75         string t;
76         
77         for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) {
78                 if (!dynamic_pointer_cast<const AudioContent> (i->content)) {
79                         continue;
80                 }
81                 
82                 t += i->content->digest ();
83
84                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i->content);
85                 if (fc) {
86                         t += lexical_cast<string> (fc->audio_stream()->id);
87                 }
88         }
89
90         t += lexical_cast<string> (_loop);
91
92         return md5_digest (t.c_str(), t.length());
93 }
94
95 string
96 Playlist::video_digest () const
97 {
98         string t;
99         
100         for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) {
101                 if (!dynamic_pointer_cast<const VideoContent> (i->content)) {
102                         continue;
103                 }
104                 
105                 t += i->content->digest ();
106                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i->content);
107                 if (fc && fc->subtitle_stream()) {
108                         t += fc->subtitle_stream()->id;
109                 }
110         }
111
112         t += lexical_cast<string> (_loop);
113
114         return md5_digest (t.c_str(), t.length());
115 }
116
117 void
118 Playlist::set_from_xml (shared_ptr<const cxml::Node> node)
119 {
120         list<shared_ptr<cxml::Node> > c = node->node_children ("Region");
121         for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
122                 _regions.push_back (Region (*i, this));
123         }
124
125         _loop = node->number_child<int> ("Loop");
126 }
127
128 void
129 Playlist::as_xml (xmlpp::Node* node)
130 {
131         for (RegionList::iterator i = _regions.begin(); i != _regions.end(); ++i) {
132                 i->as_xml (node->add_child ("Region"));
133         }
134
135         node->add_child("Loop")->add_child_text(lexical_cast<string> (_loop));
136 }
137
138 void
139 Playlist::add (shared_ptr<Content> c)
140 {
141         _regions.push_back (Region (c, 0, this));
142         Changed ();
143 }
144
145 void
146 Playlist::remove (shared_ptr<Content> c)
147 {
148         RegionList::iterator i = _regions.begin ();
149         while (i != _regions.end() && i->content != c) {
150                 ++i;
151         }
152         
153         if (i != _regions.end ()) {
154                 _regions.erase (i);
155                 Changed ();
156         }
157 }
158
159 void
160 Playlist::set_loop (int l)
161 {
162         _loop = l;
163         Changed ();
164 }
165
166 bool
167 Playlist::has_subtitles () const
168 {
169         for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) {
170                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (i->content);
171                 if (fc && !fc->subtitle_streams().empty()) {
172                         return true;
173                 }
174         }
175
176         return false;
177 }
178
179 Playlist::Region::Region (shared_ptr<Content> c, Time t, Playlist* p)
180         : content (c)
181         , time (t)
182 {
183         connection = c->Changed.connect (bind (&Playlist::content_changed, p, _1, _2));
184 }
185
186 Playlist::Region::Region (shared_ptr<const cxml::Node> node, Playlist* p)
187 {
188         shared_ptr<const cxml::Node> content_node = node->node_child ("Content");
189         string const type = content_node->string_child ("Type");
190
191         if (type == "FFmpeg") {
192                 content.reset (new FFmpegContent (content_node));
193         } else if (type == "ImageMagick") {
194                 content.reset (new ImageMagickContent (content_node));
195         } else if (type == "Sndfile") {
196                 content.reset (new SndfileContent (content_node));
197         }
198
199         time = node->number_child<Time> ("Time");
200         connection = content->Changed.connect (bind (&Playlist::content_changed, p, _1, _2));
201 }
202
203 void
204 Playlist::Region::as_xml (xmlpp::Node* node) const
205 {
206         xmlpp::Node* sub = node->add_child ("Content");
207         content->as_xml (sub);
208         node->add_child ("Time")->add_child_text (lexical_cast<string> (time));
209 }
210
211 class FrameRateCandidate
212 {
213 public:
214         FrameRateCandidate (float source_, int dcp_)
215                 : source (source_)
216                 , dcp (dcp_)
217         {}
218
219         float source;
220         int dcp;
221 };
222
223 int
224 Playlist::best_dcp_frame_rate () const
225 {
226         list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
227
228         /* Work out what rates we could manage, including those achieved by using skip / repeat. */
229         list<FrameRateCandidate> candidates;
230
231         /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
232         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
233                 candidates.push_back (FrameRateCandidate (*i, *i));
234         }
235
236         /* Then the skip/repeat ones */
237         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
238                 candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
239                 candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
240         }
241
242         /* Pick the best one, bailing early if we hit an exact match */
243         float error = std::numeric_limits<float>::max ();
244         optional<FrameRateCandidate> best;
245         list<FrameRateCandidate>::iterator i = candidates.begin();
246         while (i != candidates.end()) {
247
248                 float this_error = std::numeric_limits<float>::max ();
249                 for (RegionList::const_iterator j = _regions.begin(); j != _regions.end(); ++j) {
250                         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (j->content);
251                         if (!vc) {
252                                 continue;
253                         }
254
255                         this_error += fabs (i->source - vc->video_frame_rate ());
256                 }
257
258                 if (this_error < error) {
259                         error = this_error;
260                         best = *i;
261                 }
262
263                 ++i;
264         }
265
266         if (!best) {
267                 return 24;
268         }
269         
270         return best->dcp;
271 }
272
273 Time
274 Playlist::length (shared_ptr<const Film> film) const
275 {
276         Time len = 0;
277         for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) {
278                 Time const t = i->time + i->content->length (film);
279                 len = max (len, t);
280         }
281
282         return len;
283 }