Various incomplete hacks on regions / audio mapping.
[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 (shared_ptr<Region> (new 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 (shared_ptr<Region> (new 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 (shared_ptr<Region> (new 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         audio_mapping = AudioMapping (node->node_child ("AudioMapping"));
201         connection = content->Changed.connect (bind (&Playlist::content_changed, p, _1, _2));
202 }
203
204 void
205 Playlist::Region::as_xml (xmlpp::Node* node) const
206 {
207         xmlpp::Node* sub = node->add_child ("Content");
208         content->as_xml (sub);
209         node->add_child ("Time")->add_child_text (lexical_cast<string> (time));
210         audio_mapping.as_xml (node->add_child ("AudioMapping"));
211 }
212
213 class FrameRateCandidate
214 {
215 public:
216         FrameRateCandidate (float source_, int dcp_)
217                 : source (source_)
218                 , dcp (dcp_)
219         {}
220
221         float source;
222         int dcp;
223 };
224
225 int
226 Playlist::best_dcp_frame_rate () const
227 {
228         list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates ();
229
230         /* Work out what rates we could manage, including those achieved by using skip / repeat. */
231         list<FrameRateCandidate> candidates;
232
233         /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */
234         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
235                 candidates.push_back (FrameRateCandidate (*i, *i));
236         }
237
238         /* Then the skip/repeat ones */
239         for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) {
240                 candidates.push_back (FrameRateCandidate (float (*i) / 2, *i));
241                 candidates.push_back (FrameRateCandidate (float (*i) * 2, *i));
242         }
243
244         /* Pick the best one, bailing early if we hit an exact match */
245         float error = std::numeric_limits<float>::max ();
246         optional<FrameRateCandidate> best;
247         list<FrameRateCandidate>::iterator i = candidates.begin();
248         while (i != candidates.end()) {
249
250                 float this_error = std::numeric_limits<float>::max ();
251                 for (RegionList::const_iterator j = _regions.begin(); j != _regions.end(); ++j) {
252                         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*j)->content);
253                         if (!vc) {
254                                 continue;
255                         }
256
257                         this_error += fabs (i->source - vc->video_frame_rate ());
258                 }
259
260                 if (this_error < error) {
261                         error = this_error;
262                         best = *i;
263                 }
264
265                 ++i;
266         }
267
268         if (!best) {
269                 return 24;
270         }
271         
272         return best->dcp;
273 }
274
275 Time
276 Playlist::length (shared_ptr<const Film> film) const
277 {
278         Time len = 0;
279         for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) {
280                 Time const t = (*i)->time + (*i)->content->length (film);
281                 len = max (len, t);
282         }
283
284         return len;
285 }