Use enum class for the things in types.h
[libdcp.git] / src / dcp_time.cc
1 /*
2     Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp 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     libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 /** @file  src/dcp_time.cc
35  *  @brief Time class.
36  */
37
38 #include "raw_convert.h"
39 #include "dcp_time.h"
40 #include "exceptions.h"
41 #include "compose.hpp"
42 #include "dcp_assert.h"
43 #include <boost/algorithm/string.hpp>
44 #include <boost/optional.hpp>
45 #include <iostream>
46 #include <vector>
47 #include <cmath>
48
49 using namespace std;
50 using namespace boost;
51 using namespace dcp;
52
53 Time::Time (int frame, double frames_per_second, int tcr_)
54 {
55         set (double (frame) / frames_per_second, tcr_);
56 }
57
58 /** Construct a Time from a number of seconds and a timecode rate.
59  *
60  *  @param seconds A number of seconds.
61  *  @param tcr_ Timecode rate.
62  */
63 Time::Time (double seconds, int tcr_)
64 {
65         set (seconds, tcr_);
66 }
67
68 /** Construct a Time with specified timecode rate and using the supplied
69  *  number of seconds.
70  *
71  *  @param seconds A number of seconds.
72  *  @param tcr_ Timecode rate to use.
73  */
74 void
75 Time::set (double seconds, int tcr_)
76 {
77         s = floor (seconds);
78         tcr = tcr_;
79
80         e = int (round ((seconds - s) * tcr));
81
82         if (s >= 60) {
83                 m = s / 60;
84                 s -= m * 60;
85         } else {
86                 m = 0;
87         }
88
89         if (m >= 60) {
90                 h = m / 60;
91                 m -= h * 60;
92         } else {
93                 h = 0;
94         }
95 }
96
97 /** @param time String of the form
98  *     HH:MM:SS:EE                          for SMPTE
99  *     HH:MM:SS:E[E[E]] or HH:MM:SS.s[s[s]] for Interop
100  *  where HH are hours, MM minutes, SS seconds, EE editable units and
101  *  sss millseconds.
102  *
103  *  @param tcr_ Timecode rate if this is a SMPTE time, otherwise empty for an Interop time.
104  */
105 Time::Time (string time, optional<int> tcr_)
106 {
107         vector<string> b;
108         split (b, time, is_any_of (":"));
109
110         if (b.size() < 3 || b[0].empty() || b[1].empty() || b[0].length() > 2 || b[1].length() > 2) {
111                 boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1", time)));
112         }
113
114         if (!tcr_) {
115                 /* Interop */
116                 if (b.size() == 3) {
117                         /* HH:MM:SS.s[s[s]] */
118                         vector<string> bs;
119                         split (bs, b[2], is_any_of ("."));
120                         if (bs.size() != 2) {
121                                 boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1", time)));
122                         }
123
124                         h = raw_convert<int> (b[0]);
125                         m = raw_convert<int> (b[1]);
126                         if (bs[0].empty() || bs[0].length() > 2) {
127                                 boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; %2 has bad length", time, bs[0])));
128                         }
129                         s = raw_convert<int> (bs[0]);
130                         if (bs[1].empty() || bs[1].length() > 3) {
131                                 boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; %2 has bad length", time, bs[1])));
132                         }
133                         e = raw_convert<int> (bs[1]);
134                         tcr = 1000;
135                 } else if (b.size() == 4) {
136                         /* HH:MM:SS:EE[E] */
137                         h = raw_convert<int> (b[0]);
138                         m = raw_convert<int> (b[1]);
139                         if (b[2].empty() || b[2].length() > 2) {
140                                 boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; %2 has bad length", time, b[2])));
141                         }
142                         s = raw_convert<int> (b[2]);
143                         if (b[3].empty() || b[3].length() > 3) {
144                                 boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; %2 has bad length", time, b[3])));
145                         }
146                         e = raw_convert<int> (b[3]);
147                         tcr = 250;
148                 } else {
149                         boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1", time)));
150                 }
151
152         } else {
153                 /* SMPTE: HH:MM:SS:EE */
154                 split (b, time, is_any_of (":"));
155                 if (b.size() != 4) {
156                         boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; does not have 4 parts", time)));
157                 }
158
159                 h = raw_convert<int> (b[0]);
160                 m = raw_convert<int> (b[1]);
161                 if (b[2].empty() || b[2].length() > 2) {
162                         boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; %2 has bad length", time, b[2])));
163                 }
164                 s = raw_convert<int> (b[2]);
165                 if (b[3].empty() || b[3].length() > 2) {
166                         boost::throw_exception (ReadError (String::compose ("unrecognised time specification %1; %2 has bad length", time, b[3])));
167                 }
168                 e = raw_convert<int> (b[3]);
169                 tcr = tcr_.get();
170         }
171 }
172
173 bool
174 dcp::operator== (Time const & a, Time const & b)
175 {
176         return (a.h == b.h && a.m == b.m && a.s == b.s && (a.e * b.tcr) == (b.e * a.tcr));
177 }
178
179 bool
180 dcp::operator!= (Time const & a, Time const & b)
181 {
182         return !(a == b);
183 }
184
185 bool
186 dcp::operator<= (Time const & a, Time const & b)
187 {
188         return a < b || a == b;
189 }
190
191 bool
192 dcp::operator>= (Time const & a, Time const & b)
193 {
194         return a > b || a == b;
195 }
196
197 bool
198 dcp::operator< (Time const & a, Time const & b)
199 {
200         if (a.h != b.h) {
201                 return a.h < b.h;
202         }
203
204         if (a.m != b.m) {
205                 return a.m < b.m;
206         }
207
208         if (a.s != b.s) {
209                 return a.s < b.s;
210         }
211
212         return (a.e * b.tcr) < (b.e * a.tcr);
213 }
214
215 bool
216 dcp::operator> (Time const & a, Time const & b)
217 {
218         if (a.h != b.h) {
219                 return a.h > b.h;
220         }
221
222         if (a.m != b.m) {
223                 return a.m > b.m;
224         }
225
226         if (a.s != b.s) {
227                 return a.s > b.s;
228         }
229
230         return (a.e * b.tcr) > (b.e * a.tcr);
231 }
232
233 ostream &
234 dcp::operator<< (ostream& s, Time const & t)
235 {
236         s << t.h << ":" << t.m << ":" << t.s << "." << t.e;
237         return s;
238 }
239
240 dcp::Time
241 dcp::operator+ (Time a, Time b)
242 {
243         Time r;
244
245         /* Make sure we have a common tcr */
246         if (a.tcr != b.tcr) {
247                 a.e *= b.tcr;
248                 b.e *= a.tcr;
249                 r.tcr = a.tcr * b.tcr;
250         } else {
251                 r.tcr = a.tcr;
252         }
253
254         r.e = a.e + b.e;
255         if (r.e >= r.tcr) {
256                 r.e -= r.tcr;
257                 r.s++;
258         }
259
260         r.s += a.s + b.s;
261         if (r.s >= 60) {
262                 r.s -= 60;
263                 r.m++;
264         }
265
266         r.m += a.m + b.m;
267         if (r.m >= 60) {
268                 r.m -= 60;
269                 r.h++;
270         }
271
272         r.h += a.h + b.h;
273
274         return r;
275 }
276
277 dcp::Time
278 dcp::operator- (Time a, Time b)
279 {
280         Time r;
281
282         /* Make sure we have a common tcr */
283         if (a.tcr != b.tcr) {
284                 a.e *= b.tcr;
285                 b.e *= a.tcr;
286                 r.tcr = a.tcr * b.tcr;
287         } else {
288                 r.tcr = a.tcr;
289         }
290
291         r.e = a.e - b.e;
292         if (r.e < 0) {
293                 r.e += r.tcr;
294                 r.s--;
295         }
296
297         r.s += (a.s - b.s);
298         if (r.s < 0) {
299                 r.s += 60;
300                 r.m--;
301         }
302
303         r.m += (a.m - b.m);
304         if (r.m < 0) {
305                 r.m += 60;
306                 r.h--;
307         }
308
309         r.h += (a.h - b.h);
310
311         return r;
312 }
313
314 float
315 dcp::operator/ (Time a, Time const & b)
316 {
317         int64_t const at = a.h * 3600 + a.m * 60 + a.s * float (a.e) / a.tcr;
318         int64_t const bt = b.h * 3600 + b.m * 60 + b.s * float (b.e) / b.tcr;
319         return float (at) / bt;
320 }
321
322 /** @return A string of the form h:m:s:e padded as in 00:00:00:000 (for Interop) or 00:00:00:00 (for SMPTE) */
323 string
324 Time::as_string (Standard standard) const
325 {
326         char buffer[64];
327
328         if (standard == Standard::SMPTE) {
329                 snprintf (buffer, sizeof(buffer), "%02d:%02d:%02d:%02d", h, m, s, e);
330         } else {
331                 snprintf (buffer, sizeof(buffer), "%02d:%02d:%02d:%03d", h, m, s, e);
332         }
333
334         return buffer;
335 }
336
337 /** @param tcr_ Timecode rate with which the return value should be counted.
338  *  @return the total number of editable units that this time consists of at the specified timecode rate, rounded up
339  *  to the nearest editable unit. For example, as_editable_units (24) returns the total time in frames at 24fps.
340  */
341 int64_t
342 Time::as_editable_units (int tcr_) const
343 {
344         return ceil (int64_t(e) * double (tcr_) / tcr) + int64_t(s) * tcr_ + int64_t(m) * 60 * tcr_ + int64_t(h) * 60 * 60 * tcr_;
345 }
346
347 /** @return the total number of seconds that this time consists of */
348 double
349 Time::as_seconds () const
350 {
351         return h * 3600 + m * 60 + s + double(e) / tcr;
352 }
353
354 /** @param tcr_ New timecode rate.
355  *  @return A new Time which is this time at the spcified new timecode rate.
356  */
357 Time
358 Time::rebase (int tcr_) const
359 {
360         long int e_ = lrintf (float (e) * tcr_ / tcr);
361         int s_ = s;
362         if (e_ >= tcr_) {
363                 e_ -= tcr_;
364                 ++s_;
365         }
366         int m_ = m;
367         if (s_ >= 60) {
368                 s_ -= 60;
369                 ++m_;
370         }
371         int h_ = h;
372         if (m_ >= 60) {
373                 m_ -= 60;
374                 ++h_;
375         }
376
377         return Time (h_, m_, s_, e_, tcr_);
378 }