Fix subtitling logic in DCI naming.
[dcpomatic.git] / src / lib / film_state.cc
1 /*
2     Copyright (C) 2012 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 /** @file src/film_state.cc
21  *  @brief The state of a Film.  This is separate from Film so that
22  *  state can easily be copied and kept around for reference
23  *  by long-running jobs.  This avoids the jobs getting confused
24  *  by the user changing Film settings during their run.
25  */
26
27 #include <fstream>
28 #include <string>
29 #include <iomanip>
30 #include <sstream>
31 #include <boost/filesystem.hpp>
32 #include <boost/date_time.hpp>
33 #include "film_state.h"
34 #include "scaler.h"
35 #include "filter.h"
36 #include "format.h"
37 #include "dcp_content_type.h"
38 #include "util.h"
39 #include "exceptions.h"
40
41 using namespace std;
42 using namespace boost;
43
44 /** Write state to a stream.
45  *  @param f Stream to write to.
46  */
47 void
48 FilmState::write_metadata (ofstream& f) const
49 {
50         /* User stuff */
51         f << "name " << name << "\n";
52         f << "use_dci_name " << use_dci_name << "\n";
53         f << "content " << content << "\n";
54         if (dcp_content_type) {
55                 f << "dcp_content_type " << dcp_content_type->pretty_name () << "\n";
56         }
57         f << "frames_per_second " << frames_per_second << "\n";
58         if (format) {
59                 f << "format " << format->as_metadata () << "\n";
60         }
61         f << "left_crop " << crop.left << "\n";
62         f << "right_crop " << crop.right << "\n";
63         f << "top_crop " << crop.top << "\n";
64         f << "bottom_crop " << crop.bottom << "\n";
65         for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
66                 f << "filter " << (*i)->id () << "\n";
67         }
68         f << "scaler " << scaler->id () << "\n";
69         f << "dcp_frames " << dcp_frames << "\n";
70
71         f << "dcp_trim_action ";
72         switch (dcp_trim_action) {
73         case CUT:
74                 f << "cut\n";
75                 break;
76         case BLACK_OUT:
77                 f << "black_out\n";
78                 break;
79         }
80         
81         f << "dcp_ab " << (dcp_ab ? "1" : "0") << "\n";
82         f << "selected_audio_stream " << audio_stream << "\n";
83         f << "audio_gain " << audio_gain << "\n";
84         f << "audio_delay " << audio_delay << "\n";
85         f << "still_duration " << still_duration << "\n";
86         f << "with_subtitles " << with_subtitles << "\n";
87         f << "subtitle_offset " << subtitle_offset << "\n";
88         f << "subtitle_scale " << subtitle_scale << "\n";
89         f << "audio_language " << audio_language << "\n";
90         f << "subtitle_language " << subtitle_language << "\n";
91         f << "territory " << territory << "\n";
92         f << "rating " << rating << "\n";
93         f << "studio " << studio << "\n";
94         f << "facility " << facility << "\n";
95         f << "package_type " << package_type << "\n";
96
97         /* Cached stuff; this is information about our content; we could
98            look it up each time, but that's slow.
99         */
100         for (vector<int>::const_iterator i = thumbs.begin(); i != thumbs.end(); ++i) {
101                 f << "thumb " << *i << "\n";
102         }
103         f << "width " << size.width << "\n";
104         f << "height " << size.height << "\n";
105         f << "length " << length << "\n";
106         f << "audio_channels " << audio_channels << "\n";
107         f << "audio_sample_rate " << audio_sample_rate << "\n";
108         f << "audio_sample_format " << audio_sample_format_to_string (audio_sample_format) << "\n";
109         f << "content_digest " << content_digest << "\n";
110         f << "selected_subtitle_stream " << subtitle_stream << "\n";
111         f << "has_subtitles " << has_subtitles << "\n";
112
113         for (vector<Stream>::const_iterator i = audio_streams.begin(); i != audio_streams.end(); ++i) {
114                 f << "audio_stream " << i->to_string () << "\n";
115         }
116
117         for (vector<Stream>::const_iterator i = subtitle_streams.begin(); i != subtitle_streams.end(); ++i) {
118                 f << "subtitle_stream " << i->to_string () << "\n";
119         }
120 }
121
122 /** Read state from a key / value pair.
123  *  @param k Key.
124  *  @param v Value.
125  */
126 void
127 FilmState::read_metadata (string k, string v)
128 {
129         /* User-specified stuff */
130         if (k == "name") {
131                 name = v;
132         } else if (k == "use_dci_name") {
133                 use_dci_name = (v == "1");
134         } else if (k == "content") {
135                 content = v;
136         } else if (k == "dcp_content_type") {
137                 dcp_content_type = DCPContentType::from_pretty_name (v);
138         } else if (k == "frames_per_second") {
139                 frames_per_second = atof (v.c_str ());
140         } else if (k == "format") {
141                 format = Format::from_metadata (v);
142         } else if (k == "left_crop") {
143                 crop.left = atoi (v.c_str ());
144         } else if (k == "right_crop") {
145                 crop.right = atoi (v.c_str ());
146         } else if (k == "top_crop") {
147                 crop.top = atoi (v.c_str ());
148         } else if (k == "bottom_crop") {
149                 crop.bottom = atoi (v.c_str ());
150         } else if (k == "filter") {
151                 filters.push_back (Filter::from_id (v));
152         } else if (k == "scaler") {
153                 scaler = Scaler::from_id (v);
154         } else if (k == "dcp_frames") {
155                 dcp_frames = atoi (v.c_str ());
156         } else if (k == "dcp_trim_action") {
157                 if (v == "cut") {
158                         dcp_trim_action = CUT;
159                 } else if (v == "black_out") {
160                         dcp_trim_action = BLACK_OUT;
161                 }
162         } else if (k == "dcp_ab") {
163                 dcp_ab = (v == "1");
164         } else if (k == "selected_audio_stream") {
165                 audio_stream = atoi (v.c_str ());
166         } else if (k == "audio_gain") {
167                 audio_gain = atof (v.c_str ());
168         } else if (k == "audio_delay") {
169                 audio_delay = atoi (v.c_str ());
170         } else if (k == "still_duration") {
171                 still_duration = atoi (v.c_str ());
172         } else if (k == "with_subtitles") {
173                 with_subtitles = (v == "1");
174         } else if (k == "subtitle_offset") {
175                 subtitle_offset = atoi (v.c_str ());
176         } else if (k == "subtitle_scale") {
177                 subtitle_scale = atof (v.c_str ());
178         } else if (k == "selected_subtitle_stream") {
179                 subtitle_stream = atoi (v.c_str ());
180         } else if (k == "audio_language") {
181                 audio_language = v;
182         } else if (k == "subtitle_language") {
183                 subtitle_language = v;
184         } else if (k == "territory") {
185                 territory = v;
186         } else if (k == "rating") {
187                 rating = v;
188         } else if (k == "studio") {
189                 studio = v;
190         } else if (k == "facility") {
191                 facility = v;
192         } else if (k == "package_type") {
193                 package_type = v;
194         }
195         
196         /* Cached stuff */
197         if (k == "thumb") {
198                 int const n = atoi (v.c_str ());
199                 /* Only add it to the list if it still exists */
200                 if (filesystem::exists (thumb_file_for_frame (n))) {
201                         thumbs.push_back (n);
202                 }
203         } else if (k == "width") {
204                 size.width = atoi (v.c_str ());
205         } else if (k == "height") {
206                 size.height = atoi (v.c_str ());
207         } else if (k == "length") {
208                 length = atof (v.c_str ());
209         } else if (k == "audio_channels") {
210                 audio_channels = atoi (v.c_str ());
211         } else if (k == "audio_sample_rate") {
212                 audio_sample_rate = atoi (v.c_str ());
213         } else if (k == "audio_sample_format") {
214                 audio_sample_format = audio_sample_format_from_string (v);
215         } else if (k == "content_digest") {
216                 content_digest = v;
217         } else if (k == "has_subtitles") {
218                 has_subtitles = (v == "1");
219         } else if (k == "audio_stream") {
220                 audio_streams.push_back (Stream (v));
221         } else if (k == "subtitle_stream") {
222                 subtitle_streams.push_back (Stream (v));
223         }
224 }
225
226
227 /** @param n A thumb index.
228  *  @return The path to the thumb's image file.
229  */
230 string
231 FilmState::thumb_file (int n) const
232 {
233         return thumb_file_for_frame (thumb_frame (n));
234 }
235
236 /** @param n A frame index within the Film.
237  *  @return The path to the thumb's image file for this frame;
238  *  we assume that it exists.
239  */
240 string
241 FilmState::thumb_file_for_frame (int n) const
242 {
243         return thumb_base_for_frame(n) + ".png";
244 }
245
246 string
247 FilmState::thumb_base (int n) const
248 {
249         return thumb_base_for_frame (thumb_frame (n));
250 }
251
252 string
253 FilmState::thumb_base_for_frame (int n) const
254 {
255         stringstream s;
256         s.width (8);
257         s << setfill('0') << n;
258         
259         filesystem::path p;
260         p /= dir ("thumbs");
261         p /= s.str ();
262                 
263         return p.string ();
264 }
265
266
267 /** @param n A thumb index.
268  *  @return The frame within the Film that it is for.
269  */
270 int
271 FilmState::thumb_frame (int n) const
272 {
273         assert (n < int (thumbs.size ()));
274         return thumbs[n];
275 }
276
277 Size
278 FilmState::cropped_size (Size s) const
279 {
280         s.width -= crop.left + crop.right;
281         s.height -= crop.top + crop.bottom;
282         return s;
283 }
284
285 /** Given a directory name, return its full path within the Film's directory.
286  *  The directory (and its parents) will be created if they do not exist.
287  */
288 string
289 FilmState::dir (string d) const
290 {
291         filesystem::path p;
292         p /= directory;
293         p /= d;
294         filesystem::create_directories (p);
295         return p.string ();
296 }
297
298 /** Given a file or directory name, return its full path within the Film's directory */
299 string
300 FilmState::file (string f) const
301 {
302         filesystem::path p;
303         p /= directory;
304         p /= f;
305         return p.string ();
306 }
307
308 string
309 FilmState::content_path () const
310 {
311         if (filesystem::path(content).has_root_directory ()) {
312                 return content;
313         }
314
315         return file (content);
316 }
317
318 ContentType
319 FilmState::content_type () const
320 {
321 #if BOOST_FILESYSTEM_VERSION == 3
322         string ext = filesystem::path(content).extension().string();
323 #else
324         string ext = filesystem::path(content).extension();
325 #endif
326
327         transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
328         
329         if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
330                 return STILL;
331         }
332
333         return VIDEO;
334 }
335
336 /** @return Number of bytes per sample of a single channel */
337 int
338 FilmState::bytes_per_sample () const
339 {
340         switch (audio_sample_format) {
341         case AV_SAMPLE_FMT_S16:
342                 return 2;
343         default:
344                 return 0;
345         }
346
347         return 0;
348 }
349
350 int
351 FilmState::target_sample_rate () const
352 {
353         /* Resample to a DCI-approved sample rate */
354         double t = dcp_audio_sample_rate (audio_sample_rate);
355
356         /* Compensate for the fact that video will be rounded to the
357            nearest integer number of frames per second.
358         */
359         if (rint (frames_per_second) != frames_per_second) {
360                 t *= frames_per_second / rint (frames_per_second);
361         }
362
363         return rint (t);
364 }
365
366 int
367 FilmState::dcp_length () const
368 {
369         if (dcp_frames) {
370                 return dcp_frames;
371         }
372
373         return length;
374 }
375
376 /** @return a DCI-compliant name for a DCP of this film */
377 string
378 FilmState::dci_name () const
379 {
380         stringstream d;
381
382         string fixed_name = to_upper_copy (name);
383         for (size_t i = 0; i < fixed_name.length(); ++i) {
384                 if (fixed_name[i] == ' ') {
385                         fixed_name[i] = '-';
386                 }
387         }
388
389         /* Spec is that the name part should be maximum 14 characters, as I understand it */
390         if (fixed_name.length() > 14) {
391                 fixed_name = fixed_name.substr (0, 14);
392         }
393
394         d << fixed_name << "_";
395
396         if (dcp_content_type) {
397                 d << dcp_content_type->dci_name() << "_";
398         }
399
400         if (format) {
401                 d << format->dci_name() << "_";
402         }
403
404         if (!audio_language.empty ()) {
405                 d << audio_language;
406                 if (!subtitle_language.empty() && with_subtitles) {
407                         d << "-" << subtitle_language;
408                 } else {
409                         d << "-XX";
410                 }
411                         
412                 d << "_";
413         }
414
415         if (!territory.empty ()) {
416                 d << territory;
417                 if (!rating.empty ()) {
418                         d << "-" << rating;
419                 }
420                 d << "_";
421         }
422
423         switch (audio_channels) {
424         case 1:
425                 d << "10_";
426                 break;
427         case 2:
428                 d << "20_";
429                 break;
430         case 6:
431                 d << "51_";
432                 break;
433         }
434
435         d << "2K_";
436
437         if (!studio.empty ()) {
438                 d << studio << "_";
439         }
440
441         gregorian::date today = gregorian::day_clock::local_day ();
442         d << gregorian::to_iso_string (today) << "_";
443
444         if (!facility.empty ()) {
445                 d << facility << "_";
446         }
447
448         if (!package_type.empty ()) {
449                 d << package_type;
450         }
451
452         return d.str ();
453 }
454
455 /** @return name to give the DCP */
456 string
457 FilmState::dcp_name () const
458 {
459         if (use_dci_name) {
460                 return dci_name ();
461         }
462
463         return name;
464 }
465
466